首页 > JAVA > Java内存管理之基础概念——HotSpot垃圾回收器

Java内存管理之基础概念——HotSpot垃圾回收器

2009年7月28日 admin 发表评论 阅读评论

这是Java内存管理系列文章的第三篇了,前面两篇分别是Java内存管理之基础概念——GC(Garbage Collection)的基本概念Java内存管理之基础概念——Hotspot的分代回收(Generational Collection)。这次的主要介绍的HotSpot提供的几种垃圾收集器,下面正式开始本次的内容。

垃圾回收的种类

当新生代被占用满了,就会运行新生代的垃圾回收(也叫做次要回收)。当永生代或老生代满了以后,就会进行全回收(也叫做主要回收),也就是说所有的代都会被收集。通常对新生代会先被收集,而且使用的收集算法也是特别针对新生代的。然会都对老生代和永生代也进行收集。如果需要进行内存的压缩,每个代都独立地进行压缩。

当出现老生代内容太多,不再能容纳由新生代提升到老生代的内容的情况,所有的收集器(除了CMS收集器)都不再运行新生代的回收算法,相反的,老生代的收集算法会用在整个heap中。CMS收集器由于不回收新生代,所用是个特列。

快速分配

在很多情况下系统中有大量的连续的内存块可以用来分配对象,这种情况下使用bump-the-pointer算法来给对象分配内存是高效的。这种算法会记录前次分配对象的末尾,当有新的对象要分配的时候就会先检查剩余的空间是否内容满足对象的分配,如果可以的话,就会更新指针并且初始化对象。

在一个对线程的应用中,分配操作需要时线程安全的,如果通过全局锁的方式来保证线程安排的话,内存的分配就会成为瓶颈。所以HotSpot采用的是Thread-Local Allocation Buffers (TLABs)。每个thread都会有它自己的buffer,例如代中的一小块。每个TLAB都只有一个thread可以操作,TLAB结合bump-the-pointer技术可以实现快速的对象分配,而不需要任何的lock。只有少数不平凡的情况,thread使用的TLAB已经满了需要新的TLAB时才会需要同步操作。

串行收集器

串行收集器对新生代和旧生代的回收都是串行的(只使用一个CPU),而且垃圾回收执行期间应用程序的执行会暂停。

新生代的串行回收

2

上面这张图说明了新生代串行回收的过程。Eden中的活动对象会拷贝到初始为空的Survior空间,也就是下图中的To Survior空间,有些特别巨大的对象无法放入到To Survior空间中会被直接拷贝到老生代中,而Form Survior空间中的活动对象如果想对比较新的话就也会被拷贝到To Survior空间中,而想对比较老的话就会拷贝到老生代中。如果To Survior空间已经满了,Eden和Form Survior空间中的活动对象都会被保留下来。Eden和Form Survior空间中的活动对象在被拷贝以后就都是非活动的状态(下图中红色X标识的内容)。新生代回收以后Eden和Form Survior空间都会被清空,只有原本空的To空间保存了活动对象。执行以后的效果应该就是下面右边那张图。

1 

老生代的串行回收

对老生代和永生代的回收算法是mark-sweep-compact。这个算法有3个阶段,mark(标识回收对象),sweep(清除),compact(压缩)。在mark阶段收集器会识别出哪些对象仍然是活动的,在sweep阶段就会对垃圾对象进行回收,在compact阶段收集器执行sliding compaction,把活动对象往老生代(永生代也一样)的前端启动,而在尾部保留一块连续的空间,方便在给新对象分配空间的时候执行bump-the-pointer。下面这张图说明的就是老生代在执行垃圾回收前后的状况。

3

串行回收器的启用

在命令行中加入参数-XX:+UseSerialGC

并行回收器

并行回收器充分利用计算机的CPU提高吞吐量。

对新生代的并行回收

新生代的并行回收算法和串行回收器是一样的,只是增加了并行的能力,新生代的并行回收器仍然是stop-the-world和coping收集器,但通过在多个cpu中并发运行,降低了GC的开销并提升了应用程序的吞吐量。

4

老生代的并行回收

老生代的并行回收使用的也是串行的mark-sweepcompact回收算法,特别注意的是并行回收器对老生代的回收并没有并行处理的能力,也就是说并行回收器只对新生代并行回收。

并行回收器的启用

在命令行中加入参数-XX:+UseParallelGC

并行压缩回收器(Parallel Compacting Collector)

Parallel Compacting Collector是在J2SE 5.0 update 6中引入的,跟并行收集器最大的不同是对老生代的回收使用了不同的算法,并行压缩回收器最终会取代并行收集器。

新生代的并行压缩回收

并行压缩回收器对新生代的回收算法跟并行回收器是一样的。

老生代的并行压缩回收

并行压缩回收器同样还是会引起stop-the-world效应,并行主要是体现在sliding compaction上。收集器使用了3个阶段,首先每个代在逻辑上都分配成几个固定大小的region(区域)。在Marking阶段,在应用程序中可以直接引用到的活动对象被多个GC线程所划分掉,然后所有的活动对象就可以以并行地方式来实现对它们的标注。当某个Object被鉴定为活动对象的时候,就会更新这个对象所在区块的大小以及该对象位置的信息。

接下来是summary阶段,summary阶段操作的region而不是对象了。由于每次的GC的压缩都会每一个代的左边的部分区域活动对象密度特别高,保存了多数活动对象。对这样的高密度活动对象的区域进行压缩往往不划算。所以在summary阶段会从最左边的区域开始检验每个区域的密度,当进行到某个区域中能回收的空间达到了某个数值的时候,那么收集器会判定该区域以及该区域右边的那些区域都是值得进行回收的。该区域左边的区域都会被标识成密集,不会有对象移动到这些密集区域去,而该区域和右边的区域之后都会被进行压缩,回收空间的操作。在summary阶段会计算和保存每个活动对象在每个压缩区域的第一个字节的新位置。summary阶段目前还是串行操作,虽然并行是可以实现的,但重要性不如对marking和压缩阶段的并行来的重要。

最后的压缩阶段,回收器利用summary阶段生成的数据识别出有哪些区域是需要装填的,每个GC线程就可以独立的将数据拷贝到这些区域中。这个过程就会heap在一端很密集而另一端存在大块的空闲块。

并行压缩回收器适合运行在多个CPU的机器上,而且较之并行回收器增强了对老生代的并行回收,减少了系统停顿的时间。

并行压缩回收器的启用

在命令行中加入参数-XX:+UseParallelOldGC

还可以限制并行回收线程的数量–XX:ParallelGCThreads=n

并发Mark-Sweep(标识-清理)收集器(CMS)

很多应用中更注重快速的相应时间而不是end-to-end的处理能力。对新生代的回收通常不会造成长时间的应用程序中断,而老生代特别当Heap比较大的时候会导致长时间的中断。Hotspot引入CMS的目的就是为了解决这个问题。

CMS的新生代回收

收集的方式和并行回收一致。

CMS的老生代回收

CMS对老生代的回收多数是并发操作。垃圾收集循环开始的时候需要一个短暂的暂停,称之为初始标识(initial mark ),这个阶段会识别出那些直接被引用的活动对象。然后进入了并发标识阶段(concurrent marking phase),收集器会依据在初始标识中发现的活动对象来寻找活动对象。这个时候应用程序也同时在运行,那就无法保证所有的活动对象都会被识别、标注出来。于是应用程序会再次被暂停,在这个再标识(remark)阶段,收集器会访问在并发标识阶段中被修改过的对象并完成标志。由于再标识阶段较之初始标识更重要,所以会并发运行对个线程来提升效率。

在完成了再标识以后,所有的活动对象都已经被标注出来了。接下来就可以运行并发清理阶段。

5 

另外就是CMS是不进行内存压缩的。对象回收以后就会,收集器不会移动活动对象,执行完回收的内存情况就会使下面这张图的效果。

6

而且由于空闲空间是不连写的,收集器就必须要保存一份可用空间的列表。当需要分配对象的时候,收集器就要通过这份列表找到足够容纳新对象的空间,这就内存分配算法的效果肯定要比之前介绍的几种收集器使用的bump-the-pointer算法性能差。由于老生代的分配效率差了也就影响了新生代回收过程中需要将新生代对象移到老生代的效率。

另外,CMS较之前的几种回收器需要更大的Heap,原因是在标志过程中,应用程序同时在运行,同时在分配对象,因此老生代也同时在增长。此外,虽然活动对象在标示阶段都会被识别出来,但有些在标示阶段成为垃圾的对象并不能同时被回收,只有等到下次收集的时候才能被回收。

最后,由于没有压缩,所以就容易出现内存碎片。为了解决这个问题,CMS会分析通常对象的大小来预估下一步可能的需求,然后可能会对空闲的内存块分割或合并。CMS不会等到老生代满的时候才运行内存回收,而是提早到在老生代满之前就完成内存回收工作。所以CMS会利用之前内存回收的统计数据(收集所需要的时间、老生代被沾满的时间等等)然后选择一个开始回收的时间。CMS在老生代的占用率到达某个阀值的时候也会进行回收。这个阀值可以通过

–XX:CMSInitiatingOccupancyFraction=n来定义,缺省值是68.

总之,CMS较之并发收集器以某些时候稍微增加新生代的回收时间、增加Heap的使用量、减少一些吞吐量为代价减少了老生代回收过程中的停顿时间,而且CMS会要求在应用程序运行过和收集器分享处理器资源。对那些会产生比较大老生代的应用程序而言,如果运行在多处理器上,CMS是一个不错的选择。

CMS回收器的启用

在命令行行中增加-XX:+UseConcMarkSweepGC 启用CMS。

增加–XX:+CMSIncrementalMode会然CMS运行在增量模式。所谓的增量模式指的是把收集器的工作分成多个时间块,然后在两次新生代的回收期间加以运行,这种方式可以更进一步减少暂停的时间。

分类: JAVA 标签:
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.