前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >通过Java程序提交通用Mapreduce无法回收类的问题

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

作者头像
囚兔
发布2018-03-29 12:07:54
1.1K1
发布2018-03-29 12:07:54
举报
文章被收录于专栏:IT杂记

问题描述

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

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

代码语言:javascript
复制
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相关的类。

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

代码语言:javascript
复制
$ 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

代码语言:javascript
复制
$ 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

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

代码语言:javascript
复制
$ 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  
代码语言:javascript
复制
$ jcmd 21225 GC.class_histogram | grep MapReduceClassLoader
 264:             3            240  com.spiro.test.mr.MapReduceClassLoader
num     #instances         #bytes  class name
代码语言:javascript
复制
$ 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。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题描述
  • 分析原因
  • 总结
相关产品与服务
大数据
全栈大数据产品,面向海量数据场景,帮助您 “智理无数,心中有数”!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档