欧美一级特黄大片做受成人-亚洲成人一区二区电影-激情熟女一区二区三区-日韩专区欧美专区国产专区

Tomcat應(yīng)用中并行流帶來的類加載問題

本文首發(fā)于 vivo互聯(lián)網(wǎng)技術(shù) 微信公眾號(hào)?
鏈接:https://mp.weixin.qq.com/s/f-X3n9cvDyU5f5NYH6mhxQ
作者:肖銘軒、王道環(huán)

成都創(chuàng)新互聯(lián)是專業(yè)的句容網(wǎng)站建設(shè)公司,句容接單;提供成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè),網(wǎng)頁(yè)設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行句容網(wǎng)站開發(fā)網(wǎng)頁(yè)制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來合作!

隨著 Java8 的不斷流行,越來越多的開發(fā)人員使用并行流(parallel)這一特性提升代碼執(zhí)行效率。但是,作者發(fā)現(xiàn)在 Tomcat 容器中使用并行流會(huì)出現(xiàn)動(dòng)態(tài)加載類失敗的情況,通過對(duì)比 Tomcat 多個(gè)版本的源碼,結(jié)合并行流和 JVM 類加載機(jī)制的原理,成功定位到問題來源。本文對(duì)這個(gè)問題展開分析,并給出解決方案。

一、問題場(chǎng)景

在某應(yīng)用中,服務(wù)啟動(dòng)時(shí)會(huì)通過并行流調(diào)用 Dubbo,調(diào)用代碼如下:

Lists.partition(ids, BATCH_QUERY_LIMIT).stream()
     .parallel()
     .map(Req::new)
     .map(client::batchQuery)
     .collect(Collectors.toList());

調(diào)用日志中發(fā)現(xiàn)大量的 WARN 日志com.alibaba.com.caucho.hessian.io.SerializerFactory.getDeserializer Hessian/Burlap:‘XXXXXXX’ is an unknown class in null:java.lang.ClassNotFoundException: XXXXXXX,在使用接口返回結(jié)果的時(shí)候拋出錯(cuò)誤 java.lang.ClassCastException: java.util.HashMap cannot be cast to XXXXXXX。

二、原因分析

1、初步定位

首先根據(jù)錯(cuò)誤日志可以看到,由于依賴的 Dubbo 服務(wù)返回參數(shù)的實(shí)體類沒有找到,導(dǎo)致 Dubbo 返回的數(shù)據(jù)報(bào)文在反序列化時(shí)無法轉(zhuǎn)換成對(duì)應(yīng)的實(shí)體,類型強(qiáng)制轉(zhuǎn)化中報(bào)了java.lang.ClassCastException。通過對(duì)線程堆棧和WARN日志定位到出現(xiàn)問題的類為com.alibaba.com.caucho.hessian.io.SerializerFactory,由于?_loader?為 null 所以無法對(duì)類進(jìn)行加載,相關(guān)代碼如下:

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);
   }

接下來繼續(xù)向上定位為什么?_loader?會(huì)為?null,SerializerFactory?構(gòu)造方法中對(duì)?_loader?進(jìn)行了初始化,初始化代碼如下,可以看出?_loader?使用的是當(dāng)前線程的 contextClassLoader。

public SerializerFactory() {
    this(Thread.currentThread().getContextClassLoader());
}

public SerializerFactory(ClassLoader loader) {
    _loader = loader;
}

根據(jù)堆??吹疆?dāng)前線程為ForkJoinWorkerThread,F(xiàn)orkJoinWorkerThread是Fork/Join框架內(nèi)的工作線程(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.

因此當(dāng)前的線程contextClassLoader應(yīng)該和創(chuàng)建此線程的父線程保持一致才對(duì),不應(yīng)該是null啊?

繼續(xù)看ForkJoinWorkerThread創(chuàng)建的源碼,首先使用ForkJoinWorkerThreadFactory創(chuàng)建一個(gè)線程,然后將創(chuàng)建的線程注冊(cè)到ForkJoinPool中,線程初始化的邏輯和普通線程并無差別,發(fā)現(xiàn)單獨(dú)從JDK自身難以發(fā)現(xiàn)問題,因此將分析轉(zhuǎn)移到Tomcat中。

2、Tomcat升級(jí)帶來的問題

取 Tomcat7.0.x 的一些版本做了實(shí)驗(yàn)和對(duì)比,發(fā)現(xiàn)7.0.74之前的版本無此問題,但7.0.74之后的版本出現(xiàn)了類似問題,實(shí)驗(yàn)結(jié)果如下表。

Tomcat 應(yīng)用中并行流帶來的類加載問題Tomcat 應(yīng)用中并行流帶來的類加載問題

至此已經(jīng)將問題定位到了是Tomcat的版本所致,通過源代碼比對(duì),發(fā)現(xiàn)7.0.74版本之后的Tomcat中多了這樣的代碼:

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");
    }
}
private static class SafeForkJoinWorkerThread extends ForkJoinWorkerThread {

   protected SafeForkJoinWorkerThread(ForkJoinPool pool) {
       super(pool);
       setContextClassLoader(ForkJoinPool.class.getClassLoader());
   }
}

在 Java8 環(huán)境下,7.0.74 版本之后的 Tomcat 會(huì)默認(rèn)將?SafeForkJoinWorkerThreadFactory?作為 ForkJoinWorkerThread 的創(chuàng)建工廠,同時(shí)將該線程的 contextClassLoader 設(shè)置為ForkJoinPool.class.getClassLoader(),F(xiàn)orkJoinPool 是屬于rt.jar包的類,由BootStrap ClassLoader加載,所以對(duì)應(yīng)的類加載器為null。至此,_loader為空的問題已經(jīng)清楚,但是Tomcat為什么要多此一舉,將null作為這個(gè) ForkJoinWorkerThread的contextClassLoader呢?

繼續(xù)對(duì)比Tomcat的changeLog?http://tomcat.apache.org/tomcat-7.0-doc/changelog.html 發(fā)現(xiàn)Tomcat在此版本修復(fù)了由ForkJoinPool引發(fā)的內(nèi)存泄露問題 Bug 60620 - [JRE] Memory leak found in java.util.concurrent.ForkJoinPool,為什么線程的contextClassLoader會(huì)引起內(nèi)存泄露呢?

3、contextClassLoader內(nèi)存泄露之謎

在JDK1.2以后,類加載器的雙親委派模型被廣泛引入。它的工作過程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把整個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載,流程如下圖。

Tomcat 應(yīng)用中并行流帶來的類加載問題Tomcat 應(yīng)用中并行流帶來的類加載問題

然而雙親委派的模型并不能保證應(yīng)用程序加載類的過程,一個(gè)典型的例子就是JNDI服務(wù),這些接口定義在rt.jar并由第三方提供實(shí)現(xiàn),Bootstrap ClassLoader顯然不認(rèn)識(shí)這些代碼。為了解決這個(gè)問題,JDK1.2同時(shí)引入了線程上下文類加載器(Thread Context ClassLoader)進(jìn)行類加載,作為雙親委派模型的補(bǔ)充。

回到內(nèi)存泄漏的問題上,設(shè)想一個(gè)場(chǎng)景,如果某個(gè)線程持有了ClassLoaderA(由ClassLoaderA加載了若干類),當(dāng)應(yīng)用程序需要對(duì)ClassLoaderA以及由ClassLoaderA加載出來的類卸載完成后,線程A仍然持有了ClassLoaderA的引用,然而業(yè)務(wù)方以為這些類以及加載器已經(jīng)卸載干凈,由于類加載器和其加載出的類雙向引用,這就造成了類加載器和其加載出來的類無法垃圾回收,造成內(nèi)存泄露。在并行流中,F(xiàn)orkJoinPool和ForkJoinWorkerThreadFactory默認(rèn)是靜態(tài)且共享的(JDK官方推薦,創(chuàng)建線程本身是相對(duì)重的操作,盡量避免重復(fù)創(chuàng)建ForkJoinWorkerThread 造成資源浪費(fèi)),下圖描繪了發(fā)生內(nèi)存泄露的場(chǎng)景:

Tomcat 應(yīng)用中并行流帶來的類加載問題

因此 Tomcat 默認(rèn)使用SafeForkJoinWorkerThreadFactory作為ForkJoinWorkerThreadFactory,并將該工廠創(chuàng)建的ForkJoinWorkerThread的contextClassLoader都指定為ForkJoinPool.class.getClassLoader(),而不是JDK默認(rèn)的繼承父線程的contextClassLoader,進(jìn)而避免了Tomcat應(yīng)用中由并行流帶來的類加載器內(nèi)存泄露。

三、總結(jié)

在開發(fā)過程中,如果在計(jì)算密集型任務(wù)中使用了并行流,請(qǐng)避免在子任務(wù)中動(dòng)態(tài)加載類;其他業(yè)務(wù)場(chǎng)景請(qǐng)盡量使用線程池,而非并行流。總之,我們需要避免在Tomcat應(yīng)用中通過并行流進(jìn)行自定義類或者第三方類的動(dòng)態(tài)加載。

網(wǎng)頁(yè)名稱:Tomcat應(yīng)用中并行流帶來的類加載問題
分享網(wǎng)址:http://www.aaarwkj.com/article46/gjcdeg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供電子商務(wù)網(wǎng)站策劃、ChatGPT、網(wǎng)站制作、用戶體驗(yàn)、外貿(mào)網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)公司
日韩高清一级黄色大片网站| 久久久久久亚洲精品少妇| 日韩欧美一二区久久麻豆| 性感美女国产精品一区二区| 日本特黄高清免费大片| 99精品欧美日韩在线播放| 依依成人影院在线观看av| 午夜视频在线观看91| 国产免费不卡午夜福利在线 | 国产精品久久123区| 国产三级国产剧情国产av| 国产精品一区二区夜夜夜| 国产黄色大片一级久久 | 亚洲图文一区二区三区四区| 在线观看男人的天堂av| 亚洲精品永久在线观看| 久久午夜人妻一区二区| 久久这里精品中文字幕| 欧美三级高清视频在线播放| 日韩av熟女中文字幕| 亚洲综合激情一区二区| 色婷婷精品二区久久蜜臀av| 日韩成人一级片在线观看| 亚洲精品啪啪一区二区| 精品少妇人妻久久av免费| 91九色蝌蚪国产欧美亚洲| 成人午夜欧美熟妇小视频| 国产午夜福利av在线麻豆| 日本东京热二三四区不卡免费的| 亚洲精品中国一区二区久久| 欧美日韩另类综合91| 亚洲乱码精品一区二区| 中文字幕乱码一区二区欧美| 国产精品一区二区av麻豆| 97国产成人精品视频免费| 黑丝美女国产精品久久久| 日本黄色免费在线观看网站 | 国产精品一区二区激情视频| 激情亚洲不卡一区二区| 欧美日韩国产天天干| 日韩深夜成人在线视频|