专栏首页IT杂记通过Java程序提交通用Mapreduce无法回收类的问题

通过Java程序提交通用Mapreduce无法回收类的问题

问题描述

上次发布的博客 通过Java程序提交通用Mapreduce,在实施过程中发现,每次提交一次Mapreduce任务,JVM无法回收过程中产生的MapReduceClassLoader对象以及其生成的类。

通过定制如下代码来实现多次任务提交测试:

public class JobSubmitTest {

    public static void submit(String classPath, String mainClassName) {
        ClassLoader originCL = Thread.currentThread().getContextClassLoader();
        try {
            MapReduceClassLoader cl = new MapReduceClassLoader();
            cl.addClassPath(classPath);

            System.out.println("URLS:" + Arrays.toString(cl.getURLs()));

            Thread.currentThread().setContextClassLoader(cl);

            Class mainClass = cl.loadClass(mainClassName);
            System.out.println(mainClass.getClassLoader());
            Method mainMethod = mainClass.getMethod("main", new Class[] { String[].class });
            mainMethod.invoke(null, new Object[] {new String[0]});

            Class jobClass = cl.loadClass("org.apache.hadoop.mapreduce.Job");
            System.out.println(jobClass.getClassLoader());
            Field field = jobClass.getField(JobAdapter.JOB_FIELD_NAME);
            System.out.println(field.get(null));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            Thread.currentThread().setContextClassLoader(originCL);
        }
    }

    public static void main(String[] args) {

        String classPath = args[0];
        String mainClassName = args[1];

        Scanner scanner = new Scanner(System.in);
        String cmd = null;
        int i = 0;
        while (true) {
            cmd = scanner.next();
            if ("exit".equalsIgnoreCase(cmd)) {
                break;
            }
            submit(classPath, mainClassName);
            i++;
            System.out.println("submit index = " + i);
        }
    }
}

执行命令: java -XX:PermSize=50M -XX:MaxPermSize=50M -Dhadoop.home.dir=$HADOOP_HOME -Djava.library.path=$HADOOP_HOME/lib/native \ -classpath $CLASSPATH JobSubmitTest $MR_CLASSPATH $MR_MAIN_CLASS

执行命令后,3次输入“1” + enter,实现3次mapreduce的提交,并且都创建了独立的类加载器来加载hadoop相关的类。

通过查看永久代的使用情况变化:

$ jstat -gcutil 21225 1000 1000
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00   6.05   0.00   6.63      0    0.000     0    0.000    0.000
  0.00   0.00  26.15   0.00   8.07      0    0.000     0    0.000    0.000
  0.00   0.00  76.55   0.00  16.33      0    0.000     0    0.000    0.000
  0.00  78.52  19.82   0.10  28.19      3    0.023     0    0.000    0.023
 97.58   0.00  30.21   0.11  36.39      4    0.033     0    0.000    0.033
 97.58   0.00  34.18   0.11  36.46      4    0.033     0    0.000    0.033
  0.00  99.95  96.01   5.21  52.10      6    0.050     0    0.000    0.050
 95.45   0.00  25.96   5.22  57.08      6    0.065     0    0.000    0.065
 95.45   0.00  69.92   5.22  65.57      6    0.065     0    0.000    0.065
  0.00  99.93  37.95  10.91  77.75      7    0.098     0    0.000    0.098
  0.00  99.93  37.95  10.91  77.75      7    0.098     0    0.000    0.098
  0.00  99.93  37.95  10.91  77.75      7    0.098     0    0.000    0.098
  0.00  99.93  37.95  10.91  77.75      7    0.098     0    0.000    0.098

其中P列表示永久代的使用比例;

执行GC看永久代是否会变小: 执行jcmd $PID GC.run

$ jstat -gcutil 21225 1000 1000
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT   
  0.00  99.93  41.48  10.91  77.75      7    0.098     0    0.000    0.098
  0.00  99.93  41.48  10.91  77.75      7    0.098     0    0.000    0.098
  0.00   0.00   0.00  10.62  77.68      8    0.116     1    0.209    0.325
  0.00   0.00   0.00  10.62  77.68      8    0.116     1    0.209    0.325

可以看到永久代几乎没有发生任何变化,永久代未被回收。

$ jmap -permstat 21225
Attaching to process ID 21225, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.65-b04
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness......................................................done.
class_loader	classes	bytes	parent_loader	alive?	type

<bootstrap>	1301	7691864	  null  	live	<internal>
0x0000000085247020	1	1888	  null  	dead	sun/reflect/DelegatingClassLoader@0x0000000081e4fc00
0x000000008527f2b0	1744	12519760	0x0000000085031cb0	live	com/spiro/test/mr/MapReduceClassLoader@0x0000000082096e50
0x0000000085018b98	1757	12584416	0x0000000085031cb0	live	com/spiro/test/mr/MapReduceClassLoader@0x0000000082096e50
0x0000000085128c80	0	0	0x0000000085031cb0	live	java/util/ResourceBundle$RBClassLoader@0x00000000820f5030
0x0000000085021cc0	1	3032	  null  	dead	sun/reflect/DelegatingClassLoader@0x0000000081e4fc00
0x00000000852a6f50	1	3056	  null  	dead	sun/reflect/DelegatingClassLoader@0x0000000081e4fc00
0x0000000085031cb0	83	873544	0x0000000085031d00	live	sun/misc/Launcher$AppClassLoader@0x0000000082013318
0x0000000085021c80	1	1888	  null  	dead	sun/reflect/DelegatingClassLoader@0x0000000081e4fc00
0x00000000852a6f90	1	3056	  null  	dead	sun/reflect/DelegatingClassLoader@0x0000000081e4fc00
0x0000000085021c40	1	3056	0x0000000085018b98	dead	sun/reflect/DelegatingClassLoader@0x0000000081e4fc00
0x00000000852a6fd0	1	3056	0x00000000852a67e0	dead	sun/reflect/DelegatingClassLoader@0x0000000081e4fc00
0x0000000085031d00	0	0	  null  	live	sun/misc/Launcher$ExtClassLoader@0x0000000081fb5c08
0x0000000085021c00	1	3032	  null  	dead	sun/reflect/DelegatingClassLoader@0x0000000081e4fc00
0x00000000852a6e48	1	3056	0x000000008527f2b0	dead	sun/reflect/DelegatingClassLoader@0x0000000081e4fc00
0x00000000852a67e0	1744	12519760	0x0000000085031cb0	live	com/spiro/test/mr/MapReduceClassLoader@0x0000000082096e50

total = 16	6638	46214464	    N/A    	alive=7, dead=9	    N/A  
$ jcmd 21225 GC.class_histogram | grep MapReduceClassLoader
 264:             3            240  com.spiro.test.mr.MapReduceClassLoader
num     #instances         #bytes  class name
$ jcmd 21225 GC.class_histogram | grep org.apache.hadoop.mapreduce.Job
 772:             2             48  org.apache.hadoop.mapreduce.Job$JobState
 785:             1             48  org.apache.hadoop.mapreduce.Job
 813:             1             48  org.apache.hadoop.mapreduce.Job
 878:             2             48  org.apache.hadoop.mapreduce.Job$JobState
 883:             1             48  org.apache.hadoop.mapreduce.Job
 961:             2             48  org.apache.hadoop.mapreduce.Job$JobState
1357:             1             24  [Lorg.apache.hadoop.mapreduce.Job$JobState;
1511:             1             24  [Lorg.apache.hadoop.mapreduce.Job$JobState;
1601:             1             24  [Lorg.apache.hadoop.mapreduce.Job$JobState;

可以看到MapReduceClassLoader类型的类加载器有3个,且占用了大部分的容量。org.apache.hadoop.mapreduce.Job对象出现3个,虽然名称相同但是是不同的类,分别由3个类加载器加载。

分析原因

通过代码来看,MapReduceClassLoader cl = new MapReduceClassLoader();是定义在方法体内,当方法结束时,栈帧中的局部变量表也就消失了,MapReduceClassLoader对象应该就会被GC,并且由其加载的所有类也都应该被回收。但是为什么没有回收呢,根据Java判定对象是否存活的根搜索算法(GC Roots Tracing),肯定有如下GC roots任然持有MapReduceClassLoader对象:

  • 虚拟机栈(栈帧中的本地变量表)中的引用对象
  • 方法区中的静态属性;
  • 方法区中的常量引用;
  • 本地方法栈中JNI的引用对象;

下面通过对java进行的dump文件进行分析。 执行导出dump文件jmap -dump:live,format=b,file=heap.bin $PID

通过jvisualvm工具来分析

在Classes tab页中找到MapReduceClassLoader类,右击鼠标,选择“show in instances view”,

在下面的Refernces中的“this”上右击选择“show nearest gc root”,

可以看到有一个名为“Thread-2”的线程对象的contextClassLoader属性引用指向了MapReduceClassLoader对象。导致MapReduceClassLoader对象无法被回收。

在Summary tab页中可看到线程信息,其中一个名为“Thread-2”的线程调用栈在org.apache.hadoop.net.unix.DomainSocketWatcher类中,通过源码分析,该线程为在执行提交MR任务过程中hadoop框架启动的子线程,创建子线程时会使用父线程的contextClassLoad作为其contextClassLoad。

至此问题分析结束。

总结

问题原因是由于在提交MR任务前执行了Thread.currentThread().setContextClassLoader(cl);操作,导致提交过程中hadoop开启的一个常驻子线程使用了其父线程的contextClassLoad最为器其上下文线程,也就是MapReduceClassLoader。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • zip文件操作导致JVM crash

    1. 概况     程序运行操作系统: CentOS6.5 64bit     JDK版本:7 2. 测试 2.1 准备测试程序 测试程序很简单,就一个类一个m...

    囚兔
  • Java Socket Timeout总结

    1. Socket timeout     Java socket有如下两种timeout: 建立连接timeout,暂时就叫 connect timeout;...

    囚兔
  • eclipse启动报错:JVM terminated. Exit code=13解决(open...

    之前的JDK使用的是opensuse自带的openJDK1.7; 之后换成oracle官网下的jdk-6u38-linux-i586-rpm.bin, 导致e...

    囚兔
  • 印度担忧Aadhar计划 Mongo DB跟着引争议

    1.受棱镜门影响,各界对Aadhar的质疑从是否将威胁人民隐私与安全,转而聚焦在 Aadhar 搜集、储存以及处理资料的方法,以及美国新创公司 MongoDB...

    静一
  • 去中心化,一场反哺式互联网产业再进化

    孟永辉
  • 【微服务】143:商品分类业务的实现

    Category.vue是分类业务对应的具体vue,v-card:卡片,是vuetify框架中提供的组件。

    刘小爱
  • 学习使我快乐,手把手教你用 Spring Cloud 实现简单的微服务架构

    最近本人刚经历了工作调动,正处于持续学习的状态。在这过程中微服务这个关键词多次出现,而刚好我以前写的应用都是单体架构,未有过微服务架构开发经验,因此就微服务我展...

    DevOps时代
  • 【目标分割】开源 | 百度--模型学习前景特征与背景特征,对前景的分割更为精准,性能SOTA!

    论文地址: https://arxiv.org/pdf/2003.08333.pdf

    CNNer
  • 用机器学习来概括《哈利波特》,视频也可以有“太长不看版”

    一位叫做Sagi Shaier的程序猿,用机器学习给《哈利·波特》电影片段,做了一份太长不看的概括版。

    量子位
  • 图解告诉你怎么在VS2017下调试DLL

    战神伽罗

扫码关注云+社区

领取腾讯云代金券