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_loaderclassesbytesparent_loaderalive?type

13017691864 null live

0x000000008524702011888 null deadsun/reflect/DelegatingClassLoader@0x0000000081e4fc00

0x000000008527f2b01744125197600x0000000085031cb0livecom/spiro/test/mr/MapReduceClassLoader@0x0000000082096e50

0x0000000085018b981757125844160x0000000085031cb0livecom/spiro/test/mr/MapReduceClassLoader@0x0000000082096e50

0x0000000085128c80000x0000000085031cb0livejava/util/ResourceBundle$RBClassLoader@0x00000000820f5030

0x0000000085021cc013032 null deadsun/reflect/DelegatingClassLoader@0x0000000081e4fc00

0x00000000852a6f5013056 null deadsun/reflect/DelegatingClassLoader@0x0000000081e4fc00

0x0000000085031cb0838735440x0000000085031d00livesun/misc/Launcher$AppClassLoader@0x0000000082013318

0x0000000085021c8011888 null deadsun/reflect/DelegatingClassLoader@0x0000000081e4fc00

0x00000000852a6f9013056 null deadsun/reflect/DelegatingClassLoader@0x0000000081e4fc00

0x0000000085021c40130560x0000000085018b98deadsun/reflect/DelegatingClassLoader@0x0000000081e4fc00

0x00000000852a6fd0130560x00000000852a67e0deadsun/reflect/DelegatingClassLoader@0x0000000081e4fc00

0x0000000085031d0000 null livesun/misc/Launcher$ExtClassLoader@0x0000000081fb5c08

0x0000000085021c0013032 null deadsun/reflect/DelegatingClassLoader@0x0000000081e4fc00

0x00000000852a6e48130560x000000008527f2b0deadsun/reflect/DelegatingClassLoader@0x0000000081e4fc00

0x00000000852a67e01744125197600x0000000085031cb0livecom/spiro/test/mr/MapReduceClassLoader@0x0000000082096e50

total = 16663846214464 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工具来分析

9c0420bce6d3df106b7920c1b3105105.png

a2fcfa71bd486d477dd572763d447483.png

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

6656861b0bcd06641a85a6f21dd01e38.png

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

fffe5ce3806ffad7bb6dfefda2789523.png

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

1e2f3cf26d48321a9e2ffcae3ba314a2.png

edacfae888edc47297386cf07f2cd9c7.png

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

至此问题分析结束。

总结

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值