内存模型
Java中的内存模型如上图所示,分为两大块old(老年代)和young(新生代),新生代又分为三个区,分别是Eden(伊甸园),from Survivor(幸存者),to Survivor。为什么要这么设计呢,其实和垃圾回收算法有关。
回收算法
标记-清除算法(Mark-Sweep)
标记清除算法将垃圾收集分为两个步骤,第一步是标记,标记的对象也就是即将要回收的垃圾对象。那么如何判定一个对象是垃圾对象呢。一个对象如果不存在任何引用就可以认为它是一个垃圾对象。第二步是清除,将第一步中找出的垃圾对象进行清除。
从上述的描述可知,标记-清除算法容易导致大量的空间碎片,因为回收后的空间是不连续的。
复制算法(Copying)
针对标记清除算法的缺点,有人提出了复制算法。复制算法的核心在于将内存分为两块,在进行对象分配时,只使用其中一块内存(from),当这块内存满了就进行垃圾回收:将存活的对象复制到另一块内存(to)中,此时可以清除from中的所有对象,完成此次垃圾收集。若to满了,便将对象复制到from,清除to中的对象,如此反复。
复制算法保证了空间的连续性,避免出现大量的空间碎片,但是它的空间利用率却不高,相当于只利用了50%的空间。
标记-压缩算法 (Mark-Compact)
在存活对象少,垃圾对象多的情况下,复制算法的效率很高,但是如果存活对象多并且对象较大,此时在使用复制算法,复制成本高,效率很低。
标记-压缩算法可以解决上述的问题,它是基于标记-清除算法进行改进的一种算法。在完成标记之后,标记-压缩算法并不会马上进入清除阶段,而是将存活对象压缩到内存的另一端,之后再将边界外的对象进行清除。
这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。
并行收集线程数
并行垃圾收集器的垃圾收集线程数与机器的CPU相关。当CPU小于8时,线程数与CPU的数量相等,当CPU大于8时,线程数等于5/8的CPU数
thread = N (N <= 8)
thread = 5/8 * N (N > 8)
可以使用-XX:ParallelGCThreads参数来指定线程数。
设置参数
并行垃圾收集器可以设置的参数有三点
- 最大停顿时间
- 吞吐量
- 堆大小
最大停顿时间
-XX:+MaxGCPauseMills:\
吞吐量
-XX:+GCTimeRatio:\
堆大小
-Xmx表示最大的堆内存,堆的大小也会影响垃圾收集。并行收集器会通过调整堆的大小来实现最大停顿时间以及吞吐量这两个目标。
heap调整
并行收集器会通过调整堆的大小来实现最大停顿时间以及吞吐量这两个目前。收集器通过一定的比例来扩大或缩小堆的大小,默认情况扩大的比例是20%,缩小的比例是5%。这两个比例可以通过参数来调整。
XX:YoungGenerationSizeIncrement=\
XX:TenuredGenerationSizeIncrement=\
XX:AdaptiveSizeDecrementScaleFactor=\
OutOfMemoryError
当花费在垃圾收集的时间过多时,收集器将抛出OutOfMemoryError的异常。默认情况下,如果超过98%的时间用于垃圾收集,而回收的堆大小小于2%,那么系统将抛出OutOfMemoryError。
之所以这样设置是为了避免系统花费了大量的时间用于垃圾收集,但是得到的收益却很小。可以用-XX:-UseGCOverheadLimit这个参数来关闭这个特性。
应用场景
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
并行收集器可以通过设置参数来设置对应的目标。