其他分享
首页 > 其他分享> > 2019幸运飞艇冠军三码规律公式波胆567码计划公式实践技巧稳赢不输图解

2019幸运飞艇冠军三码规律公式波胆567码计划公式实践技巧稳赢不输图解

作者:互联网

Tomcat 应用中并行流带来的类加载问题

  1765243235

本文首发于 vivo互联网技术 微信公众号 
链接:https://mp.weixin.qq.com/s/f-X3n9cvDyU5f5NYH6mhxQ
作者:肖铭轩、王道环

随着 Java8 的不断流行,越来越多的开发人员使用并行流(parallel)这一特性提升代码执行效率。但是,作者发现在 Tomcat 容器中使用并行流会出现动态加载类失败的情况,通过对比 Tomcat 多个版本的源码,结合并行流和 JVM 类加载机制的原理,成功定位到问题来源。本文对这个问题展开分析,并给出解决方案。

一、问题场景

在某应用中,服务启动时会通过并行流调用 Dubbo,调用代码如下:

1 2 3 4 5 Lists.partition(ids, BATCH_QUERY_LIMIT).stream()      .parallel()      .map(Req::new)      .map(client::batchQuery)      .collect(Collectors.toList());
调用日志中发现大量的 WARN 日志com.alibaba.com.caucho.hessian.io.SerializerFactory.getDeserializer Hessian/Burlap:‘XXXXXXX’ is an unknown class in null:java.lang.ClassNotFoundException: XXXXXXX,在使用接口返回结果的时候抛出错误 java.lang.ClassCastException: java.util.HashMap cannot be cast to XXXXXXX。

二、原因分析

1、初步定位

首先根据错误日志可以看到,由于依赖的 Dubbo 服务返回参数的实体类没有找到,导致 Dubbo 返回的数据报文在反序列化时无法转换成对应的实体,类型强制转化中报了java.lang.ClassCastException。通过对线程堆栈和WARN日志定位到出现问题的类为com.alibaba.com.caucho.hessian.io.SerializerFactory,由于 _loader 为 null 所以无法对类进行加载,相关代码如下:

1 2 3 4 5 6 7 try {        Class cl = Class.forName(type, false, _loader);        deserializer = getDeserializer(cl);    catch (Exception e) {        log.warning("Hessian/Burlap: '" + type + "' is an unknown class in " + _loader + ":\n" + e);     log.log(Level.FINER, e.toString(), e);    }

 接下来继续向上定位为什么** _loader** 会为 nullSerializerFactory 构造方法中对 _loader 进行了初始化,初始化代码如下,可以看出 _loader 使用的是当前线程的 contextClassLoader。

1 2 3 4 5 6 7 public SerializerFactory() {     this(Thread.currentThread().getContextClassLoader()); }    public SerializerFactory(ClassLoader loader) {     _loader = loader; }

 根据堆栈看到当前线程为ForkJoinWorkerThread,ForkJoinWorkerThread是Fork/Join框架内的工作线程(Java8 并行流使用的就是Fork/Join)。JDK文档指出:

The context ClassLoader is provided by the creator of the thread for use by code running in this thread when loading classes and resources. If not set, the default is the ClassLoader context of the parent Thread.

因此当前的线程contextClassLoader应该和创建此线程的父线程保持一致才对,不应该是null啊?

继续看ForkJoinWorkerThread创建的源码,首先使用ForkJoinWorkerThreadFactory创建一个线程,然后将创建的线程注册到ForkJoinPool中,线程初始化的逻辑和普通线程并无差别,发现单独从JDK自身难以发现问题,因此将分析转移到Tomcat中。

2、Tomcat升级带来的问题

取 Tomcat7.0.x 的一些版本做了实验和对比,发现7.0.74之前的版本无此问题,但7.0.74之后的版本出现了类似问题,实验结果如下表。

至此已经将问题定位到了是Tomcat的版本所致,通过源代码比对,发现7.0.74版本之后的Tomcat中多了这样的代码:

1 2 3 4 5 6 7 if (forkJoinCommonPoolProtection && IS_JAVA_8_OR_LATER) {     // Don't override any explicitly set property     if (System.getProperty(FORK_JOIN_POOL_THREAD_FACTORY_PROPERTY) == null) {         System.setProperty(FORK_JOIN_POOL_THREAD_FACTORY_PROPERTY,                 "org.apache.catalina.startup.SafeForkJoinWorkerThreadFactory");     } }

 

1 2 3 4 5 6 7 private static class SafeForkJoinWorkerThread extends ForkJoinWorkerThread {       protected SafeForkJoinWorkerThread(ForkJoinPool pool) {        super(pool);        setContextClassLoader(ForkJoinPool.class.getClassLoader());    } }

在 Java8 环境下,7.0.74 版本之后的 Tomcat 会默认将 SafeForkJoinWorkerThreadFactory 作为 ForkJoinWorkerThread 的创建工厂,同时将该线程的 contextClassLoader 设置为ForkJoinPool.class.getClassLoader(),ForkJoinPool 是属于rt.jar包的类,由BootStrap ClassLoader加载,所以对应的类加载器为null。至此,_loader为空的问题已经清楚,但是Tomcat为什么要多此一举,将null作为这个 ForkJoinWorkerThread的contextClassLoader呢?

继续对比Tomcat的changeLog http://tomcat.apache.org/tomcat-7.0-doc/changelog.html 发现Tomcat在此版本修复了由ForkJoinPool引发的内存泄露问题 Bug 60620 - [JRE] Memory leak found in java.util.concurrent.ForkJoinPool,为什么线程的contextClassLoader会引起内存泄露呢?

标签:波胆,Tomcat,公式,ForkJoinPool,loader,线程,ForkJoinWorkerThread,稳赢,null
来源: https://www.cnblogs.com/hfdh435/p/11695914.html