垃圾收集器与内存分配策略

哪些区域需要回收?java堆和方法区

一、怎么判断对象已死?

  • 引用计数器法:Java并未采取这种策略,因为它难以解决对象之间相互循环访问的问题
  • 根搜索算法:以一系列的的“GC Roots”对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链接相连时,则证明此对象是不可用的。可作为GC Roots的对象包括下面几种:
  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中的类静态属性引用的对象
  3. 方法区中的常量引用的对象
  4. 本地方法栈中的引用的对象

二、垃圾收集算法

  1. 标记-清除算法:它的缺点有两方面,一是效率问题:标记和清除过程的效率不高;二是空间问题:标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当程序在以后的运行过程中需要分配大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作
  2. 复制算法:解决了效率问题,将可用内存划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活者的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对其中一块进行内存回收,内存分配时也不用考虑内存碎片等复杂情况。代价是将内存缩小为原来的一半。此算法用于新生代回收,因为新生代的对象具有朝生夕死的特点,所以不用按照1:1的比例划分内存,而是将内存划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor,每次回收时,将Eden和Survivor中还活着的对象一次性拷贝到另外一块Survivor空间,最后清理掉Eden和和刚才使用过的Survivor空间。当Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保(Handle Promotion),即如果另外一块Survivor空间没有足够的空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代。它的缺点是在对象存活率较高时需要执行较高的复制操作,效率将会变低。更为严重的是,如果不想浪费50% 的空间,就需要额外的空间进行分配担保,以应对被使用的内存中100%的对象都存活的极端情况,所以在老年代一般不能直接选用这种算法。
  3. 标记-整理算法(Mark-Compact):适用于老年代,让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存
  4. 分代收集算法:Java堆分为新生代和老年代。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清理或标记-整理算法来进行收集

三、垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是垃圾回收的具体实现。如下:如果两个收集器之间有连线,那么说明这两个收集器可以搭配使用

  1. Serial:单线程收集器,在它进行垃圾回收时,必须暂停所有的工作线程(Stop the World)
  2. ParNew:Serial的多线程版本,Server模式下的虚拟机新生代的首选
  3. Parallel Scavenge:新生代、使用复制算法、多线程收集器
  4. Serial Old:Serial的老年代版本,单线程,使用标记-整理算法
  5. Parallel Old:Parallel Scavenge的老年代版本,使用多线程和标记整理算法
  6. CMS(Concurrent Mark Sweep):以获取最短时间停顿时间的收集器,基于“标记-清除”算法实现,它的优点是并发收集、低停顿,它的缺点有:对CPU资源敏感、无法处理浮动垃圾、产生空间碎片
  7. G1:相对于CMS有两个显著的改进:一是基于“标记-整理算法实现”,不会产生空间碎片;二是可以非常精准地控制停顿,它可以实现在不牺牲吞吐量的前提下完成低停顿的回收,这是由于它极力地避免全区域的垃圾回收,之前的收集器的回收区域都是整个新生代或老年代,而G1将Java堆划分为多个大小固定的独立区域,并且跟踪这些区域里的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域(这就是Garbage First名称的由来)

四、内存分配与回收策略

对象优先在Eden区分配

大多数情况下,对象在新生代Eden区分配,当Eden区没有足够的空间分配时,虚拟机将发起一次Minor GC

大对象直接进入老年代

所谓大对象是指需要大量连续内存空间的Java对象(比如字符串和数组),虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代中分配,这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝

长期存活的对象将直接进入老年代

虚拟机既然采用了分代收集的思想来管理内存,那内存回收是就必须能识别哪些对象应放在新生代,哪些对象应放在老年代。为了做到这点,虚拟机给每个对象定义了一个对象年龄计数器。如果对象在Eden区出生并经过第一次Minor GC后任然存活,并且能被Survivor容纳的话,将被移动到Survivor空间,并经对象年龄设为1.对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁)(可由参数-XX:MaxTenuringThreshold来设置)时,就会被晋升到老年代。

动态对象年龄判断

为了更好地适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄

空间分配担保

在发生Minor GC 时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandelPromotionFailure设置是否允许担保失败;如果允许,那只会进行Minor GC;如果不允许,则要改为进行一次Full GC。老年代要进行空间分配担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来,在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。

五、会触发Full GC的情况

  1. 调用System.gc
  2. 老年代空间不足:老年代空间只有在年轻代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行 Full GC 后空间仍然不足,则抛出如下错误 Java.lang.OutOfMemoryError: Java heap space 。为了避免以上两种状况引起的 Full GC,调优时应尽量做到让对象在 MinorGC 阶段被回收,让对象在年轻代多存活一段时间,以及尽量不要创建过大的对象及数组。

  3. 永久代空间满:永久代中存放的是一些类的信息,当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下会执行 Full GC。如果经过 Full GC 仍然回收不了,那么jvm会抛出如下错误信息 Java.lang.OutOf1MemoryError: PerrnGen space 。为了避免永久代被占满造成 Full GC 现象,可采用的方法为增大永久代空间或转为使用CMS GC 。
  4. CMS GC时出现Promotion Failed和Concurrent Mode Failure:对于采用 CMS 进行老年代 GC 的程序而言,尤其要注意 GC 日志中是否有 Promotion Failed和 Concurrent Mode Failure 两种状况,当这两种状况出现时可能会触发 Full GC 。 Promotio Failed是在进行 MinorGC 时, Survivor Space 放不下,对象只能放入老年代,而此时老年代也放不下时造成的。 Concurrent Mode Failure 是在执行 CMS GC 的过程中,同时有对象要放入老年代,而此时老年代空间不足造成的 。应对措施为增大 Survivor Space、老年代空间或调低触发并发 GC 的比率
  5. 统计得到Minor GC晋升到老年代的平均大小大于老年代的剩余空间:HotSpot 为了避免由于年轻代对象晋升到老年代导致老年代空间不足的现象,在进行 MinorGC 时,做了一个判断,如果之前统计所得到的 MinorGC 晋升到老年代的平均大小大于老年代的剩余空间,那么就直接触发 Full GC 。
     

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值