堆内存示例图
Minor GC / Young GC
指发生在新生代的垃圾收集动作
注意:一般Minor GC 会频繁触发,并且效率非常快
Major GC / Full GC
一般回收老年代,年轻代,方法区的垃圾
注意: Major GC 的速度一般比Minor GC 慢十倍以上
内存分配策略
1.对象优先分配分配在Eden中分配
大多数情况下,对象都是在Eden 区分配,当Eden 区没有足够的内存进行分配时,会自动发起一次Minor GC
2. 大对象直接进入老年代
大对象: 就是需要大量连续的内存空间的对象,例如:字符串,数组
可以通过JVM参数: -XX:PretenureSizeThreshold(只有垃圾收集器是Serial和ParNew时有效) 来设置大对象的大小
为什么大对象直接进入老年代:为了避免大对象分配内存时的复制操作从而降低效率
3. 长期存活的对象将进入老年代
虚拟机将会给每个对象一个年龄计数器,如果对象在Eden区出生,
并且经过Minor GC之后能够存活,而且能够被Survivor 容纳的话将被移动到Survivor中去,则将年龄计数器设置为一;
对象在Survivor中每熬过一次Minor GC 就会将年龄计数器加一
什么情况下会进入老年区:默认在年龄计数器等于15的时候,就会将该对象放入到老年代中
4. Minor GC 后存活的对象 Survivor 区放不下
Minor GC 后存活的对象 Survivor 区放不下时,会把存活的对象部分放在老年区和 Survivor区中
5. Eden 区和 Survivor 区默认的比例是 8:1:1
大量的对象会分配在Eden 区,在Minor GC 之后绝大部分的垃圾会被回收,然后剩余的对象会被放入到Survivor区中
6. 对象动态年龄判断
如果当前放对象的 Survivor 区中一批对象的总大小大于当前 Survivor区内存大小的50%,那么此时对于等于这批对象年龄最大值的对象就会直接进入到老年代中
注:这个规则其实是希望那些可能长期存活的对象,尽早进入老年代
注:对象动态年龄判断一般是在Minor GC 之后触发
7. 老年代空间分配担保机制
垃圾回收算法
标记清除算法
首先标记出所有需要回收的对象,标记完成之后,统一回收
缺点:这种方式会产生大量不连续的空间,当有大对象需要分配连续的空间时,也能会再次触发垃圾回收
标记整理算法
首先标记出所有需要回收的对象,标记完成之后,统一回收之后,然后会集中整理内存区域,得到较为规整的区域(连续的内存空间)
好处:可以避免出现连续大量的内存空间
缺点:效率低
复制算法
复制算法是将内存分为相同的2块部分,每次只用一块,当其中某一块内存用完了,就会将还在存活的对象复制到另外一块内存中,然后将之前的一块内存全部清空
好处:效率高,没碎片,适合朝生夕死的内存区域
缺点:内存利用率低,且不适合在对象存活率高的老年代中使用
JVM 中使用的算法
新生代中使用复制算法
老年代/元数据区使用标记清除/标记整理算法
垃圾收集器
Serial 收集器
单线程收集器,该收集器在收集垃圾时,需要暂停其他线程,才能进行收集,收集之后其他线程才能执行,该收集器在不断的优化,执行时间在慢慢缩短
新生代算法:复制算法
老年代算法:标记整理算法
优点:简单而高效,而没有线程交互开销,自然会获得很高的效率
缺点:会让其他线程停顿
ParNew 收集器
就是Serial收集器的多线程版本
新生代算法:复制算法
老年代算法:标记整理算法
注意:ParNew收集器默认的线程数跟CPU 核数相同(也可以用参数指定数量-XX:ParallelGCThreads 指定线程收集树)
Parallel Scavenge 收集器
和ParNew收集器类似,Parallel Scavenge是Java1.8默认的收集器,
新生代算法:复制算法
老年代算法:标记整理算法
优点:并行的多线程回收,以吞吐量优先。
CMS(Conrrurent Mark Sweep) 收集器
以获取最短回收停顿时间为目标的收集器
优点:并发收集,停顿低
缺点:
1. 对CPU 资源敏感
2. 无法处理浮动垃圾
3. 它使用标记清除算法,会有大量空间碎片产生
4. 执行过程中的不确定性,会在上一次垃圾回收还未执行完,然后垃圾回收又被触发的情况