[计算机软件及应用]java内存分析.doc

上传人:sccc 文档编号:4561560 上传时间:2023-04-27 格式:DOC 页数:34 大小:302.18KB
返回 下载 相关 举报
[计算机软件及应用]java内存分析.doc_第1页
第1页 / 共34页
[计算机软件及应用]java内存分析.doc_第2页
第2页 / 共34页
[计算机软件及应用]java内存分析.doc_第3页
第3页 / 共34页
[计算机软件及应用]java内存分析.doc_第4页
第4页 / 共34页
[计算机软件及应用]java内存分析.doc_第5页
第5页 / 共34页
点击查看更多>>
资源描述

《[计算机软件及应用]java内存分析.doc》由会员分享,可在线阅读,更多相关《[计算机软件及应用]java内存分析.doc(34页珍藏版)》请在三一办公上搜索。

1、1 JVM简介 JVM是我们Javaer的最基本功底了,刚开始学Java的时候,一般都是从“Hello World”开始的,然后会写个复杂点class,然后再找一些开源框架,比如Spring,Hibernate等等,再然后就开发企业级的应用,比如网站、企业内部应用、实时交易系统等等,直到某一天突然发现做的系统咋就这么慢呢,而且时不时还来个内存溢出什么的,今天是交易系统报了StackOverflowError,明天是网站系统报了个OutOfMemoryError,这种错误又很难重现,只有分析Javacore和dump文件,运气好点还能分析出个结果,运行遭的点,就直接去庙里烧香吧!每天接客户的电话

2、都是战战兢兢的,生怕再出什么幺蛾子了。我想Java做的久一点的都有这样的经历,那这些问题的最终根结是在哪呢? JVM。JVM全称是Java Virtual Machine,Java虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用 VMWare不一样,那个虚拟的东西你是可以看到的,这个JVM你是看不到的,它存在内存中。我们知道计算机的基本构成是:运算器、控制器、存储器、输入和输出设备,那这个JVM也是有这成套的元素,运算器是当然是交给硬件CPU还处理了,只是为了适应“一次编译,随处运行”的情况,需要做一个翻译动作,于是就用了JVM自己的命令集,这与汇编的命令集有点类似,每一种汇编命令集针对

3、一个系列的CPU,比如8086系列的汇编也是可以用在8088上的,但是就不能跑在8051上,而JVM的命令集则是可以到处运行的,因为JVM做了翻译,根据不同的CPU,翻译成不同的机器语言。JVM中我们最需要深入理解的就是它的存储部分,存储?硬盘?NO,NO, JVM是一个内存中的虚拟机,那它的存储就是内存了,我们写的所有类、常量、变量、方法都在内存中,这决定着我们程序运行的是否健壮、是否高效,接下来的部分就是重点介绍之。2 JVM的组成部分 我们先把JVM这个虚拟机画出来,如下图所示:从这个图中可以看到,JVM是运行在操作系统之上的,它与硬件没有直接的交互。我们再来看下JVM有哪些组成部分,如

4、下图所示:该图参考了网上广为流传的JVM构成图,大家看这个图,整个JVM分为四部分:q Class Loader 类加载器类加载器的作用是加载类文件到内存,比如编写一个HelloWord.java程序,然后通过javac编译成class文件,那怎么才能加载到内存中被执行呢?Class Loader承担的就是这个责任,那不可能随便建立一个.class文件就能被加载的,Class Loader加载的class文件是有格式要求,在JVM Specification中式这样定义Class文件的结构: ClassFile u4 magic; u2 minor_version; u2 major_vers

5、ion; u2 constant_pool_count; cp_info constant_poolconstant_pool_count-1; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfacesinterfaces_count; u2 fields_count; field_info fieldsfields_count; u2 methods_count; method_info methodsmethods_count; u2 attributes_count; attrib

6、ute_info attributesattributes_count; 需要详细了解的话,可以仔细阅读JVM Specification的第四章“The class File Format”,这里不再详细说明。友情提示:Class Loader只管加载,只要符合文件结构就加载,至于说能不能运行,则不是它负责的,那是由Execution Engine负责的。q Execution Engine 执行引擎执行引擎也叫做解释器(Interpreter),负责解释命令,提交操作系统执行。q Native Interface本地接口本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C

7、+程序,Java诞生的时候是C/C+横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C+程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机,或者Java系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍。q Runtime data

8、 area运行数据区运行数据区是整个JVM的重点。我们所有写的程序都被加载到这里,之后才开始运行,Java生态系统如此的繁荣,得益于该区域的优良自治,下一章节详细介绍之。整个JVM框架由加载器加载文件,然后执行器在内存中处理数据,需要与异构系统交互是可以通过本地接口进行,瞧,一个完整的系统诞生了!2 JVM的内存管理 所有的数据和程序都是在运行数据区存放,它包括以下几部分:q Stack 栈栈也叫栈内存,是Java程序的运行区,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就Over。问题出来了:栈中存的是那些数据呢

9、?又什么是格式呢?栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,执行完毕后,先弹出F2栈帧,再弹出F1栈帧,遵循“先进后出”原则。那栈帧中到底存在着什么数据呢?栈帧中主要保存3类数据:本地变量(Local Variables),包括输入参数和输出参数以及方法内的变量;栈操作(Operand Stack),记录出栈、入栈的操作;栈帧数据(Frame Data),包括类文件、方法等等。光说比较枯

10、燥,我们画个图来理解一下Java栈,如下图所示:图示在一个栈中有两个栈帧,栈帧2是最先被调用的方法,先入栈,然后方法2又调用了方法1,栈帧1处于栈顶的位置,栈帧2处于栈底,执行完毕后,依次弹出栈帧1和栈帧2,线程结束,栈释放。q Heap 堆内存一个JVM实例只存在一个堆类存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分为三部分:Permanent Space 永久存储区永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是

11、不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存。Young Generation Space 新生区新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:伊甸区(Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new出来的。幸存区有两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动

12、到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1区也满了呢?再移动到养老区。Tenure generation space养老区养老区用于保存从新生区筛选出来的JAVA对象,一般池对象都在这个区域活跃。 三个区的示意图如下:q Method Area 方法区方法区是被所有线程共享,该区域保存所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。q PC Register 程序计数器每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码,由执行引擎读取下一条指令。q Native Method Stack 本地方法栈3 JVM相关问题 问:

13、堆和栈有什么区别答:堆是存放对象的,但是对象内的临时变量是存在栈内存中,如例子中的methodVar是在运行期存放到栈中的。栈是跟随线程的,有线程就有栈,堆是跟随JVM的,有JVM就有堆内存。问:堆内存中到底存在着什么东西?答:对象,包括对象变量以及对象方法。问:类变量和实例变量有什么区别?答:静态变量是类变量,非静态变量是实例变量,直白的说,有static修饰的变量是静态变量,没有static修饰的变量是实例变量。静态变量存在方法区中,实例变量存在堆内存中。问:我听说类变量是在JVM启动时就初始化好的,和你这说的不同呀!答:那你是道听途说,信我的,没错。问:Java的方法(函数)到底是传值还

14、是传址?答:都不是,是以传值的方式传递地址,具体的说原生数据类型传递的值,引用类型传递的地址。对于原始数据类型,JVM的处理方法是从Method Area或Heap中拷贝到Stack,然后运行frame中的方法,运行完毕后再把变量指拷贝回去。问:为什么会产生OutOfMemory产生?答:一句话:Heap内存中没有足够的可用内存了。这句话要好好理解,不是说Heap没有内存了,是说新申请内存的对象大于Heap空闲内存,比如现在Heap还空闲1M,但是新申请的内存需要1.1M,于是就会报OutOfMemory了,可能以后的对象申请的内存都只要0.9M,于是就只出现一次OutOfMemory,GC也

15、正常了,看起来像偶发事件,就是这么回事。 但如果此时GC没有回收就会产生挂起情况,系统不响应了。问:我产生的对象不多呀,为什么还会产生OutOfMemory?答:你继承层次忒多了,Heap中 产生的对象是先产生 父类,然后才产生子类,明白不?问:OutOfMemory错误分几种?答:分两种,分别是“OutOfMemoryError:java heap size”和”OutOfMemoryError: PermGen space”,两种都是内存溢出,heap size是说申请不到新的内存了,这个很常见,检查应用或调整堆内存大小。“PermGen space”是因为永久存储区满了,这个也很常见,一

16、般在热发布的环境中出现,是因为每次发布应用系统都不重启,久而久之永久存储区中的死对象太多导致新对象无法申请内存,一般重新启动一下即可。问:为什么会产生StackOverflowError?答:因为一个线程把Stack内存全部耗尽了,一般是递归函数造成的。问:一个机器上可以看多个JVM吗?JVM之间可以互访吗?答:可以多个JVM,只要机器承受得了。JVM之间是不可以互访,你不能在A-JVM中访问B-JVM的Heap内存,这是不可能的。在以前老版本的JVM中,会出现A-JVM Crack后影响到B-JVM,现在版本非常少见。问:为什么Java要采用垃圾回收机制,而不采用C/C+的显式内存管理?答:

17、为了简单,内存管理不是每个程序员都能折腾好的。问:为什么你没有详细介绍垃圾回收机制?答:垃圾回收机制每个JVM都不同,JVM Specification只是定义了要自动释放内存,也就是说它只定义了垃圾回收的抽象方法,具体怎么实现各个厂商都不同,算法各异,这东西实在没必要深入。问:JVM中到底哪些区域是共享的?哪些是私有的?答:Heap和Method Area是共享的,其他都是私有的,问:什么是JIT,你怎么没说?答:JIT是指Just In Time,有的文档把JIT作为JVM的一个部件来介绍,有的是作为执行引擎的一部分来介绍,这都能理解。Java刚诞生的时候是一个解释性语言,别嘘,即使编译成

18、了字节码(byte code)也是针对JVM的,它需要再次翻译成原生代码(native code)才能被机器执行,于是效率的担忧就提出来了。Sun为了解决该问题提出了一套新的机制,好,你想编译成原生代码,没问题,我在JVM上提供一个工具,把字节码编译成原生码,下次你来访问的时候直接访问原生码就成了,于是JIT就诞生了,就这么回事。问:JVM还有哪些部分是你没有提到的?答:JVM是一个异常复杂的东西,写一本砖头书都不为过,还有几个要说明的:常量池(constant pool):按照顺序存放程序中的常量,并且进行索引编号的区域。比如int i =100,这个100就放在常量池中。安全管理器(Sec

19、urity Manager):提供Java运行期的安全控制,防止恶意攻击,比如指定读取文件,写入文件权限,网络访问,创建进程等等,Class Loader在Security Manager认证通过后才能加载class文件的。方法索引表(Methods table),记录的是每个method的地址信息,Stack和Heap中的地址指针其实是指向Methods table地址。 问:为什么不建议在程序中显式的生命System.gc()?答:因为显式声明是做堆内存全扫描,也就是Full GC,是需要停止所有的活动的(Stop The World Collection),你的应用能承受这个吗?问:JV

20、M有哪些调整参数?答:非常多,自己去找,堆内存、栈内存的大小都可以定义,甚至是堆内存的三个部分、新生代的各个比例都能调整。Java与C+之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。概述:对于从事C、C+程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又是执行最基础工作的劳动人民拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任。对于Java程序员来说,不需要在为每一个new操作去写配对的delete/free,不容易出现内容泄漏和内存溢出错误,看起来由JVM管理内存一切都很美好。不过,也正是因为Java程序员把

21、内存控制的权力交给了JVM,一旦出现泄漏和溢出,如果不了解JVM是怎样使用内存的,那排查错误将会是一件非常困难的事情。VM运行时数据区域 JVM执行Java程序的过程中,会使用到各种数据区域,这些区域有各自的用途、创建和销毁时间。根据Java虚拟机规范(第二版)(下文称VM Spec)的规定,JVM包括下列几个运行时数据区域:1.程序计数器(Program Counter Register):每一个Java线程都有一个程序计数器来用于保存程序执行到当前方法的哪一个指令,对于非Native方法,这个区域记录的是正在执行的VM原语的地址,如果正在执行的是Natvie方法,这个区域则为空(undef

22、ined)。此内存区域是唯一一个在VM Spec中没有规定任何OutOfMemoryError情况的区域。2.Java虚拟机栈(Java Virtual Machine Stacks)与程序计数器一样,VM栈的生命周期也是与线程相同。VM栈描述的是Java方法调用的内存模型:每个方法被执行的时候,都会同时创建一个帧(Frame)用于存储本地变量表、操作栈、动态链接、方法出入口等信息。每一个方法的调用至完成,就意味着一个帧在VM栈中的入栈至出栈的过程。在后文中,我们将着重讨论VM栈中本地变量表部分。经常有人把Java内存简单的区分为堆内存(Heap)和栈内存(Stack),实际中的区域远比这种观

23、点复杂,这样划分只是说明与变量定义密切相关的内存区域是这两块。其中所指的“堆”后面会专门描述,而所指的“栈”就是VM栈中各个帧的本地变量表部分。本地变量表存放了编译期可知的各种标量类型(boolean、byte、char、short、int、float、long、double)、对象引用(不是对象本身,仅仅是一个引用指针)、方法返回地址等。其中long和double会占用2个本地变量空间(32bit),其余占用1个。本地变量表在进入方法时进行分配,当进入一个方法时,这个方法需要在帧中分配多大的本地变量是一件完全确定的事情,在方法运行期间不改变本地变量表的大小。在VM Spec中对这个区域规定了

24、2中异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果VM栈可以动态扩展(VM Spec中允许固定长度的VM栈),当扩展时无法申请到足够内存则抛出OutOfMemoryError异常。3.本地方法栈(Native Method Stacks)本地方法栈与VM栈所发挥作用是类似的,只不过VM栈为虚拟机运行VM原语服务,而本地方法栈是为虚拟机使用到的Native方法服务。它的实现的语言、方式与结构并没有强制规定,甚至有的虚拟机(譬如Sun Hotspot虚拟机)直接就把本地方法栈和VM栈合二为一。和VM栈一样,这个区域也会抛出StackOve

25、rflowError和OutOfMemoryError异常。4.Java堆(Java Heap)对于绝大多数应用来说,Java堆是虚拟机管理最大的一块内存。Java堆是被所有线程共享的,在虚拟机启动时创建。Java堆的唯一目的就是存放对象实例,绝大部分的对象实例都在这里分配。这一点在VM Spec中的描述是:所有的实例以及数组都在堆上分配(原文:The heap is the runtime data area from which memory for all class instances and arrays is allocated),但是在逃逸分析和标量替换优化技术出现后,VM Sp

26、ec的描述就显得并不那么准确了。Java堆内还有更细致的划分:新生代、老年代,再细致一点的:eden、from survivor、to survivor,甚至更细粒度的本地线程分配缓冲(TLAB)等,无论对Java堆如何划分,目的都是为了更好的回收内存,或者更快的分配内存,在本章中我们仅仅针对内存区域的作用进行讨论,Java堆中的上述各个区域的细节,可参见本文第二章JVM内存管理:深入垃圾收集器与内存分配策略。根据VM Spec的要求,Java堆可以处于物理上不连续的内存空间,它逻辑上是连续的即可,就像我们的磁盘空间一样。实现时可以选择实现成固定大小的,也可以是可扩展的,不过当前所有商业的虚拟

27、机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中无法分配内存,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。5.方法区(Method Area)叫“方法区”可能认识它的人还不太多,如果叫永久代(Permanent Generation)它的粉丝也许就多了。它还有个别名叫做Non-Heap(非堆),但是VM Spec上则描述方法区为堆的一个逻辑部分(原文:the method area is logically part of the heap),这个名字的问题还真容易令人产生误解,我们在这里就不纠结了。方法区中存放了每个Class的结构信息,包括常量池、

28、字段描述、方法描述等等。VM Space描述中对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存,也可以选择固定大小或者可扩展外,甚至可以选择不实现垃圾收集。相对来说,垃圾收集行为在这个区域是相对比较少发生的,但并不是某些描述那样永久代不会发生GC(至少对当前主流的商业JVM实现来说是如此),这里的GC主要是对常量池的回收和对类的卸载,虽然回收的“成绩”一般也比较差强人意,尤其是类卸载,条件相当苛刻。6.运行时常量池(Runtime Constant Pool)Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量表(constant_pool tabl

29、e),用于存放编译期已可知的常量,这部分内容将在类加载后进入方法区(永久代)存放。但是Java语言并不要求常量一定只有编译期预置入Class的常量表的内容才能进入方法区常量池,运行期间也可将新内容放入常量池(最典型的String.intern()方法)。运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法在申请到内存时会抛出OutOfMemoryError异常。7.本机直接内存(Direct Memory)直接内存并不是虚拟机运行时数据区的一部分,它根本就是本机内存而不是VM直接管理的区域。但是这部分内存也会导致OutOfMemoryError异常出现,因此我们放到这里一起描述

30、。在JDK1.4中新加入了NIO类,引入一种基于渠道与缓冲区的I/O方式,它可以通过本机Native函数库直接分配本机内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java对和本机堆中来回复制数据。显然本机直接内存的分配不会受到Java堆大小的限制,但是即然是内存那肯定还是要受到本机物理内存(包括SWAP区或者Windows虚拟内存)的限制的,一般服务器管理员配置JVM参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统

31、级的限制),而导致动态扩展时出现OutOfMemoryError异常。实战OutOfMemoryError 上述区域中,除了程序计数器,其他在VM Spec中都描述了产生OutOfMemoryError(下称OOM)的情形,那我们就实战模拟一下,通过几段简单的代码,令对应的区域产生OOM异常以便加深认识,同时初步介绍一些与内存相关的虚拟机参数。下文的代码都是基于Sun Hotspot虚拟机1.6版的实现,对于不同公司的不同版本的虚拟机,参数与程序运行结果可能结果会有所差别。Java堆Java堆存放的是对象实例,因此只要不断建立对象,并且保证GC Roots到对象之间有可达路径即可产生OOM异常

32、。测试中限制Java堆大小为20M,不可扩展,通过参数-XX:+HeapDumpOnOutOfMemoryError让虚拟机在出现OOM异常的时候Dump出内存映像以便分析。(关于Dump映像文件分析方面的内容,可参见本文第三章JVM内存管理:深入JVM内存异常分析与调优。)清单1:Java堆OOM测试/* VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError* author zzm*/public class HeapOOM static class OOMObject public static void main(String a

33、rgs) List list = new ArrayList(); while (true) list.add(new OOMObject(); 运行结果:java.lang.OutOfMemoryError: Java heap spaceDumping heap to java_pid3404.hprof .Heap dump file created 22045981 bytes in 0.663 secsVM栈和本地方法栈Hotspot虚拟机并不区分VM栈和本地方法栈,因此-Xoss参数实际上是无效的,栈容量只由-Xss参数设定。关于VM栈和本地方法栈在VM Spec描述了两种异常:S

34、tackOverflowError与OutOfMemoryError,当栈空间无法继续分配分配时,到底是内存太小还是栈太大其实某种意义上是对同一件事情的两种描述而已,在笔者的实验中,对于单线程应用尝试下面3种方法均无法让虚拟机产生OOM,全部尝试结果都是获得SOF异常。1.使用-Xss参数削减栈内存容量。结果:抛出SOF异常时的堆栈深度相应缩小。2.定义大量的本地变量,增大此方法对应帧的长度。结果:抛出SOF异常时的堆栈深度相应缩小。3.创建几个定义很多本地变量的复杂对象,打开逃逸分析和标量替换选项,使得JIT编译器允许对象拆分后在栈中分配。结果:实际效果同第二点。清单2:VM栈和本地方法栈O

35、OM测试(仅作为第1点测试程序)/* VM Args:-Xss128k* author zzm*/public class JavaVMStackSOF private int stackLength = 1; public void stackLeak() stackLength+; stackLeak(); public static void main(String args) throws Throwable JavaVMStackSOF oom = new JavaVMStackSOF(); try oom.stackLeak(); catch (Throwable e) Syste

36、m.out.println(stack length: + oom.stackLength); throw e; 运行结果:stack length:2402Exception in thread main java.lang.StackOverflowError at org.fenixsoft.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:20) at org.fenixsoft.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:21) at org.fenixsoft.oom.JavaVM

37、StackSOF.stackLeak(JavaVMStackSOF.java:21)如果在多线程环境下,不断建立线程倒是可以产生OOM异常,但是基本上这个异常和VM栈空间够不够关系没有直接关系,甚至是给每个线程的VM栈分配的内存越多反而越容易产生这个OOM异常。原因其实很好理解,操作系统分配给每个进程的内存是有限制的,譬如32位Windows限制为2G,Java堆和方法区的大小JVM有参数可以限制最大值,那剩余的内存为2G(操作系统限制)-Xmx(最大堆)-MaxPermSize(最大方法区),程序计数器消耗内存很小,可以忽略掉,那虚拟机进程本身耗费的内存不计算的话,剩下的内存就供每一个线程的

38、VM栈和本地方法栈瓜分了,那自然每个线程中VM栈分配内存越多,就越容易把剩下的内存耗尽。清单3:创建线程导致OOM异常/* VM Args:-Xss2M (这时候不妨设大些)* author zzm*/public class JavaVMStackOOM private void dontStop() while (true) public void stackLeakByThread() while (true) Thread thread = new Thread(new Runnable() Override public void run() dontStop(); ); threa

39、d.start(); public static void main(String args) throws Throwable JavaVMStackOOM oom = new JavaVMStackOOM(); oom.stackLeakByThread(); 特别提示一下,如果读者要运行上面这段代码,记得要存盘当前工作,上述代码执行时有很大令操作系统卡死的风险。运行结果:Exception in thread main java.lang.OutOfMemoryError: unable to create new native thread 运行时常量池要在常量池里添加内容,最简单的就

40、是使用String.intern()这个Native方法。由于常量池分配在方法区内,我们只需要通过-XX:PermSize和-XX:MaxPermSize限制方法区大小即可限制常量池容量。实现代码如下:清单4:运行时常量池导致的OOM异常/* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M* author zzm*/public class RuntimeConstantPoolOOM public static void main(String args) / 使用List保持着常量池引用,压制Full GC回收常量池行为 List list = n

41、ew ArrayList(); / 10M的PermSize在integer范围内足够产生OOM了 int i = 0; while (true) list.add(String.valueOf(i+).intern(); 运行结果:Exception in thread main java.lang.OutOfMemoryError: PermGen space at java.lang.String.intern(Native Method) at org.fenixsoft.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.ja

42、va:18)方法区上文讲过,方法区用于存放Class相关信息,所以这个区域的测试我们借助CGLib直接操作字节码动态生成大量的Class,值得注意的是,这里我们这个例子中模拟的场景其实经常会在实际应用中出现:当前很多主流框架,如Spring、Hibernate对类进行增强时,都会使用到CGLib这类字节码技术,当增强的类越多,就需要越大的方法区用于保证动态生成的Class可以加载入内存。清单5:借助CGLib使得方法区出现OOM异常/* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M* author zzm*/public class JavaMet

43、hodAreaOOM public static void main(String args) while (true) Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() public Object intercept(Object obj, Method method, Object args, MethodProxy proxy) throws Throwable return proxy.invokeSuper(obj, args); ); enhancer.create(); static class OOMObject

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 教育教学 > 成人教育


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号