待学
- JVM垃圾回收
JVM垃圾回收
如上图所示,Eden,Survivor0,Survivor1都是新生代区,Old Memory属于老年代区。
大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数
-XX:MaxTenuringThreshold
来设置。
一次Minor GC的过程:将Eden和From存活的对象复制到To区去,然后清理掉Eden和From空间,然后将From和To的角色互换一下。在Hotspot虚拟机中默认Eden和Survivor的大小比例是8:1,也就是说新生代可用内存空间为整个新生代容量的90%。
当垃圾回收时,不能保证存活对象的大小不会超过新生代内存的10%,所以当Survivor空间不够用时会使用老年代进行分配担保。
堆内存常用分配策略
- 大对象直接进入老年代
- 长期存活的对象会进入老年代
- 对象优先分配到Eden区
大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
能够避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。
长期存活对象进入老年代
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数
-XX:MaxTenuringThreshold
来设置。
对象优先进入Eden区
垃圾收集算法
标记-清除算法
最基础的算法是:标记-清除(Mark-Sweep)算法。它分为两部分,第一部分:标记所有需要清除的对象,第二部分:标记完成后统一回收对象。
它有两个缺点:
- 效率问题,无论是标记还是清除效率都不高。
- 空间问题,标记-清除算法会产生大量不连续的内存碎片,如果空间碎片太多,会导致分配内存较大的对象时提前进行一次垃圾收集动作。
复制算法
普通型
普通型复制算法将内存分成两个大小相等的部分,每次只使用一块,当这一块的内存使用完后,将还存活的对象复制到另一块上去,然后将使用过的这一块一次性清理掉。
好处:
- 垃圾收集时不用考虑空间问题,不会产生内存碎片等情况。
- 效率也快。
缺点:
- 将内存缩小成原来的一半,代价太高。
实用性
实用性复制算法思路与上面类似,但不需要将内存进行1:1的划分。根据研究,新生代里的对象都是“朝生夕死”的,所以它将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。
当回收时,将Eden空间和Survivor空间还存活的对象复制到另一个Survivor空间上,最后将Eden和刚才使用过的Survivor一起清理掉。也就是说新生代可用内存空间为整个新生代容量的90%。
当垃圾回收时,不能保证存活对象的大小不会超过新生代内存的10%,所以当Survivor空间不够用时会使用老年代进行分配担保。
好处:
- 不会耗费很多内存,只耗费10%
- 垃圾收集时不用考虑空间问题,不会产生内存碎片等情况。
- 效率也快。
坏处:
- 需要老年代进行担保
标记-整理算法
标记-整理(Mark-Compact)算法。它分为两部分,第一部分:标记所有需要清除的对象,第二部分:让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。
好处:
- 不需要担保
- 不会产生内存碎片
垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
另外没有最好的垃圾收集器,只有最适合的,没有“银弹”,我们能做的就是根据具体应用场景选择适合自己的垃圾收集器。
垃圾收集器主要有下面5种:
- Serial
- ParNew
- Parallel Scavenge
- CMS
- G1
Serial收集器
它是历史最基本、最悠久的垃圾收集器。正如它的名字一样,它是serial(连续的)单线程的,这意味着它只会有一个线程来进行垃圾收集工作,并且其他工作线程都会被暂停(Stop The World)。
它新生代采用了复制算法,老年代采用了标记-整理算法。
它简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。
ParNew收集器
它就是Serial的多线程版本,除了使用多线程进行垃圾收集外,其他行为都跟Serial一模一样。
它新生代采用了复制算法,老年代采用了标记-整理算法。
它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。
Parallel Scavenge
Parallel Scavenage收集器与ParNew收集器几乎一模一样。不过它重点关注的是吞吐量(高效利用CPU),Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择。
它新生代采用了复制算法,老年代采用了标记-整理算法。
它是JDK1.8默认使用的垃圾收集器