JVM内存白皮书的翻译内容,结合了摘抄的内容,希望自己对JVM的内存和垃圾回收机制有更深入的理解。
资料来源:http://chaoticjava.com/posts/parallel-and-concurrent-garbage-collectors/
垃圾回收器主要负责找到并释放未被引用的对象。
所希望的垃圾回收器的特性
安全、高效:不会发生错误的回收,不会使得应用出现明显的卡顿现象
对碎片的控制:消除碎片的方法也称为compaction。
现在的GC都使用了代回收策略,所谓代回收,指的是内存被划分为generation,不同的generation存储不同年龄的对象。使用比较广泛的是两代划分:年轻代和老年代。
这个想法的基础就是弱年代假设:
1.大部分被分配的对象很早就死了;
2. 极少有从老年到少年的对象。
因为Java中绝大部分都是周期很短的对象,这些对象会初始存放在年轻代,年轻代的内存空间较小,且回收很频繁;而对于生命周期很长的对象则放在老年代,老年代的内存空间很大,且不经常回收,回收的耗时也会比较长。针对在不同的代中可以使用特定的垃圾回收算法。
J2SE 5.0 HotSpot JVM中的GC
内存被划分为了3代:年轻代、老年代和永久代。大部分对象初始分配在年轻代。老年代包含一些已经活了几个年轻代的对象,一些比较大的对象也可能直接分配在老年代。永久代存放的主要存放加载的Class类级对象如class本身,method,field等等,默认的空间大小4M。
年轻代包含一个Eden和2个survivor(from,to)空间。大部分的对喜爱那个直接分配在Eden。而survivor主要存放已经生存了超过至少一个代周期的对象,survivor内的对象在被放到老年代之前,会有机会被销毁。在任意时刻,survivor中必有一个用来存储,一个是空的。
垃圾回收类型
当年轻代被填满时,将执行年轻代回收(也称为minor回收);
当老年代或永久代被填满后,full collection(也成为major回收)将会被执行。这种情况下,是所有的代都会被回收。一般来说,会先回收年轻代,然后回收老年代和永久代(在特定的代会使用特定的回收策略),如果支持压缩,每个代各自完成压缩。
有时候由于要先回收年轻代,而老年代过满,而无法接受从年轻代传过来的对象。在这种情况下,除了CMS回收器,年轻代的回收算法将不会执行。老年代的回收算法将在整个堆上应用。(CMS算法会尽力避免发生这种情况)
为对象快速分配内存
在单线程情况下,只需要根据堆当前已使用内存数量的指针即可快速为对象分配内存。
在多线程情况下,这种分配方式就存在很大的性能问题。因此jvm使用了Thread Local Allocaiton BufferS机制,为每个线程分配了属于自己的buffer
串行回收器
在串行回收中,年轻代和老年代都是串行回收的,执行的是stop-the-world形式,也就是当回收发生的时候,应用执行就会被中止。
年轻代回收(使用串行回收器)
Eden区域的活跃对象将被复制到一开始为空的survivor(标为to)区,除了一些特别大的数据就会直接移到老年代。在另一个survivor(标为from)的对象也将被复制到这个survivor。Note:如果to满了,无论eden和from中的对象活了多久,都会直接放到老年代。在Eden和from中的活跃对象被移走后,剩下的就是垃圾对象了,如下图的x。
在一次年轻代被回收完成后,eden和之前被占用的from就会被清空。如下图
老年代回收(使用串行回收器)
使用串行回收器,老年代和永久区通过mark-sweep-compact回收算法进行回收。在标记阶段,回收器将标识出活的对象。在清除阶段,收集器会在代上扫,识别出垃圾。然后收集器将活的对象全部移动到老年代的开始处,如下图
一般在单机上,对应用暂停时间没有高要求的客户机。在一般非服务器上的jvm都是使用的串行收集器,同样可以显示的要求使用串行回收器,-XX:+UseSerialGC。
并行收集器
年轻代(使用并行收集器)
并行的收集器使用的收集算法和串行收集器的并行版本,但因为并行收集器更好的利用了多个cpu,使得应用被迫暂停的时间大大缩短。
具体的技术细节如下:
老年代(使用并行收集器)
和串行收集器相同也是使用了串行的 mark-sweep-compact 的收集方式。一般来说,在服务器上jvm都是自动跑的并行收集器,同样可以使用-XX:+UseParallelGC.
并行压缩收集器
它和并行收集器的一个中药的区别在于它对老年代的垃圾回收使用了新的算法。并行压缩收集器将替代并行收集器。
年轻代(使用并行压缩收集器)收集器使用了3阶段。1,每代在逻辑上被划分成固定大小的区域。在marking阶段,所有的活的对象被并行划分和标记。当一个对象被标记为活的时候,它所在的区域就会更新关于这个对象的大小和位置的信息。summary阶段是针对region来说的。一般来说,如果一个区域本身就存在大量的活对象,密集度很高,就无需再进行压缩。所有summary阶段首先会检查这些区域的密集度,它从最左开始扫,直到某处的右边值得压缩(即某处右边比较稀疏)为止。该处左边的部分就称为密集区域,没有对象会被移动到那里。而该处右边部分将被压缩,消除掉所有的存有死对象。summary阶段计算了并存储了每个区域的压缩点的开始位置。
在压缩阶段,利用summary阶段的数据来识别出哪个region需要被填充,线程可以独立的复制数据到这些区域里。如果想使用并行压缩收集器的话可以-XX:+UseParallelOldGC。
一个具体的实现细节如下:首先将老年代划分为几个region(一般根据GC的线程数进行划分),然后并行的标记更新每个region的活跃对象信息;确定好dense prefix,也就是确定了需要压缩的区域;而且在summary阶段,收集器可以知道每个区域所剩余的空间,从而确定将哪些区域活跃对象填充到某些区域去(压缩),一般是选择右边的区域作为源区域,左边的区域作为目标区域(保持左边的密集度高于右边);由于每个thread负责各自的区域,除了作为源区域的线程不进行sweep操作外,其他的线程各自进行sweep,清除垃圾;当目标区域的thread处理好目标区域后,会立即将源区域的活跃对象填入目标区域,然后清理源区域。
并发的mark-sweep(CMS)收集器
该收集器也以低延迟收集器著称。