使用Memory Analyzer tool(MAT)分析内存泄漏(二)_第1页
使用Memory Analyzer tool(MAT)分析内存泄漏(二)_第2页
使用Memory Analyzer tool(MAT)分析内存泄漏(二)_第3页
使用Memory Analyzer tool(MAT)分析内存泄漏(二)_第4页
使用Memory Analyzer tool(MAT)分析内存泄漏(二)_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

使用MemoryAnalyzertool(MAT)分析内存泄漏(二)前言的前言写blog就是好,在大前提下可以想说什么写什么,不像投稿那么字字斟酌。上周末回了趟成都办事,所以本文来迟了。K117从达州经由达成线往成都方向走的时候,发现铁路边有条河,尽管我现在也不知道其名字,但已被其深深的陶醉。河很宽且水流平缓,河边山丘森林密布,民房星星点点的分布在河边,河里偶尔些小船。当时我就在想,在这里生活是多么的惬意,夏天还可以下去畅游一番,闲来无事也可垂钓。唉,越来越讨厌北漂了。前言在使用MemoryAnalyzertool(MAT)分析内存泄漏(一)中,我介绍了内存泄漏的前因后果。在本文中,将介绍MAT如何根据heapdump分析泄漏根源。由于测试范例可能过于简单,很容易找出问题,但我期待借此举一反三。一开始不得不说说ClassLoader,本质上,它的工作就是把磁盘上的类文件读入内存,然后调用java.lang.ClassLoader.defineClass方法告诉系统把内存镜像处理成合法的字节码。Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。systemclassloader在没有指定装载器的情况下默认装载用户类,在SunJava1.5中既sun.misc.Launcher$AppClassLoader。更详细的内容请参看下面的资料。准备heapdump请看下面的Pilot类,没啥特殊的。/**Pilotclass@authorrosenjiang*/packageorg.rosenjiang.bo;publicclassPilot{Stringname;intage;publicPilot(Stringa,intb){name=a;age=b;}然后再看OOMHeapTest类,它是如何撑破heapdump的。OOMHeapTestclass@authorrosenjiang*/packageorg.rosenjiang.test;importjava.util.Date;importjava.util.HashMap;importjava.util.Map;importorg.rosenjiang.bo.Pilot;publicclassOOMHeapTest{publicstaticvoidmain(String[]args){oom();}privatestaticvoidoom(){MapvString,Pilot>map=newHashMapvString,Pilot>();Object]]array=newObject[1OOOOOO];for(inti=0;ivlOOOOOO;i++){Stringd=newDate().toString();Pilotp=newPilot(d,i);map.put(i+"rosenjiang",p);array[i]=p;}}}是的,上面构造了很多的Pilot类实例,向数组和map中放。由于是StrongRef,GC自然不会回收这些对象,一直放在heap中直到溢出。当然在运行前,先要在Eclipse中配置VM参数-XX:+HeapDumpOnOutOfMemoryError。好了,一会儿功夫内存溢出,控制台打出如下信息。java.Iang.OutOfMemoryError:JavaheapspaceDumpingheaptojava_pid3600.hprofHeapdumpfilecreated[78233961bytesin1.995secs]Exceptioninthread"main"java.lang.OutOfMemoryError:Javaheapspacejava_pid3600.hprof既是heapdump,可以在OOMHeapTest类所在的工程根目录下找到。MAT安装话分两头说,有了heapdump还得安装MAT。可以在/mat/downloads.php选择合适的方式安装。安装完成后切换到MemoryAnalyzer视图。在Eclipse的左上角有OpenHeapDump按钮,按照刚才说的路径找到java_pid3600.hprof文件并打开。解析hprof文件会花些时间,然后会弹出向导,直接Finish即可。稍后会看到下图所示的界面。iIII@脛b矗▼QiOverview^0defaultereportorg.eclipse,mat.api:suspects贸LqnIcSuspe£t:gLeakSuspectsSystemOverviewpLeakspOverview(a)63.4MB(b)324.8KB(a)63.4MB(b)324.8KB匚Total:63.7MB▽❽ProblemSuspect1•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••|Thethreadjava」ang.Thread@0x232d9000mainkeepslocalvariableswithtotalsize66f460jbytes・\Thememoryis-accumulatedinoneinstanceof"'java」^ng・Thre袒d"loadedby"<systemclassl«:java」日门g・Th®dDetailsqMAT工具分析了heapdump后在界面上非常直观的展示了一个饼图,该图深色区域被怀疑有内存泄漏,可以发现整个heap才64M内存,深色区域就占了99.5%。接下来是一个简短的描述,告诉我们main线程占用了大量内存,并且明确指出systemclassloader加载的"java.lang.Thread"实例有内存聚集,并建议用关键字"java.lang.Thread"进行检查。所以,MAT通过简单的两句话就说明了问题所在,就算使用者没什么处理内存问题的经验。在下面还有一个"Details"链接,在点开之前不妨考虑一个问题:为何对象实例会聚集在内存中,为何存活(而未被GC)?是的一一StrongRef,那么再走近一些吧。iIII®疸”越▼QiOverview de£auli_repor七oreclipse.ma七.api:suspects班jThememoryisaccumulatedinoneinstmncEof"javadang-Ttiread"loadedby"<systemclass11[java.Iang.ThreadpShortestPathsTotheAccumulationPoint匸Ib旦旦Name ShallowHeapRetainedHeapjava.Ian口・Thr晋@QK232d90DDmainThreadLD4石石「4石DrOl石厲AccumulatedObjectsClassNameShallowHeapRetainedHeapPer匚巳ntwj,ia^a」mri口.Thread@0)c232dgooomain10466f460f01699.5•… ia^a.util.Ha&hNap@0x232d8f204029rg51r7SS44.a•…皿侣曲口.Cb-iecfLOOOOOOl©0z22ec00004f000f0L64f000fC16s.g•… 」an口.ThreadLccal^ThresdLocalMsp@0x232deda024232a.o24136a.o0x232de=db8i…“ ora.rcsenliana.bo,.Pilot@Ox:229eOOcO16112a.o•…|_^ora.rcteniian口.bci”Filc:t區0x:229e0L9016112a.o•…Qora.rctenliana.bo,.Pilot@0x:229e02&015112a.o点击了"Details"链接之后,除了在上一页看到的描述外,还有ShortestPathsTotheAccumulationPoint和AccumulatedObjects部分,这里说明了从GCroot到聚集点的最短路径,以及完整的refereneechain。观察AccumulatedObjects部分,java.util.HashMap和java.lang.0bject[1000000]实例的retainedheap(size)最大,在上一篇文章中我们知道retainedheap代表从该类实例沿着refereneechain往下所能收集到的其他类实例的shallowheap(size)总和,所以明显类实例都聚集在HashMap和Object数组中了。这里我们发现一个有趣的现象,既Object数组的shallowheap和retainedheap竟然一样,通过

Shallowandretainedsizes一文可知,数组的shallowheap和一般对象(非数组)不同,依赖于数组的长度和里面的元素的类型,对数组求shallowheap,也就是求数组集合内所有对象的shallowheap之和。好,再来看org.rosenjiang.bo.Pilot对象实例的shallowheap为何是16,因为对象头是8字节,成员变量int是4字节、String引用是4字节,故总共16字节。pAccumulatedObjectsbyCla&&LabelNuniberOfObjertsUfedHeapSizeRetainedHeapSize0ora.roseniiana.bo.Pilot290r2354,643,76032,506r3Z0Qiava.util.HaEhMsp14029.95Lf7&3-回iava口”Ctxi已]4,000,0164,aoorai&[?]iava-lana-Strinad38ir2oaLf200时iavb」mri口..Thre=3dLaes1£Thre3dLocs1Mad2483S8-.門sun.util.calendar.GreaorianJDate15596-ijjiava.lanchEtririciBuild巳厂1L5S3-javaa&ecuritv・Auu呂£导亡口口廿口1亡口口t巳妊L£424回char'lL£4Z4足|iava.lanq.Cbiect1afi-国iava.lan口・Cbss1002Total:11entries290,283&t645p23266T459r912接着往下看,来到了AccumulatedObjectsbyClass区域,顾名思义,这里能找到被聚集的对象实例的类名。org.rosenjiang.bo.Pilot类上头条了,被实例化了290,325次,再返回去看程序,我承认是故意这么干的。还有很多有用的报告可用来协助分析问题,只是本文中的例子太简单,也用不上。以后如有用到,一定撰文详细叙述。又是permgen我们在上一篇文章中知道,permgen是个异类,里面存储了类和方法数据(与classloader有关)以及internedstrings(字符串驻留)。在heapdump中没有包含太多的permgen信息。那么我们就用这些少量的信息来解决问题吧。看下面的代码,利用internedstrings把permgen撑破了。/**OOMPermTestclass@authorrosenjiang*/packageorg.rosenjiang.test;publicclassOOMPermTest{publicstaticvoidmain(String[]args){oom();}privatestaticvoidoom(){Object]]array=newObject[1OOOOOOO];for(inti=0;ivlOOOOOOO;i++){Stringd=String.valueOf(i).intern();array[i]=d;}}}控制台打印如下的信息,然后把java_pid1824.hprof文件导入到MAT。其实在MAT里,看到的状况应该和“OutOfMemoryError:Javaheapspace”差不多(用了数组),因为heapdump并没有包含internedstrings方面的任何信息。只是在这里需要强调,使用intern()方法的时候应该多加注意。java.Iang.OutOfMemoryError:PermGenspaceDumpingheaptojava_pid1824.hprofHeapdumpfilecreated[121273334bytesin2.845secs]Exceptioninthread"main"java.lang.OutOfMemoryError:PermGenspace倒是在思考如何把classloader撑破废了些心思。经过尝试,发现使用ASM来动态生成类才能达到目的。ASM()的主要作用是处理已编译类(compiledclass),能对已编译类进行生成、转换、分析(功能之一是实现动态代理),而且它运行起来足够的快和小巧,文档也全面,实属居家必备之良品。ASM提供了coreAPI和treeAPI,前者是基于事件的方式,后者是基于对象的方式,类似于XML的SAX、DOM解析,但是使用treeAPI性能会有损失。既然下面要用到ASM,这里不得不啰嗦下已编译类的结构,包括:1、 修饰符(例如public、private)、类名、父类名、接口和annotation部分。2、 类成员变量声明,包括每个成员的修饰符、名字、类型和annotation。3、 方法和构造函数描述,包括修饰符、名字、返回和传入参数类型,以及annotation。当然还包括这些方法或构造函数的具体Java字节码。4、 常量池(constantpool)部分,constantpool是一个包含类中出现的数字、字符串、类型常量的数组。

Modifiers,Eiame.superclas^interfacesC^nst^ntpool:numeric,stringimdtyp&constantsSonr-celiterLftiiic(optional)EnclosingclassreferenceAnnatation*Attribute*Innerclass*\anicField^Modifiers,name,typeAnnotation*Attribute*Method^Modifiers,najne,returnandparametertypesAnnotatLan^Attribute*Cornpiledcode已编译类和原来的类源码区别在于,已编译类只包含类本身,内部类不会在已编译类中出现,而是生成另外一个已编译类文件;其二,已编译类中没有注释;其三,已编译类没有package和import部分。这里还得说说已编译类对Java类型的描述,对于原始类型由单个大写字母表示,Z代表boolean、C代表char、B代表byte、S代表short、代表int、F代表float、代表Iong、D代表double;而对类类型的描述使用内部名(internalname)外加前缀L和后面的分号共同表示来表示,所谓内部名就是带全包路径的表示法,例如String的内部名是java/lang/String;对于数组类型,使用单方括号加上数据元素类型的方式描述。最后对于方法的描述,用圆括号来表示,如果返回是void用V表示,具体参考下图。JavatypeTypedescriptorboolean.ZcharC "byteBshortSInt :I ;floatF 'JdouMoDObjectLjava./lang/Object;int[]口objecton[[LJa.va/1ang/Object;MethoddeclarationinsourcefileMethoddescripto】voidmtint1ffloatf)CIF)VlntmftJbjecto)CLjava/1aiig/ObJect;)Ililt口a(int1Strings)(ILjava/laag/Strlug;〉[IObjectmClnt□i)([IiLjava/laAg/OtJ&ct;下面的代码中会使用ASMcoreAPI,注意接口ClassVisitor是核心,Fieldvisitor、Methodvisitor都是辅助接口。ClassVisitor应该按照这样的方式来调用:visitvisitsource?visitOuterClass?(visitAnnotation|visitAttribute)*(visitInnerClass|visitField|visitMethod)*visitEnd。就是说visit方法必须首先调用,再调用最多一次的visitSource,再调用最多一次的visitOuterClass方法,接下来再多次调用visitAnnotation和visitAttribute方法,最后是多次调用visitInnerClass、visitField和visitMethod方法。调用完后再调用visitEnd方法作为结尾。注意ClassWriter类,该类实现了ClassVisitor接口,通过toByteArray方法可以把已编译类直接构建成二进制形式。由于我们要动态生成子类,所以这里只对ClassWriter感兴趣。首先是抽象类原型:/**@authorrosenjiangMyAbsClassclass*/packageorg.rosenjiang.test;publicabstractclassMyAbsClass{intLESS=-1;intEQUAL=0;intGREATER=1;abstractintabsTo(Objecto);}其次是自定义类加载器,实在没法,ClassLoader的defineClass方法都是protected的,要加载字节数组形式(因为toByteArray了)的类只有继承一下自己再实现。/**@authorrosenjiangMyClassLoaderclass*/packageorg.rosenjiang.test;publicclassMyClassLoaderextendsClassLoader{publicClassdefineClass(Stringname,byte[]b){returndefineClass(name,b,0,b.length);}}最后是测试类。/***@authorrosenjiang*OOMPermTestclass*/packageorg.rosenjiang.test;importjava.util.ArrayList;importjava.util.List;importorg.objectweb.asm.ClassWriter;importorg.objectweb.asm.Opcodes;publicclassOOMPermTest{publicstaticvoidmain(String[]args){OOMPermTesto=newOOMPermTest。;o.oom();}privatevoidoom(){try{ClassWritercw=newClassWriter(O);cw.visit(Opcodes.V1_5,Opcodes.ACC_PUBLIC+Opcodes.ACC_ABSTRACT,"org/rosenjiang/test/MyAbsClass",null,"java/lang/Object",newString[]{});cw.visitField(Opcodes.ACC_PUB口C+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC,"LESS","I",null,newInteger(-1)).visitEnd();cw.visitField(Opcodes.ACC_PUB口C+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC,"EQUAL","I",null,newInteger(0)).visitEnd();cw.visitField(Opcodes.ACC_PUB口C+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC,"GREATER","I",null,newInteger(1)).visitEnd();cw.visitMethod(Opcodes.ACC_PUBLIC+Opcodes.ACC_ABSTRACT,"absTo","(Ljava/lang/Object;)I",null,null).visitEnd();cw.visitEnd();byte[]b=cw.toByteArray();ListvClassLoader>classLoaders=newArrayListvClassLoader>();while(true){MyClassLoaderclassLoader=newMyClassLoader();classLoader.defineClass("org.rosenjiang.test.MyAbsClass",b);classLoaders.add(classLoader);}}catch(Exceptione){

e.printStackTrace();}}}不一会儿,控制台就报错了。java.Iang.OutOfMemoryError:PermGenspaceDumpingheaptojava_pid3023.hprofHeapdumpfilecreated[92593641bytesin2.405secs]Exceptioninthread"main"java.lang.OutOfMemoryError:PermGenspace打开java_pid3023.hprof文件,注意看下图的Classes:88.1k和ClassLoader:87.7k部分,从这点可看出classloader加载了大量的类。7)Filot.java[0]□OMFermTest.java-0web.Mini .技內EMyAbaCLaas..javaIiiii务袖疸▼匾三)qiOvervihj'v>3 >:irg.&clipEH.n:±t.:kpi:EUEp&cts▼Oe-ta.ils■■・■■■■■■■■■■■■■■■・・・・■■■Size:63-5IBCLisses:8S.IkUbjects:1_8bClassLuailer:8T.7kl「nr已:ajuh"1e[Ibi已亡七wKiw七oetsjti更进一步分析,点击上图中红框线圈起来的按钮,选择JavaBasicsClassLoaderExplorer功能。打开后能看到下图所示的界面,第一列是classloader名字;第二列是classloader已定义类(definedclasses)的个数,这里要说一下已定义类和已加载类(loadedclasses)了,当需要加载类的时候,相应的classloader会首先把请求委派给父classloader,只有当父classloader加载失败后,该classloader才会自己定义并加载类,这就是Java自己的''双亲委派加载链”结构;第三列是classloader所加载的类的实例数目。j.OverviC1;15SNaiTlEdet4.ult_rtjporiorg.eclipse.m:it.api:EUEjiecte0clazsioaierskplorerquery2-iDefinedCla.<SegeK>O\EyEtHmclasslcaler>+ijgsuit,mimisc.LauncherjAppC1=±ssLoader@0x22e£t=EiO8org.rosenjihl耳.test.FflyClaseLoader@0x229&U14Uijjp*jrenteutl.miec.L:=nmcherfA-ppClassLo^.iler@0x22e±e608Oof©TDMEnjiaiig,t£2t,IfljAbsUldEZ2Total:2entries田田0田E0田田十org.rosenjiang.test.MyClassLoader@0x229&0428org.rosenji:±TLg.test.Mj^laEsLoader@OK229tsOTOEiorg.ros^nj ttjEt.Mv€l&EELo:idHf@OKZSg&O'jtjSorg.rozenj1==LTLg.test.My€las^Loader@OK22Qt:0cc3org.rosenji==LTLg.test.MyClassLiiiader@Ox2L!9eO£:=itJorg.rosen]i==LTLg.test.MyClaseLoader@0x229&12SS回回回aaaEL囤回回回&000S田田田0000org・iro旨哙口j让辽唸,昌t・MyCJ.辽ssL口包1已匕@口乳22日

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论