JVM内存分配策略
- 对象有限分配在Eden区, 如果Eden区空间不足则执行一次Minor GC. 执行Minor GC之后Eden中绝大多数的对象都会被清理, 少部分进入Suvior区. Minor GC的触发总是在分配对象空间不够时.
- 大对象的分配直接进入老年代. 为什么这样做: 因为如果大对象分配到年轻代的话会产生大量的内存拷贝. -XX:PretenureSizeThreshold类设置直接进入老年代的对象大小.
- 长期存活的对象晋升老年代. 经过几轮的GC之后仍然没有被回收的对象会晋升到老年代. -XX:MaxTenureSize来设置对象进入老年代的年龄限.
- 动态年龄判断. 当S区中相同年龄对象占据的空间达到总空间的一半时则大于等于该年龄的对象晋升到老年代.
- 空间分配担保. 在发生Minor GC的时候, 虚拟机会检查每次晋升到老年代对象大小的平均值是否大于现在老年代的剩余空间. 如果大于则改为执行一次Full GC. 如果小于, 则检查是否设置HandlePromotionFailure是否设置是否为允许担保失败, 如果允许则进行一次Minor GC, 如果不允许则改为一次Full GC. 通常为了避免频繁的执行Full GC这个个开关都打开.
空间担保的意思是说, 执行Minor GC前, 假如年轻代空间不足, 则需要老年代来容纳现在在Suvivor区存活的对象. 因为GC还没有进行则根本不知道能够收集多少垃圾, 所以比较的值是每次晋升到老年代的平均值. 所以在执行Minor GC时如果当前老年代的剩余空间比平均值小则转换为一次Full GC.
垃圾收集器
在介绍垃圾收集器之前先说一下垃圾收集算法. 最简单的垃圾收集算法是标记-清除. 该算法的工作过程非常简单, 事先标记出要回收的对象, 然后清除. 标记清除算法的最大缺点就是容易产生内存碎片.
在此基础上人们提出了另外一种算法: 复制算法. 复制算法把内存空间分成两部分, 每次只用一块内存, 执行垃圾收集之后, 存活的对象转移到另外一块内存. 这种方式解决了内存碎片的问题, 但是空间使用率大大下降了.
基于标记清除的另外一种改进是标记整理算法, 和标记清除算法不同的是该算法在标记完成之后要先做一次内存整理. 整理的过程是这样的, 把存活的对象移动到内存的另一端. 然后把剩下的内存空间都清理掉. 标记整理算法解决了标记清除算法的内存碎片问题, 也不存在复制算法的空间浪费问题.
但是人们并没有止步, 又进一步提出了分代算法. 该算法的思想是把内存分成不同的区域: 老年代和新生代. 新生代的对象的特点是: 量大, 短命, 而老年代的对象更稳定一些. 因此可以根据不同区域的对象特点选择不同的垃圾收集算法.
了解了垃圾收集算法, 下边看一下垃圾收集器.
适用于年轻代的收集器
- Serial. 单线程执行垃圾收集, 收集时要Stop the world.
- Par New. Serial的多线程版本.
- Parallel Scavenge. 吞吐量优先垃圾收集器. 吞吐量 = 用户代码时间 / 垃圾收集时间 + 用户代码时间
适用于老年代的收集器
- Serial Old. Serial Old垃圾收集器是单线程的垃圾收集器, 采用"标记-整理"算法. 它是Client模式下的默认老年代垃圾收集器, 同时也是CMS的后备预案. 在并发收集器发生Concurrent Mode Failure时启用.
- CMS 初始标记, 并发标记, 重新标记, 并发清除. 在这四个阶段中, 初始标记, 和重新标记是需要STW的, 其他都是和用户线程并发执行的. 由于CMS算法在工作过程中用户还会产生垃圾, 因此不能像其它垃圾收集器那样等到空间快满时才执行. CMS默认实在空间使用68%时就会触发垃圾回收. 这个参数是可以调节的: -XX:UseCMSInitiatingOccupancyOnly. 如果在CMS运行期间预留的空间无法满足程序的需求这时会出现一次"Concurrent Mode Failure", 出现这个异常之后, 会启用Serial Old收集器, 暂停应用进行彻底收集.
CMS垃圾收集器还有一个缺点, 执行GC后会有碎片, 所以JVM也提供了参数用于在执行了GC之后执行一次空间整理(-XX: CMSCompactAtFullCollection). 还有一个参数用于设置执行几次GC后才执行一次空间整理(-XX:CMSFullGCsBeforeCompaction).
CMS垃圾收集器的另外一个缺点是: 对CPU资源敏感. CMS默认启用的线程数是: CPUs + 3 / 4, 所以在CPU资源敏感的地方, CMS收集器还是会影响程序性能. - Parallel Old. Prrallel Old使用"标记整理算法", 它是Parallel Scavenge的老年代版本. 它和Parallel Scavenge搭配也是最是和的.
- G1. G1收集器采用"标记整理算法", 它把Java堆内存分成了多个大小固定的块, 并且跟踪这些区域里面垃圾堆积程度, 根据允许的收集时间优先收集垃圾最多的区域. 这样可以确保在有限的时间获得最高的垃圾收集效率.
G1最大的优势是它可以精确的控制垃圾收集时间, 一般它适用于4G以上的大堆.