ITPub博客

首页 > 应用开发 > Java > 深入理解JVM之内存管理

深入理解JVM之内存管理

原创 Java 作者:541732025 时间:2014-03-27 16:32:12 0 删除 编辑


1,方法区:存放类的信息(名称、修饰符等)、类中的静态变量、类中final型常量、类中的Field信息,类中的方法信息。方法区是全局共享的,在一定条件下也会被GC,当方法区要使用的内存超过其允许的大小时,会抛出OutOfMemory。
JVM中这块区域对应Permanet Generation,又称为持久代。默认最小为16MB,最大为64MB,可通过-XX:PermSize及-XX:MaxPermSize来指定最小值和最大值
2,堆:存放对象实例及数组值,大部分new创建的对象的内存都在此分配,这里也是GC最频繁的地方。32位系统最大为2G,64位则没限制,大小可通过-Xmx和-Xms来指定,为了避免在运行时对heap的调整,通常将-Xmx和-Xms的值设成一样。

为了让内存回收更加高效,JDK1.2开始采用分代回收,不同的区域采用不同的回收算法。
1,新生代:大多数new对象都在此分配内存,新生代由Eden、两个存活区(survivor space)构成,可通过-Xmn来指定新生代的大小,还可以通过-XX:SurvivorRatio(在Parallel Scavenge中是通过-XX:InitialSurvivorRatio设置)来调整Eden、存活区的大小比例,默认是8:1
2,老年代:用于存放新生代中经过多次垃圾回收任然存活的对象,如缓存的对象,新建的对象也有可能在老年代上直接分配,这种情况分两种,一种是大对象,直接在老年代分配,可以通过-XX:PretenureSizeThreshold指定大小阈值,但此参数在新生代采用Parallel Scavenge GC时无效Parallel Scavenge GC会根据运行状况决定什么对象直接在老年代分配;另一种在老年代直接分配的是大数组,其数组中无引用外部对象。老年代的大小为-Xmx减去-Xmn
3,本地方法栈:用于支持native方法的执行,存储每个native方法调用的状态,在Hotspot中本地方法栈和JVM方法栈是同一个。
4,PC寄存器和JVM方法栈:每个线程都会创建PC寄存器和JVM方法栈,PC寄存器占用的可能是CPU寄存器或操作系统内存,JVM方法栈占用操作系统内存,JVM方法栈为线程私有,其内存分配高效,当方法运行完毕时,其对应的栈帧所占用的内存也会被自动释放。当JVM方法栈空间不足时,会抛出StackOverflowError,可以通过-Xss来指定方法栈的大小,如果不出现无穷递归,栈的深度不会太大,一般配置1M够矣。

内存分配:
主要在堆上分配,堆为所有线程共享,因此在堆上分配内存时需要加锁,这导致创建对象开销比较大,当堆空间不足时,会触发GC,如果GC后任然不足,则抛OutOfMemory。
为了提升内存分配效率,JVM会为新创建的线程在新生代的Eden上分配一块独立的空间,称为TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行情况计算而来,可通过-XX:TLABWasteTargetPrecentlaishezhi TLAB可占用Eden的百分比,默认为1%。在TLAB上分配不需加锁,因此效率高,JVM在给线程中的对象分配内存时会尽量在TLAB上分配,如果对象过大或TLAB空间用完,则在堆上分配。
除了在堆上分配及在TLAB上分配,还有一种基于逃逸分析直接在栈上分配的情况。

内存回收
收集器
1,引用计数收集器:采用计数器来判断对象是否被引用,当计数器为0时,说明此对象已经不再被引用,可以被回收。引用计数对于循环引用的场景没办法回收,所以在Sun JDK实现GC时也未采用这种方式。
2,跟踪收集器:全局记录数据的引用状态,基于一定的条件触发(定时、空间不足)执行时需要从根集合来扫描对象的引用关系。主要有复制、标记-清除、标记-压缩三种实现算法。
复制:从根集合扫描出存活的对象,并将它们复制到一块新的空间。当回收区域存活对象较少时,复制算法比较高效,其成本是开辟一块空内存以及对象的移动。
标记-清除:从根集合扫描出存活的对象,并对它们进行标记,之后清空未标记的对象。标记-清除动作不需要对象移动,仅对不存活的对象处理,在空间中存活对象较多的情况下较为高效,但会产生内存碎片。
标记-压缩:前面动作和标记-清除一样,但是清除不存活的对象后,会将存活对象都往空闲的空间移动,并更新引用指针。成本相对较高,但避免了内存碎片。
根集合对象:当前线程栈上引用的对象、常量、静态变量、传到本地方法中,还没有被本地方法释放的对象。

JDK中的GC收集器:不同的区域采用不同的GC收集器

新生代可用GC:
新生代中的对象通常存活时间较短,因此采用复制算法。上面提到,复制时将存活对象移动到一块新的区域,这个区域就是新生代中的其中一个survior space。对新生代的回收又叫Minor GC。
1,串行GC(Serial GC)
当新生代不够空间来创建对象时,就会触发Minor GC。如果Minor GC仅从根集合对象中扫描存活对象,则当老年代中的对象引用了新生代的对象时会出问题,但老年代通常比较大,为了提高性能,也不可能每次Minor GC都会去扫描整个老年代,Sun JDK采用的是remember set方式来解决此问题。JVM在进行对象引用赋值时,会检查赋值的对象是否在老年代,并检查该对象是否有引用指向新生代,如果条件满足,则在remember set做个标记。因此,对于Minor GC而言,完整的根集合为Sun JDK认为的根集合对象加上remember set中标记的对象。
为了避免在扫描过程中引用关系发生变化,采用暂停应用的方式,JDK在编译代码时会为每段方法注入SafePoint,通常SafePoint位于方法中循环的结束
点一级方法执行完毕的点。在暂停应用时需要等待所有的用户线程进入SafePoint,然后将内存页设置为不可读状态,从而实现暂停用户线程的执行。
对象引用关系:除了默认的强引用,还有软引用、弱引用、虚引用
A a = new A(),这就是强引用,这种对象只有主动释放引用后才会被GC
软引用,采用SoftReference来实现,这种对象会在JVM内存不足时被回收,因此软引用很适合用于实现缓存,另外当GC认为扫描到的软引用对象不经常使用时,也会被回收。
弱引用:采用WeakReference来实现,这种对象在没有强引用后,会被回收。

在Minor GC后存活的对象并不是直接进入老年代,只有经历过几次Minor GC后任然存货的对象才会进入老年代。这个在Minor GC中存活的次数在串行、ParNew方式时可通过-XX:MaxTenuringThreshhold设置,在Parallel Scavenge则由hotspot根据运行状况来句定。当存活区已满,剩下的存活对象则直接进入老年代。
Serial GC在整个扫描、复制过程中均采用单线程,适用于单CPU、新生代空间较小、及对暂停时间要求

2,并行回收GC(Parallel Scavenge)
上面已经提过,在Parallel Scavenge中,Eden,survivor的比例通过-XX:InitialSurvivorRatio来配置,默认也为8,不过,在jdk6以后,也能通过-XX:SurvivorRatio来配置了。
在启动时Eden,survivor的比例按照配置划分,但是在运行一段时间以后,并行回收GC会根据Minor GC的频率,消耗的时间来动态调整比例,可以通过-XX:-UseAdaptiveSizePolicy来固定比例
在PS GC中不是通过-XX:PretenureSizeThreshold来决定对象是否在老年代直接分配的,而是当分配内存时,如果Eden空间不够,而且对象大小也大于等于Eden的一半,则直接在老年代分配。
PS GC也是采用复制算法回收垃圾,但区别于Serial GC的地方在于,其扫描和复制时均采用多线程方式来进行,在多CPU机器上效率更高,适合对暂停时间要求较短的应用上。PS GC也是C2级别上默认采用的GC方式。

3,并行GC(ParNew)
ParNew在SurvivorRatio的方式上和串行GC一样。ParNew与Ps GC的区别在于ParNew必须配合老年代使用CMS GC,因为CMS GC在对老年代回收时,有些过程是并发进行的,如此时发生Minor GC,需要进行相应的处理,而PS GC是没有做这些处理的,也正是这个原因,ParNew不可与并行的老年代GC同时使用。
在配置老年代使用CMS GC的情况下,新生代默认采用ParNew
同样,当Eden空间不足时,会触发Minor GC

综上:新生代各GC器的区别在于:
1,
-XX:PretenureSizeThreshold,关于大对象在老年代分配,Serial是根据此值判断,而Parallel Scavenge实在分配内存时判断,如果Eden不够,且对象大于Eden的一半,则直接在老年代分配
2,关于晋升老年代的条件,Serial、ParNew是设置
-XX:MaxTenuringThreshhold,熬过了一定次数的GC则晋升老年代,如果survivor已满,则直接晋升老年代。而Parallel Scavenge则是根据运行状况来决定。
3,ParNew的特点是必须搭配老年代的CMS GC使用
综上,主要区别还是与老年代有关

Minor GC的触发方式
1,Eden上分配内存时空间不足,触发Minor GC
2,System.gc显示调用也可以触发Minor GC

老年代、持久代可用GC
串行、并行、并发
主要讲讲并发CMS GC:其它几种回收都是stop the world,造成应用暂停,所以提供了CMS GC,它的大部分动作都能与应用并发执行。CMS GC采用Mark-Sweep
CMS分4个步骤:
1,初始标记:stop the world,也是从根集合出发,扫描
2,并发标记:并行运行,标记上一步存活对象的引用的对象
3,再次标记:stop the world,因为上一步中可能会有新对象创建,或者对象引用改变,所以要对这些对象进行扫描
4,并发收集

CMS GC触发方式有2种
1,如果老年代使用CMS,则可设置CMSInitiatingOccupancyFraction百分比,当老年代空间使用达到某个值时,触发
2,还一种是JVM自动触发,基于之前GC的频率以及老年代的增长趋势来决定

Full GC
除CMS GC之外,当老年代、持久代发生GC时,其实是对新生代、老年代、持久代都进行GC,因此又叫Full GC。
以下情况会触发Full GC:
1,老年代空间不足(新生代晋升、直接老年代创建大对象),会触发Full GC,若GC后空间还不够,则抛OutOfMemory:java heap space
2,Permanent Generation空间满,该区域存放class信息,当空间满,则触发Full GC,如果GC后任然不够,则抛OutOfMemory:PermGen space
3,CMS GC时出现promotion failed和concurrent mode failure。在执行Minor GC(对应ParNew)时,晋升老年代的对象过多;或者执行CMS GC时同时有对象要放入老年代,而老年代空间有不足,这两种CMS情况会触发Full GC
4,统计得到Minor GC晋升到老年代的平均大小大于老年代空间,在进行Minor GC时,如果之前统计得到Minor GC晋升到老年代的平均大小大于老年代剩余空间,则触发Full GC。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/28912557/viewspace-1130874/,如需转载,请注明出处,否则将追究法律责任。

下一篇: Java并发相关概念
请登录后发表评论 登录
全部评论

注册时间:2013-05-23

  • 博文量
    127
  • 访问量
    481786