分代垃圾
垃圾收集器基于弱分代假设
- 大多数分配对象的存活时间很短
- 存活时间久的对象很少引用存活时间短的对象
新生代
空间小且收集发生频繁,新生代大部分对象存活时间短,新生代收集(Minor GC)之后存活的对象很少。由于Minor GC专注于小对象且有大量垃圾对象的空间,所以收集效率很高。
老年代
新生代长期存活的对象会被晋升(Tenure)到老年代,老年代收集(Full GC)发生频率低,但是发生时间长。
垃圾收集器不需要扫描整个老年代就可以知道新生代中存活的对象,因为老年代中使用了Card Table结构用来保存老年代对象对新生代的引用状态,这个Card Table会在新生代对象字段发生变化时更新是否为脏卡,Minor GC会只扫描老年代中的脏卡,所以效率很高。
新生代
- Eden 大多数新对象分配在这里,如果是大对象直接分配到老年代,Eden几乎总是空的。
- Survivor 这个有一对,分别具有From To两个角色,这里存放的对象至少经历过了一次Mnior GC,他们在晋升到老年代之前还有一次被回收的机会,当发生Mnior GC后这一对空间的角色会发生互换,From表示保存存活的对象,To表示空的空间。
由于Minor GC收集过程中复制存活的对象,这种垃圾收集器称为复制垃圾收集器
。当Minor GC中Survivor可能不足以容纳Eden和另一个Survivor中存活对象,如果出现存活对象溢出,多余的对象会被过早的晋升到老年代,这会导致老年代中短期存活对象的增长从而引发性能问题。当在Minor GC后对象溢出发生后会进行Full FC,这会导致遍历整个Java堆,这称为提早失败
。
快速分配内存
垃圾收集器以复制方式回收HotSpot VM新生代,其好处在于回收以后Eden总为空,在Eden中运用被称为指针碰撞
的技术就可以有效的分配空间。应用多是多线程的,内存分配的操作需要考虑多线程安全,如果只用全局锁,在Eden中的分配操作就会成为瓶颈因而降低性能,HotSpot VM使用了线程本地分配缓冲区(TLAB)
技术,为每个线程设置各自的缓冲区,即Eden的一下块,以此来改进多线程分配的吞吐量。这样使用指针碰撞技术快速分配不需要使用锁。当TLAB被填满时需要获取新的空间,就需要采用多线程安全的方式。
老年代
老年代采用标记清除压缩收集算法
Serial 收集器
Minor GC和Full GC都是以Stop-The-World方式运行,只有等垃圾收集结束后,应用程序才会继续执行。适合对停顿不敏感或者客户端应用。
Parallel收集器
吞吐量为先的收集器。新生代采用Stop-The-World方式收集,老年代采用标记-压缩方式。Minor GC和Full GC都是并行的,使用所有可用的处理器资源。
Mostly-Concurrent收集器
低延迟为先。有的应用需要快速响应,在Stop-The-World模式中,应用线程在垃圾收集开始时停止运行,直到垃圾收集结束后才继续运行和处理外部请求。Minor GC通常不会导致长时间的停顿。
HotSpot VM引入了Mostly-Concurrent收集器,也称为并发标记清除收集器(CMS),它管理新生代的方式和Parallel收集器和Serial收集器相同,但在老年代则是尽可能的并发执行,每个垃圾收集周期只有2次短的停顿,这是因为它有2个标记阶段,初始标记和重新标记。
与Parallel收集器相比CMS老年代停顿变短了,但代价是新生代停顿略微拉长,吞吐量有所降低,堆的大小有所增长,并且由于并发,垃圾收集会占用应用CPU周期,这适合快速响应的(Web服务器)应用。
G1收集器
他是CMS的替代者。G1是一个并行、并发和增量式压缩停顿的垃圾收集器。G1整体上没有划分成新生代和老年代,它将Java堆分成相同尺寸的块(区域 Region)。G1的名字由来是优先收集垃圾最多的区域。
影响垃圾收集的条件
- 内存分配 当Eden满时,就会发生Minor GC;当老年代占用超过CMS初始限额就会发生CMS,应用的内存分配速率越高,垃圾收集的触发就越频繁。不合适的数组类数据结构尺寸,如果ArrayList初始尺寸太小,它内部的数组随后可能调整尺寸几次,导致不必要的内存分配。
- 存活数据的多少 Java堆中存活对象越多,收集器需要做的工作越多。不好的编程实践,对象池化,它是长时间存活的,因此他们会增加老年代存活数据的尺寸。
- 老年代中引用的更新