内存溢出之PermGen OOM调查
内存溢出问题

OutOfMemory定位问题的分享,垃圾回收是参考江南白衣的一篇博文,各个点都是些比较表层的sharing,还没有写完,也和广大同仁做个交流1 分析工具1)动态分析工具Jprofile下面的内存都是运行时的1)静态分析工具a: 在启动java的时候加上参数 -XX:+HeapDumpOnOutOfMemoryError,这样如果由于OOM导致JVM crash的时候可以便于我们分析,生成的heap dump文件名字的命名规范如下, java_pidxxxx.hprofb: elcipsematIBM heap ana:我们MR比较喜欢用这个IBM的工具进行OOM诊断,启动方式java -Xmx1600 -jar ha396.jar,然后选择文件打开生成的heap dump.2 Java 内存机制1对于从事C、C++程序开发的开发人员来说,担负着每一个对象生命开始到终结的维护责任。
对于Java程序员来说,不需要在为每一个new操作去写配对的delete/free,不容易出现内容泄漏和内存溢出错误。
不过,也正是因为Java程序员把内存控制的权力交给了JVM,一旦出现泄漏和溢出,如果不了解JVM是怎样使用内存的,那排查错误将会是一件非常困难的事情。
下面介绍一下java出现的OOM有关的 Exception和可能出现的方式A Exception in thread "main" ng.OutOfMemoryError: PermGen spacepublic static void main(String[] args) {//使用List保持着常量池引用,压制Full GC回收常量池行为List<String> list = new ArrayList<String>();// 10M的PermSize在integer范围内足够产生OOM了int i = 0;while (true) {list.add(String.valueOf(i++).intern());}}这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域(包括常量池: 静态变量),它和和存放Instance的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误动态生成的类,加载如Spring、Hibernate对类进行增强时,都会使用到CGLib这类字节码技术,当增强的类越多,就需要越大的方法区用于保证动态生成的Class可以加载入内存,以上是PermGen space区的常量池内存泄露导致的OOM.B ng.OutOfMemoryError: Java heap space,运行中产生的对象,被缓存的实例(Cache)对象,大的map,list引用大的对象等等,都会保存于此(Heap)public static void main(String[] args) {List<String> list = new ArrayList<String>();int i = 0;while (true) {list.add(new String(“test”));}}C Exception in thread "main" ng.StackOverflowError栈帧太多,也就是函数调用层级过多)导致。
oom 内存溢出的排查思路

oom 内存溢出的排查思路以OOM(Out of Memory)内存溢出的排查思路为标题,我们将从以下几个方面来探讨如何解决这个问题。
1. 理解OOM内存溢出的原因OOM内存溢出是指应用程序在申请内存时,无法获得足够的内存空间而导致的错误。
这可能是因为应用程序的内存使用超过了系统分配给它的内存限制,或者是由于内存泄漏等问题导致的。
理解OOM的原因对于解决问题至关重要。
2. 分析错误日志在遇到OOM内存溢出问题时,首先应该分析错误日志。
错误日志通常会提供有关错误发生的位置、异常堆栈信息以及导致问题的原因的线索。
通过仔细阅读错误日志,可以确定问题的具体来源,并为解决问题提供指导。
3. 检查代码中的内存泄漏内存泄漏是导致OOM内存溢出的常见原因之一。
在排查问题时,需要仔细检查应用程序的代码,查找可能存在的内存泄漏点。
内存泄漏通常是由于未正确释放对象或者对象的生命周期管理不当导致的。
通过分析代码,可以找到潜在的内存泄漏点,并进行修复。
4. 检查内存使用情况除了检查代码中的内存泄漏,还应该对应用程序的内存使用情况进行监控和分析。
可以通过使用内存分析工具来获取应用程序的内存快照,并查看内存中存在的对象、其大小以及引用关系等信息。
通过对内存使用情况的分析,可以找到内存占用较大的对象,进一步缩小问题的范围。
5. 调整内存配置参数在一些情况下,OOM内存溢出可能是由于应用程序的内存配置参数设置不合理导致的。
例如,堆内存大小设置过小,无法满足应用程序的需求。
在排查问题时,可以尝试调整内存配置参数,增加堆内存大小或者调整垃圾回收算法等。
通过适当调整内存配置参数,可以有效地缓解或解决OOM内存溢出问题。
6. 优化代码和算法除了修复内存泄漏和调整内存配置参数外,还可以通过优化代码和算法来降低应用程序的内存占用。
例如,可以减少对象的创建和销毁次数,尽量复用对象,避免使用过多的静态变量等。
通过优化代码和算法,可以减少内存占用,提高应用程序的性能和稳定性。
内存溢出和内存泄漏的区别,产生原因以及解决方案

内存溢出和内存泄漏的区别,产⽣原因以及解决⽅案1.1内存溢出:(Out Of Memory---OOM)系统已经不能再分配出你所需要的空间,⽐如你需要100M的空间,系统只剩90M了,这就叫内存溢出例⼦:⼀个盘⼦⽤尽各种⽅法只能装4个果⼦,你装了5个,结果掉倒地上不能吃了。
这就是溢出。
⽐⽅说栈,栈满时再做进栈必定产⽣空间溢出,叫上溢,栈空时再做退栈也产⽣空间溢出,称为下溢。
就是分配的内存不⾜以放下数据项序列,称为内存溢出。
说⽩了就是我承受不了那么多,那我就报错,1.2内存泄漏: (Memory Leak)----》强引⽤所指向的对象不会被回收,可能导致内存泄漏,虚拟机宁愿抛出OOM也不会去回收他指向的对象意思就是你⽤资源的时候为他开辟了⼀段空间,当你⽤完时忘记释放资源了,这时内存还被占⽤着,⼀次没关系,但是内存泄漏次数多了就会导致内存溢出例⼦:你向系统申请分配内存进⾏使⽤(new),可是使⽤完了以后却不归还(delete),结果你申请到的那块内存你⾃⼰也不能再访问(也许你把它的地址给弄丢了),⽽系统也不能再次将它分配给需要的程序。
就相当于你租了个带钥匙的柜⼦,你存完东西之后把柜⼦锁上之后,把钥匙丢了或者没有将钥匙还回去,那么结果就是这个柜⼦将⽆法供给任何⼈使⽤,也⽆法被垃圾回收器回收,因为找不到他的任何信息。
⼀般我们所说的内存泄漏指的是堆内存的泄露,堆内存是指程序从堆中分配的,⼤⼩随机的⽤完后必须显⽰释放的内存,C++/C中有free函数可以释放内存,java中有垃圾回收机制不⽤程序员⾃⼰⼿动调⽤释放如果这块内存不释放,就不能再⽤了,这就叫这块内存泄漏了--------------------------------------------------------------------------------------------------------------------------------------------------------------------2.以发⽣的⽅式来分类,内存泄漏可以分为4类:1. 常发性内存泄漏。
jvm内存溢出的三种情况以及解决办法

jvm内存溢出的三种情况以及解决办法1 前⾔相信有⼀定java开发经验的⼈或多或少都会遇到OutOfMemoryError的问题,这个问题曾困扰了我很长时间,随着解决各类问题经验的积累以及对问题根源的探索,终于有了⼀个⽐较深⼊的认识。
在解决java内存溢出问题之前,需要对jvm(java虚拟机)的内存管理有⼀定的认识。
jvm管理的内存⼤致包括三种不同类型的内存区域:Permanent Generation space(永久保存区域)、Heap space(堆区域)、Java Stacks(Java栈)。
其中永久保存区域主要存放Class(类)和Meta的信息,Class第⼀次被Load的时候被放⼊PermGen space区域,Class需要存储的内容主要包括⽅法和静态属性。
堆区域⽤来存放Class的实例(即对象),对象需要存储的内容主要是⾮静态属性。
每次⽤new创建⼀个对象实例后,对象实例存储在堆区域中,这部分空间也被jvm的垃圾回收机制管理。
⽽Java栈跟⼤多数编程语⾔包括汇编语⾔的栈功能相似,主要基本类型变量以及⽅法的输⼊输出参数。
Java程序的每个线程中都有⼀个独⽴的堆栈。
容易发⽣内存溢出问题的内存空间包括:Permanent Generation space和Heap space。
2 第⼀种OutOfMemoryError: PermGen space发⽣这种问题的原意是程序中使⽤了⼤量的jar或class,使java虚拟机装载类的空间不够,与Permanent Generation space有关。
解决这类问题有以下两种办法:1. 增加java虚拟机中的XX:PermSize和XX:MaxPermSize参数的⼤⼩,其中XX:PermSize是初始永久保存区域⼤⼩,XX:MaxPermSize是最⼤永久保存区域⼤⼩。
如针对tomcat6.0,在catalina.sh 或catalina.bat⽂件中⼀系列环境变量名说明结束处(⼤约在70⾏左右)增加⼀⾏:JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m"3 第⼆种OutOfMemoryError: Java heap space发⽣这种问题的原因是java虚拟机创建的对象太多,在进⾏垃圾回收之间,虚拟机分配的到堆内存空间已经⽤满了,与Heap space有关。
JVM:全面理解线上服务器内存溢出(OOM)问题处理方案

JVM:全面理解线上服务器内存溢出(OOM)问题处理方案在现代应用程序开发中,内存管理是一个非常重要的方面。
虽然现代计算机中的内存容量已经非常大,但是在高负载和大数据量的情况下,仍然可能遇到内存溢出(OOM)。
内存溢出是指程序在运行过程中使用的内存量超过了系统设置的限制,导致程序运行失败。
这对生产环境的服务器是非常严重的,因为它可能导致服务器崩溃,进而影响用户体验。
JVM是Java程序的运行时环境,一旦发生线上服务器内存溢出问题,我们需要处理这个问题的步骤如下:一、分析内存溢出错误日志JVM在发生内存溢出时会产生错误日志,这些日志信息提供了非常有用的信息,有助于分析问题的原因。
在分析日志的时候,需要关注以下几个方面:1.错误信息:内存溢出错误的类型,以及导致错误的相关代码。
2.内存使用情况:分析 JVM 中各个方面的内存使用情况,例如堆内存、非堆内存、元数据内存等。
3.内存泄漏:分析可能导致内存泄漏的代码。
二、调整 JVM 参数JVM提供了很多可供调整的参数,通过调整这些参数可以使JVM 在运行过程中使用更少的内存。
例如,调整堆大小、非堆大小、GC策略等。
在选择适当的 JVM 参数时,可以参考JVM 官方文档中提供的建议参数。
但是,需要注意的是,不要随意调整JVM 参数,否则可能会导致系统运行状况更糟糕。
三、检查代码中的内存泄漏内存泄漏是指程序中申请的内存没有被及时释放,导致内存空间被占用,进而导致内存溢出。
在 Java 中,由于 Java 自带GC,因此内存泄漏的问题相对较少,但仍然有可能发生。
在排查内存泄漏问题时,可以使用 Java 堆栈跟踪工具,例如Eclipse Memory Analyzer (MAT) 来分析堆中的对象和数据,从而快速定位内存泄漏的原因。
四、优化代码优化代码是解决内存溢出问题的最重要的一步。
通过优化代码,减少对内存的消耗,可以有效地防止内存溢出问题。
优化代码的方法有很多,例如,使用缓存、避免频繁的创建多个对象、使用数据结构等。
内存溢出的三种情况及系统配置解决方案

内存溢出的三种情况及系统配置解决方案内存溢出是指程序在运行过程中申请的内存超过了系统或者进程所能提供的上限。
导致内存溢出的原因可能是程序中存在内存泄漏、内存分配过多或者递归调用过深等。
下面将介绍三种常见的内存溢出情况及其系统配置解决方案。
1.程序内存泄漏导致内存溢出:内存泄漏指程序在运行过程中动态分配内存空间后,没有对其进行释放,导致一部分内存无法再次使用。
长时间运行的程序中,如果内存泄漏较为严重,系统可用内存会不断减少,直到最终耗尽所有内存资源。
解决方案:使用内存泄漏检测工具来检测和修复程序中的内存泄漏问题。
同时,可以考虑使用自动内存管理的编程语言,如Java和Python,在程序运行过程中自动回收未使用的内存。
2.内存分配过多导致内存溢出:解决方案:优化程序的内存使用,尽可能减小内存分配的数量和大小。
可以通过使用更高效的内存管理算法来减少内存碎片,或者使用内存池技术来提前分配一定量的内存供程序使用。
3.递归调用过深导致内存溢出:递归函数在每次调用时会将一定量的数据压入栈中,如果递归调用层数过深,栈的空间可能会超过系统的限制,从而导致内存溢出。
这种情况通常发生在没有设置递归终止条件或者递归层数过多的情况下。
解决方案:优化递归算法,设置合适的递归终止条件,避免递归调用过深。
如果无法避免使用递归算法,可以考虑使用尾递归或者迭代算法来替代递归调用,减少栈的压力。
在系统配置方面,可以采取以下措施来预防和解决内存溢出问题:1.增加系统内存容量:如果内存溢出是由于系统可用内存不足引起的,可以考虑增加系统的内存容量。
这可以通过增加物理内存条或者使用虚拟内存技术来实现。
虚拟内存技术会将部分磁盘空间用作缓存,并将一部分数据暂时存储在磁盘上,以释放内存空间。
2. 调整JVM参数:对于使用Java虚拟机(JVM)的应用程序,可以通过调整JVM的参数来控制内存的分配和管理。
例如,可以通过设置-Xmx参数来限制JVM使用的最大堆内存大小,或者通过设置-XX:MaxPermSize参数来限制JVM使用的最大持久代(PermGen)内存大小。
Java内存溢出(OOM)异常完全指南
Java内存溢出(OOM)异常完全指南本⽂分析什么情况会导致这些异常出现,提供⽰例代码的同时为您提供解决指南。
Nikita Salnikov-TarnovskiPlumbr Co-Founder and VP of Engineering本⽂内容来源于,对原⽂内容有删减和补充这也许是⽬前最为完整的Java OOM异常的解决指南。
1、ng.OutOfMemoryError:Java heap spaceJava应⽤程序在启动时会指定所需要的内存⼤⼩,它被分割成两个不同的区域:Heap space(堆空间)和Permgen(永久代):JVM内存模型⽰意图这两个区域的⼤⼩可以在JVM(Java虚拟机)启动时通过参数-Xmx和-XX:MaxPermSize设置,如果你没有显式设置,则将使⽤特定平台的默认值。
当应⽤程序试图向堆空间添加更多的数据,但堆却没有⾜够的空间来容纳这些数据时,将会触发ng.OutOfMemoryError: Java heap space异常。
需要注意的是:即使有⾜够的物理内存可⽤,只要达到堆空间设置的⼤⼩限制,此异常仍然会被触发。
原因分析触发ng.OutOfMemoryError: Java heap space最常见的原因就是应⽤程序需要的堆空间是XXL号的,但是JVM提供的却是S号。
解决⽅法也很简单,提供更⼤的堆空间即可。
除了前⾯的因素还有更复杂的成因:流量/数据量峰值:应⽤程序在设计之初均有⽤户量和数据量的限制,某⼀时刻,当⽤户数量或数据量突然达到⼀个峰值,并且这个峰值已经超过了设计之初预期的阈值,那么以前正常的功能将会停⽌,并触发ng.OutOfMemoryError: Java heap space异常。
内存泄漏:特定的编程错误会导致你的应⽤程序不停的消耗更多的内存,每次使⽤有内存泄漏风险的功能就会留下⼀些不能被回收的对象到堆空间中,随着时间的推移,泄漏的对象会消耗所有的堆空间,最终触发ng.OutOfMemoryError: Java heap space错误。
oom排查思路
oom排查思路
1.首先,确定OOM是否是真正的原因。
可以通过查看日志、监控系统等方式来确认是否存在内存溢出问题。
2. 如果确定存在OOM问题,则需要找出造成问题的根本原因。
可以通过以下途径来排查:
- 分析堆内存快照,查看哪些对象占用了较多的内存;
- 查看GC日志,分析GC过程,确定是否存在内存泄漏等问题; - 分析代码,查看是否存在内存泄漏、循环引用等问题;
- 分析系统配置,确定是否存在JVM参数设置不当等问题;
- 分析业务逻辑,确定是否存在大量数据处理、频繁创建对象等问题。
3. 根据排查结果,采取相应的解决方案:
- 对于内存占用较大的对象,可以考虑优化代码,减少对象创建,或者增加JVM堆内存大小;
- 对于内存泄漏等问题,可以通过代码优化、修改配置等方式来解决;
- 对于业务逻辑问题,可以考虑分批处理数据、优化算法等方式来减少内存占用。
4. 最后,需要进行测试验证,确保问题得到了有效解决,并且不会对系统的稳定性和性能产生负面影响。
- 1 -。
记录一次OOM的排查过程以及内存分析、解决方案
记录⼀次OOM的排查过程以及内存分析、解决⽅案 在测试环境中开启的堆⼤⼩是4g。
但是却发⽣了OOM。
发⽣OOM的场景是:上传Excel 之后进⾏数据的清洗,然后清洗完成之后会将清洗掉的、清洗后的数据再次备份到磁盘中;同时将清洗后的数据⼊关系型数据库。
(解析Excel ⽤的是POI,数据清洗⽤的是Tablesaw,且清洗的操作都是在内存中处理的) 记录下此次OOM的排查过程。
1. 前置知识 关于JVM调试的前置知识。
0. 抛出OOM的前提The parallel collector throws an OutOfMemoryError if too much time is being spent in garbage collection (GC): If more than 98% of the total time is spent in garbage collection and less than 2 1. 内存区域JVM的内存区域分为五块,随线程消亡的包括本地⽅法栈、虚拟机栈(栈)、PC(程序计数器),线程共享的区域包括:堆、⽅法区(JDK7的永久代,JDK8的MetaSpace元空间)。
-Xms2g 可以指定初始化堆的⼤⼩,-Xmx2g可以指定最⼤堆的⼤⼩。
其中堆分为新⽣代(Eden区、From Survivor区和To Survivor)、⽼年代。
新⽣代和⽼年代的⽐例默认是1:2,也就是新⽣代占堆的1/3,⽼年代占堆的2/3(–XX:NewRatio可以调节新⽣代和⽼年代⽐例)。
新⽣代Eden和两个Survivor的⽐例是8:1:1。
(–XX:SurvivorRatio可以调节E区和两个S区⽐例)。
不指定-Xms 初始化堆⼤⼩的情况下初始化是机器内存的1/64 ,最⼩是8m。
不指定-Xmx 最⼤堆的⼤⼩是1/4 机器内存。
查看默认的初始堆和最⼤堆的⼤⼩:我的机器是16G运⾏内存C:\Users\xxx>java -XX:+PrintFlagsFinal -version | findstr HeapSizeuintx ErgoHeapSizeLimit = 0 {product}uintx HeapSizePerGCThread = 87241520 {product}uintx InitialHeapSize := 264241152 {product}uintx LargePageHeapSizeThreshold = 134217728 {product}uintx MaxHeapSize := 4206886912 {product}换成M之后InitialHeapSize 是 252 m, MaxHeapSize 是 4012 M也可以⽤java 程序查看总堆以及剩余的堆内存:long totalMemory1 = Runtime.getRuntime().totalMemory();long freeMemory1 = Runtime.getRuntime().freeMemory(); 另外JVM 有两种模式,client模式和server 模式。
JVM原理及内存溢出经典案列分析
JVM原理及内存溢出经典案列分析JVM(Java Virtual Machine)是Java程序的运行环境,它提供了一种在不同的操作系统上运行Java程序的统一接口。
在JVM中,程序通过Java字节码执行,而不是直接执行机器码。
JVM具有自动内存管理功能,包括垃圾回收和内存分配。
然而,由于程序设计不合理或者资源使用不当,依然可能导致内存溢出的情况。
内存溢出(OutOfMemoryError)是指当JVM无法分配所需的内存空间时,发生溢出。
当可用内存不足时,JVM会尝试释放内存中不再使用的对象,垃圾回收器会回收这些对象的内存。
然而,有时候存在一些无法回收的对象,导致内存无法释放,进而引发内存溢出。
现在,我们来看一个经典的内存溢出案例:OOM问题。
```import java.util.ArrayList;import java.util.List;public class OutOfMemoryErrorExamplepublic static void main(String[] args)List<String> list = new ArrayList<>(;while (true)list.add("OutOfMemoryErrorExample");}}```这个例子会不断地往一个ArrayList中添加字符串对象。
由于没有限制循环的次数,程序会一直运行下去。
ArrayList的默认初始容量为10,当容量不够时,会自动扩容为原来的1.5倍。
而在这个例子中,由于没有限制容量,ArrayList会不断地扩容,直到超过JVM可以分配的内存,最终导致内存溢出。
解决这个问题的方法有多种,其中包括增加JVM内存、优化程序设计、限制循环次数等。
下面是一些常用的解决方法:1. 调整JVM内存参数。
可以通过增加JVM的最大堆内存限制来解决内存溢出问题。
可以通过`-Xmx`参数指定最大堆内存的大小,例如`-Xmx1024m`表示最大堆内存为1024MB。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
PermGen OOM调查Catalog 目录第1章虚拟机运行时数据区介绍 (3)1.1说明 (3)1.2运行时数据区分类 (3)1.3运行时数据区访问方式总结 (3)第2章Java类型卸载与类型更新解析 (4)2.1 说明 (4)2.2相关规范摘要 (4)2.3 类型卸载进一步分析 (5)1.测试场景一使用自定义类加载器加载,然后测试将其设置为unreachable的状态 (6)2.测试场景二使用系统类加载器加载,但是无法将其设置为unreachable的状态73.测试场景三使用扩展类加载器加载,但是无法将其设置为unreachable的状态82.4 类型卸载总结 (9)2.5 类型更新进一步分析 (9)1.测试场景一 (10)2.测试场景二同一个类加载器实例重复加载同一类型 (10)3.测试场景三同一个加载器类型的不同实例重复加载同一类型 (12)2.6 类型更新总结 (12)第3章PermGen内存溢出深入分析 (13)3.1前提知识 (13)3.2测试程序分析 (13)1.测试程序一:模拟PermGen OOM (14)2.测试程序一分析 (14)3.测试程序二:PermGen区域垃圾收集 (15)4.测试程序二分析 (16)3.3PermGen OOM原因总结 (16)3.4常见的类加载器和类型卸载的可能性总结 (17)1.普通Java应用 (17)2.插件开发 (17)3.5PermGen内存溢出的应对措施 (18)1.合理设置参数(针对普通用户和开发者) (18)2.有效利用虚拟机类型卸载机制(针对开发者) (19)第4章内存泄漏保护介绍............................................................................ 错误!未定义书签。
第1章虚拟机运行时数据区介绍1.1 说明本文分析总结PermGen OOM的原因,其中涉及到了Java虚拟机运行时数据区、类型装载、类型卸载等,测试代码涉及到了JMX协议。
相关前提知识如下:1、Java类加载的基本原理2、Java类型卸载相关的知识,3、简要了解JMX协议。
本章将对Java虚拟机运行时数据区做一个简单的介绍,着重说明PermGen区域(永久存储区)存放的内容,并对运行时数据区的访问方式做一个归纳说明,为后面深入分析类型卸载和PermGen OOM做铺垫。
为了更具有通用性,本部分将更多关注虚拟机协议本身,可能和具体的虚拟机实现有少许的出入。
1.2 运行时数据区分类Java虚拟机的运行时数据区一般分类如下(不一定是物理划分):∙堆:主要存放对象实例,线程共享∙栈:主要存储特定线程的方法调用状态,线程独占∙本地方法栈:存储本地方法的调用状态,线程独占∙PC寄存器:学过操作系统课程的都知道,线程独占∙方法区:主要存储了类型信息,线程共享方法区可以简单的等价为所谓的PermGen区域(永久存储区),在很多虚拟机相关的文档中,也将其称之为"永久堆"(permanent heap),作为堆空间的一部分存在。
介于此,我们可以简单说明一下我们常用的几个堆内存配置的参数关系:*-XX: PermSize:*永久堆(Pergen区域)大小默认值*-XX:MaxPermSize:*永久堆(Pergen区域)最大值*-Xms:*堆内存大小默认值*-Xmx:*堆内存最大值1.3 运行时数据区访问方式总结从开发者角度,虚拟机运行时数据区的访问方式简要归纳如下:∙活动的线程可以通过对应的栈来访问运行时数据区信息∙栈是堆访问的入口∙堆上ng.Class实例是访问PermGen区域中类型信息的入口一个类型装载之后会创建一个对应的ng.Class实例,这个实例本身和普通对象实例一样存储于堆中,我觉得之所以说是这是一种特殊的实例,某种程度上是因为其充当了访问PermGen区域中类型信息的代理者。
图中"Class类型实例"和"类加载器实例"分别是A类型对应的ng.Class实例和加载A类型的类加载器实例。
只要是有active的对象实例句柄,就能够访问到对应的Class类型实例和类加载器实例,分别通过Object.getClass()方法和Class.getClassLoader()方法。
只要是有active的Class类型实例句柄,就能够访问到对应的类加载器实例。
第2章Java类型卸载与类型更新解析2.1 说明本部分简要分析一下java类型卸载(unloading)的问题,并简要分析一下如何解决运行时加载newly compiled version的问题。
2.2相关规范摘要首先看一下,关于java虚拟机规范中时如何阐述类型卸载(unloading)的:Java虚拟机规范中关于类型卸载的内容就这么简单两句话,大致意思就是:只有当加载该类型的类加载器实例(非类加载器类型)为unreachable状态时,当前被加载的类型才被卸载.启动类加载器实例永远为reachable状态,由启动类加载器加载的类型可能永远不会被卸载.我们再看一下Java语言规范提供的关于类型卸载的更详细的信息(部分摘录):通过以上我们可以得出结论:类型卸载(unloading)仅仅是作为一种减少内存使用的性能优化措施存在的,具体和虚拟机实现有关,对开发者来说是透明的.纵观java语言规范及其相关的API规范,找不到显示类型卸载(unloading)的接口,换句话说:1、一个已经加载的类型被卸载的几率很小至少被卸载的时间是不确定的2、一个被特定类加载器实例加载的类型运行时可以认为是无法被更新的。
2.3 类型卸载进一步分析前面提到过,如果想卸载某类型,必须保证加载该类型的类加载器处于unreachable状态,现在我们再看看有关unreachable状态的解释:某种程度上讲,在一个稍微复杂的java应用中,我们很难准确判断出一个实例是否处于unreachable状态,所以为了更加准确的逼近这个所谓的unreachable状态,我们下面的测试代码尽量简单一点.1.测试场景一使用自定义类加载器加载,然后测试将其设置为unreachable的状态说明:1、自定义类加载器(为了简单起见,这里就假设加载当前工程以外D盘某文件夹的class)2、假设目前有一个简单自定义类型MyClass对应的字节码存在于D:/classes目录下我们增加虚拟机参数-verbose:gc来观察垃圾收集的情况,对应输出如下:2.测试场景二使用系统类加载器加载,但是无法将其设置为unreachable的状态说明:将场景一中的MyClass类型字节码文件放置到工程的输出目录下,以便系统类加载器可以加载由于系统ClassLoader实例(AppClassLoader@197d257">uncher$AppClassLoader@197d257)加载了很多类型,而且又没有明确的接口将其设置为null,所以我们无法将加载MyClass类型的系统类加载器实例设置为unreachable状态,所以通过测试结果我们可以看出,MyClass类型并没有被卸载.(说明:像类加载器实例这种较为特殊的对象一般在很多地方被引用,会在虚拟机中呆比较长的时间)。
3.测试场景三使用扩展类加载器加载,但是无法将其设置为unreachable的状态说明:将测试场景二中的MyClass类型字节码文件打包成jar放置到JRE扩展目录下,以便扩展类加载器可以加载的到。
由于标志扩展ClassLoader实例(ExtClassLoader@7259da">uncher$ExtClassLoader@7259da)加载了很多类型,而且又没有明确的接口将其设置为null,所以我们无法将加载MyClass类型的系统类加载器实例设置为unreachable状态,所以通过测试结果我们可以看出,MyClass类型并没有被卸载.关于启动类加载器我们就不需再做相关的测试了,jvm规范和JLS中已经有明确的说明了.2.4 类型卸载总结通过以上的相关测试(虽然测试的场景较为简单)我们可以大致这样概括:1、有启动类加载器加载的类型在整个运行期间是不可能被卸载的(jvm和jls规范).2、被系统类加载器和标准扩展类加载器加载的类型在运行期间不太可能被卸载,因为系统类加载器实例或者标准扩展类的实例基本上在整个运行期间总能直接或者间接的访问的到,其达到unreachable的可能性极小.(当然,在虚拟机快退出的时候可以,因为不管ClassLoader实例或者Class(ng.Class)实例也都是在堆中存在,同样遵循垃圾收集的规则).3、被开发者自定义的类加载器实例加载的类型只有在很简单的上下文环境中才能被卸载,而且一般还要借助于强制调用虚拟机的垃圾收集功能才可以做到.可以预想,稍微复杂点的应用场景中(尤其很多时候,用户在开发自定义类加载器实例的时候采用缓存的策略以提高系统性能),被加载的类型在运行期间也是几乎不太可能被卸载的(至少卸载的时间是不确定的).综合以上三点,我们可以默认前面的结论1,一个已经加载的类型被卸载的几率很小至少被卸载的时间是不确定的.同时,我们可以看的出来,开发者在开发代码时候,不应该对虚拟机的类型卸载做任何假设的前提下来实现系统中的特定功能.2.5 类型更新进一步分析前面已经明确说过,被一个特定类加载器实例加载的特定类型在运行时是无法被更新的.注意这里说的是一个特定的类加载器实例,而非一个特定的类加载器类型.1.测试场景一说明:现在要删除前面已经放在工程输出目录下和扩展目录下的对应的MyClass类型对应的字节码输出如下:通过结果我们可以看出来,两次加载获取到的两个Class类型实例是相同的.那是不是确实是我们的自定义类加载器真正意义上加载了两次呢(即从获取class字节码到定义class类型…整个过程呢)?通过对ng.ClassLoader的loadClass(String name,boolean resolve)方法进行调试,我们可以看出来,第二次加载并不是真正意义上的加载,而是直接返回了上次加载的结果.说明:为了调试方便,在Class classLoaded2 = classLoader.loadClass("MyClass");行设置断点,然后单步跳入,可以看到第二次加载请求返回的结果直接是上次加载的Class实例.2.测试场景二同一个类加载器实例重复加载同一类型说明:首先要对已有的用户自定义类加载器做一定的修改,要覆盖已有的类加载逻辑,MyURLClassLoader.java类简要修改如下:重新运行测试场景四中的测试代码说明:this.findClass(name)会进一步调用父类URLClassLoader中的对应方法,其中涉及到了defineClass(String name)的调用,所以说现在类加载器MyURLClassLoader会针对D:/classes/目录下的类型进行真正意义上的强制加载并定义对应的类型信息.结论:如果同一个类加载器实例重复强制加载(含有定义类型defineClass动作)相同类型,会引起ng.LinkageError: duplicate class definition.3.测试场景三同一个加载器类型的不同实例重复加载同一类型测试对应的输出如下:2.6 类型更新总结由不同类加载器实例重复强制加载(含有定义类型defineClass动作)同一类型不会引起ng.LinkageError错误,但是加载结果对应的Class类型实例是不同的,即实际上是不同的类型(虽然包名+类名相同). 如果强制转化使用,会引起ClassCastException.(说明:为什么不同类加载器加载同名类型实际得到的结果其实是不同类型-----在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间).应用场景:我们在开发的时候可能会遇到这样的需求,就是要动态加载某指定类型class 文件的不同版本,以便能动态更新对应功能.建议:1. 不要寄希望于等待指定类型的以前版本被卸载,卸载行为对java开发人员透明的.2. 比较可靠的做法是,每次创建特定类加载器的新实例来加载指定类型的不同版本,这种使用场景下,一般就要牺牲缓存特定类型的类加载器实例以带来性能优化的策略了.对于指定类型已经被加载的版本,会在适当时机达到unreachable状态,被unload并垃圾回收.每次使用完类加载器特定实例后(确定不需要再使用时),将其显示赋为null,这样可能会比较快的达到jvm 规范中所说的类加载器实例unreachable状态,增大已经不再使用的类型版本被尽快卸载的机会.3. 不得不提的是,每次用新的类加载器实例去加载指定类型的指定版本,确实会带来一定的内存消耗,一般类加载器实例会在内存中保留比较长的时间.第3章PermGen内存溢出深入分析3.1 前提知识∙由不同的类加载器实例加载的类型可以等价为完全不同的类型,哪怕是同一类型类加载器的不同实例加载的,都会在PermGen区域分配相应的空间来存储类型信息。