1.垃圾回收器期职责
- 开辟空间
- 任何引用可达的对象都在内存内
- 回收不再使用的内存
3.垃圾回收器概念
3.1.垃圾回收器期望的性能
- 垃圾回收器必须安全,存活的对象不应该被释放,应该释放的对象存活的时间应该在很小的回收循环内.
- 垃圾回收器必须高效,不应该让应用长时间停顿.根据三个方面来衡量,时间,空间,频率.
堆太小,容易被填满,垃圾回收搜集的频率高,回收速度快。
堆太大,不容易填满,垃圾回收的频率低,回收速度慢。
- 生成的空间碎片少.
- 可伸缩,垃圾回收器不应该成为应用程序瓶颈.
3.2.可选的设计方案
1.串行回收器 VS 并行回收器
串行回收器不能更好的利用多核CPU,只能使用一个核.
并行回收器可以把任务拆分,更好的利用多个核完成任务.但是会增加实现的复杂性和内存碎片.
2.并发 VS 停止应用程序
停止应用程序类型的垃圾回收器工作时,让应用程序停止,垃圾回收工作完毕后应用程序继续执行.
并发型垃圾搜集器也需要让应用程序停顿一小会.停止应用程序类型的垃圾回收器实现比并发回收器简单,因为它工作时.
堆内容固定不变,并发回收器让应用停止时间比停止应用程序类型回收器,但是它需要考虑在回收时应用程序同时更新的对象,
同样,它需要大的堆内存.
3.压缩 VS 非压缩 VS 复制
在垃圾回收器确定哪些对象是垃圾时,此时可以压缩内存,移动所有存活的对象然后重新计算剩余内存大小.在压缩后,更容易更快为对象开辟内存空间.
对于非压缩行为,回收器直接在对象原始位置释放内存,不移动所有存活对象,然后重新计算剩余内存大小.但是非压缩回收器能更快的完毕回收工作,副效应就是
带来较多的内存碎片.通常,在释放对象的原始位置开辟需要的空间比在压缩后的空间中开辟要昂贵.它需要搜索整个内存空间,直到找到一块连续并且大小合适的内存.
第三种可替换的搜集器是复制搜集器,它复制存活对象至一块不同的内存区域.好处就是,源内存可以直接当做是空内存,能够更快的用于下次开辟空间,但是副效应就是
复制对象需要时间,还需要其他的内存空间.
3.3.测量性能
吞吐量:未花费在垃圾回收上的时间百分比,需要长期考虑. (not use in gc)/t
垃圾回收时间:和吞吐量相反,所有花费在垃圾回收上的时间. (use in gc)/t
停顿时间:发生垃圾回收时应用程序停顿时间.
垃圾回收频率:多久发生一次垃圾回收,与正在执行的应用程序相关.
占用的空间:可测量的大小,比如堆大小.
速度:成为垃圾的对象到对象占用的空间变得可用之间的时间.
3.4.按代搜集
使用按代搜集时,内存区域被划分成多个代.比较广泛的分代方法是,对于年轻对象放一个代,年老的对象放一个代.
对于不同的代使用不同回收算法.它们依据以下理论.
1.许多对象不会长时间被引用,在年轻时它们就会死掉.
2.年老对象很少引用年轻对象.
年轻代垃圾回收器发生回收行为频繁,回收行为高效并且快,因为年轻代通常比较小,包含许多没有被引用的对象.
年轻代中的对象经过多次回收行为,如果还活着,就转移到年老代,年老代的内存通常大于年轻代,并且增加速度比年轻代慢.
所以,对于年老代的回收行为不频繁,但是需要长时间才能完成.
为年轻代选择垃圾回收器主要考虑的费用是速度,因为年轻代搜集行为频繁.另外来说,年老代回收算法在空间管理上更加高效,因为年老代内存大小
比年轻代大,而且年老代算法需要在垃圾密度很低的情况下工作的很好.
5.J2SE5.0 HotSpot JVM中垃圾回收器
J2SE5.0 HotSpot中所有回收器都是按代的回收器.这个章节描述回收器类型和代,然后讨论为什么为对象开辟空间速度很快,很高效.然后提供更多关于回收器的详细信息.
5.1.HotSpot中对象的代
HotSpot虚拟机中的代包括年轻代,年老代,持久代.许多对象在年轻代中开辟和初始化.年老代中保存经过多次回收后还幸存的对象,当然一些大的对象可能之间在
年老代中开辟空间.持久代保存那些查找方便的对象(用于JVM垃圾搜集),比如对象和方法描述信息,同样也包括类和方法本身.
年轻代由Eden区域和俩块小的幸存区组成,如图2所示。大多数对象在Eden中开辟空间然后初始化.(正如上面提到的,少数对象可能直接在年老代开辟空间)。幸存区保存年轻代中至少经历过一次回收的对象,但是在变成年老代之前就会死掉的对象.在一个任意的给定的时间点,这些对象保存在幸存区中的某一个区域内(图中指示的是From区),幸存区中的未保存对象的另外一块区域保存为空直到下一次垃圾搜集.
5.2.垃圾搜集器类型
当年轻代被填满时,年轻代搜集器在年轻代上执行(有适合也叫做minor collection).当年老代或者持久代被填满时,执行一次full collection(有时候也叫做major collection).在这种
情况下,所有的代都被搜集,通常,年轻代首先被收集.使用针对年代的算法搜集垃圾,因为这个算法在年轻代中标识垃圾具有很高的效率.然后在年老代和持久代上运行特定的垃圾搜集器.如果指定了压缩,每个代都被单独的压缩.
如果年轻代先搜集.有时候会造成年轻代提升太多的对象到年老代,导致年老代太过于饱和.在这种情况下,所有的搜集器(除了CMS搜集器)在年轻代上都不运行.
相反,年老代垃圾搜集算法在所有代上使用(CMS年老代算法是比较特殊,因为它不能搜集年轻代)
5.3.快速开辟空间
正如你在以下看到的那样,在许多情况下都有一大片连续的内存块用于给对象开辟内存,从这样块开辟空间的速度非常高效,使用一种比较简单的技术,名字叫做bump-the-pointer,先前开辟的最后一个对象通常需要保存起来.当一个新的开辟空间请求需要被满足时,需要检查代中剩余的空间是否对申请合适,如果合适,更新最后一个对象,然后对象初始化.
对于多线程应用程序,开辟空间操作需要多线程安全.如果使用一个全局锁来保证安全,在代中开辟空间的行为会成为系统瓶颈,然后拉低性能.相反,HotSpot JVM采用一个叫做
Thread-Local Alloction Buffers(TLABs)的技术.它能提升多线程开辟空间的吞吐量(每个线程在自己的buffer里面开辟空间,buffer是代的一小部分).在每个TLAB里面既然只有一个线程开辟空间,此时可以使用bump-the-pointer来快速开辟空间,而不需要偶外的锁.在线程填满自己的TLAB的时候需要获取一个新的TLAB,这种是发生的频率很低,但是需要同步实现.多种技术由于使用TLAB让空间浪费变成很小.比如,平均下来,TLABs可以让内存分配器浪费的内存小于Eden的1%.使用TLABs+bump-the-pointer技术可以让开辟非常高效,仅需要差不多10条原生的指令.
5.4.串行搜集器
使用串行搜集器,年轻代搜集和年老代搜集都是串行的(使用一个CPU).使用stop-the-world风格.这样,当垃圾搜集器进行时应用程序执行被强制停止.
在年轻代中使用串行搜集器
图3指出来在年轻代中使用串行搜集器.Eden内存活的对象被复制到幸存区内(图内标记为To的那块),除了那些太大而不适合To空间的对象,这些对象直接被复制到年老代.
在From区域内存活的对象同样被复制到其他区域(To),如果对象太老,直接复制到年老代.(注意:如果To空间已经满了,从Eden或者From来的存活对象会被直接复制到年老代
,此时不管此对象在年轻代搜集器下幸存过多少次).在Eden或者From区域内未被复制走的对象都是未活着的对象,它们不需要被再次测试是否存活.(图内标记成X的那些对象)
在年轻代搜集器完成后,所有Eden和幸存区内某一块全是空的(幸存区内另外一块空间保存着存活的对象).此时,幸存区内俩个空间交换.具体查看图4.
在年老代中使用串行搜集器
在年老代和持久代中串行搜集器使用mark-sweep-compact算法.在标记阶段,搜集器标示那些还存活的对象,在扫除阶段,清理区域中所有的垃圾,然后搜集器执行滑动压缩.
滑动所有存活对象区去年老代开始地方(在持久代中一样),与其相对的地方都是连续空闲的块.具体查看图5.压缩使得以后的开辟行为使用bump-the-pointer开辟空间很快.
何时使用串行搜集器
运行在client-style的虚拟机适合使用串行垃圾搜集器,并且应用程序不需要低停顿的时间.在当前的硬件环境下,串行搜集器能够高效的管理许多队空间为64MB的应用程序
并且全量搜集最坏情况下的停顿时间小于半秒.
使用串行搜集器
在J2SE5.0的发行版中,非服务器类机器上默认选择串行垃圾搜集器,正如Section 5内描述的那样.在其他机器上,可以通过指定-XX:UseSerialGC命令行参数来显式指定.