`
chenzhou123520
  • 浏览: 4259270 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JVM学习笔记(三):JVM的垃圾回收

阅读更多

注:本文根据《深入理解Java虚拟机》第3章部分内容整理而成。

 

 

一.如何判断对象是否需要回收?

 

堆中几乎放着java世界中的所有的对象实例,垃圾收集器在对堆进行回收前,第一件事就是要确定这些对象哪些还存活着,哪些已经死去(即不可能再被任何途径使用的对象)。而如何判断对象是否应该回收,存在两个算法:引用计数算法(Reference Counting)和根搜索算法(GC Roots Tracing 。但是Java语言中没有选用引用计数算法来管理内存,主要的原因是它很难解决对象之间的相互循环引用的问题。所以下面我们主要介绍根搜索算法。

       JavaC#中都是使用根搜索算法判定对象是否存活。算法基本思路就是通过一系列的称为“GC Roots”的点作为起始进行向下搜索,当从GC到某一个对象不可达的时候,也就是说一个对象到GC Roots没有任何引用链相连的时候,这个对象就会被判定为可回收的。

       java中,可作为GC Roots的对象包括下面几种:

1.虚拟机栈(栈帧中的本地变量表)中的引用的对象。

2.方法区中的类静态属性引用的对象。

3.方法区中的常量引用的对象。

4.本地方法栈中JNI(即一般说的Native方法)引用的对象。

 

根搜索算法的图示如下:

 

根搜索算法判定对象是否可回收

图:根搜索算法判定对象是否可回收

 

二.垃圾收集算法

 

1.标记-清除算法(Mark-Sweep

这是最基础的垃圾收集算法,算法分为标记清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。它的主要缺点有两个:一个是效率问题,标记和清除效率都不高;另一个是空间问题,标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致分配大对象时没有足够的大的连续空间,而不得不提前触发另一次垃圾收集动作。

 

标记-清除算法示意图

图:标记-清除算法示意图

 

2.复制算法(Copying

为了解决效率问题,有了复制的算法,他将可用内存分为大小相同两块。每次只用一块,当一块空间用完了,就将还存活的对象复制到另一块上,然后将刚使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。实现简单,运行高效。只是这种算法的代价是将内存缩小到原来的一半,代价太贵了点。实际上,新生代中的对象98%都是朝生夕死,所以不需要按11的比例来分内存,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中一块Survivior空间。当回收时,将EdenSurvivor中还存活的对象一次性的拷贝到另一块Suivivior中,最后清理掉Eden和刚用过的Survivor空间。

复制算法示意图

图:复制算法示意图

 

3.标记-整理(Mark-Compact

复制收集算法在对象存活率高的时候就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

    因此人们提出另外一种标记-整理Mark-Compact)算法由于老年代中的对象生存周期都较长,有人提出标记-整理算法,标记过程和标记-清理一样,但在清除已死对象的同时会对存活对象进行整理,这样可以减少碎片空间

 

标记-整理算法示意图

图:标记-整理算法示意图

 

4.分代收集

当前商业虚拟机的垃圾收集都是采用分代收集Generational Collecting)算法,这种算法并没有什么新的思想出现,只是根据对象不同的存活周期将内存划分为几块。一般是把Java堆分作新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就用复制算法,只要少量复制成本就可以完成收集。而老年代中因为对象的存活率较高、周期长,就用标记-整理标记-清除算法来回收。 

1
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics