关于JVM的GC机制

GC优点:

1.提高生产率,不用逐行检查内存是否释放。

2.Java安全策略的一部分,不会使用户错误释放内存而导致JVM崩溃。

 

GC算法基本两点:

1.检测出垃圾对象。

2.回收垃圾对象,释放相应堆空间。

垃圾检测一般是先建立一个根对象集合,其它对象要是从根对象起可触及就是活的,无法到达的就是垃圾,如下图

栈是真正进行程序执行地方,所以要获取哪些对象正在被使用,则需要从 Java栈开始。同时,一个栈是与一个线程对应的,因此,如果有多 个线程的话,则必须对这些线程对应的所有的栈进行检查。同时,除了栈外,还有系统运行时的寄存器等,也是存储程序运行数据的。这样,以栈或寄存器中的引用为起点,我们可以找到堆中的 对象,又从这些对象找到对堆中其他对象的引用,这种引用逐步扩展,最终以null引用或者基本类型结束,这样就形成了一颗以Java栈中引 用所对应的对象为根节点的一颗对象树,如果栈中有多个引用,则最终会形成多颗对象树。在这些对象树上的对象,都是当前系统运行所需 要的对象,不能被垃圾回收。而其他剩余对象,则可以视为无法被引用到的对象,可以被当做垃圾进行回收。

 

区别活动对象和垃圾的两个基本方法是引用计数和跟踪

引用计数:GC的早期策略,队中每个对象都有一个引用计数器,当对象创建并赋给一个变量时,引用计数为1.每次赋给别的变量时,引用计数加1。当对象的引用超过了生存期或指向到了新值,对象引用减1。为0时就是垃圾,可清楚。但是该方法对多个对象的循环引用无能为力,此外还有引用数的增减带来额外开销。现在该计数已经不为人所接受了。

跟踪收集器:跟踪收集追踪从根节点开始的对象引用图,基本的追踪算法叫“标记并清除”,也就是GC的两个阶段。标记阶段,垃圾收集器遍历引用树,标记每一个遇到的对象。清除阶段,未被标记的对象被释放。可能在对象本身设置标记,要么就是用一个独立的位图来设置标记。

 

标记和清除通常使用两种策略来消除堆碎片:压缩和拷贝,这两种方法都是快速移动对象来减小碎片。

 

压缩收集器:

压缩收集器把活动对象越过空闲区滑到堆的一堆,留下另一端的大的连续空闲块。被移动的对象的引用也被更新,指向新的位置。更新被移动对象的引用有时通过一个间接对象引用层来实现的,对象的引用不实际指向堆中对象,而是指向一个对象句柄表(由它完成对象引用到堆中对象的实际位置的映射),对象被移动了,只需要更新对象句柄表的句柄值,这样程序中的对象引用不变。这种方法简化了消除堆碎片的工作,但是每次对象访问都要查一下映射表,带来了性能上的损失。

 

拷贝收集器:

拷贝收集器把所有的活动对象移动到一个新的区域。这种方法在追踪对象过程中随着发现而被拷贝,不再有标记和清除的区分。一般的拷贝收集算法称为“停止并拷贝”。在这个方案中,堆被分为两个区域,任何时候只使用其中一个区域。对象在某一个区域中分配,直到这个区域被耗尽时,程序执行停止,遍历这个区域,活动对象移到另一个区域,完成后程序恢复执行,对象在新的区域分配。原来的区域剩下垃圾,全清除。直到新的区域耗尽时,程序停止,活动对象又往回移,循环工作。这种方法的代价就是堆内存只能使用到一半。

 

停止拷贝收集器的缺点:

每次收集时,所有的活动对象都要移动来移动去。对于短生命的对象还好说,经常可以就地解决掉,可是对于长生命周期的对象就纯粹是个体力劳动了,把它挪来挪去除消耗大量的时间,没有产生任何效益。分代收集能直接让长生命周期的对象长时间的呆在一个地方按兵不动。GC 的精力可以更多的花在收集短命对象上。

 

分代收集器:

分代收集能直接让长生命周期的对象长时间的呆在一个地方按兵不动。GC 的精力可以更多的花在收集短命对象上。这种方法里,堆被分成两个或更多的子堆,每一个堆为一“代”对象服务。最年幼的那一代进行最频繁的垃圾收集。因为多数对象是短命的,只有很小部分的年幼对象可以在经历第一次收集后还存活。如果一个最年幼的对象经历了好几次垃圾收集后仍是活着的,那这个对象就成为寿命更高的一代,它被转移到另外一个子堆中去。年龄更高一代的收集没有年轻一代来得频繁。每当对象在所属的年龄代中变得成熟(多次垃圾收集后仍幸存)之后,就可以转移到更高年龄的一代中去。分代收集除了可应用于拷贝算法,也可以应用于标记清除算法。不管在哪种情况下,把堆按照对象年龄分组可以提高最基本的垃圾收集的性能。

 

GC的一般缺点:

垃圾收集一般都会停止整个程序的运行来查找和收集垃圾对象,它们可能在程序执行的任意时刻暂停,并且暂停的时间也无法确定。垃圾收集也可能使得程序对事件响应迟钝,无法满足实时系统的要求。如果一种垃圾收集算法可能导致用户可察觉的到的停顿或者使得程序无法适合实时系统的要求,这种算法被称作破坏性。垃圾收集算法的还有一个基本目标是使本质上的破坏性尽可能少,如果可能的话,尽可能消除这种破坏性。达到(或试图达到) 非破坏性垃圾收集的方法是使用渐进式收集算法。渐进式收集器就是不试图一次性发现并回收所有不可触及的对象,而是每次发现并回收一部分。因为每次只有堆的 一部分执行垃圾收集,因此理论上说每次收集会持续更短的时间。保证这个最短时间接近某一个时长,就可以让 Java 虚拟机适合实时环境,也就可以消除用户可察觉的垃圾收集停顿,这可称之为限时渐近时垃圾收集。渐进式收集器通常是分代收集的,大部分调用中,都是收集堆的一部分。大部分对象都是短命的,利用这一点,分代收集器在年幼子堆中比在年长子堆中更活跃。因 为除了最高寿的子堆(成熟对象空间) 外,每个子堆中都可以给定一个最大尺寸,分代收集器大体上可以保证在一个最大时间内渐进地收集所有对象(最高寿的除外)。成熟对象空间无法给定最大尺寸, 因为其中的对象不适合时没个去处。

 

火车算法(转自http://blog.csdn.net/zouxinfox/article/details/1594216):

在火车算法中,内存被分为块,多个块组成一个集合。为了形象化,一节车厢代表一个块,一列火车代表一个集合如下图一

注意每个车厢大小相等,但每个火车包含的车厢数不一定相等。垃圾收集以车厢为单位,收集顺序依次为1.11.21.31.42.12.22.33.13.23.3。这个顺序也是块被创建的先后顺序。

垃圾收集器先从块1.1开始扫描直到1.4,如果火车1四个块中的所有对象没有被火车2和火车3的对象引用,而只有火车1内部的对象相互引用,则整个火车1都是垃圾,可以被回收。

 

图二,车厢1.1中有对象A和对象B1.3中有对象C1.4中有对象D车厢2.2中有对象E,车厢3.3中有对象F。在火车1中,对象C引用对象A,对象B引用对象D,可见,火车2和火车3没有引用火车1的对象,则整个火车1都是垃圾。

 

 

如果火车1中有对象被其它火车引用,见图三,扫描车厢1.1时发现对象A被火车2中的E引用,则将对象A从车厢1.1转移到车厢2.2,然后扫描A引用的对象D,把D也转移到车厢2.2,然后扫描D,看D是否引用其它对象,如果引用了其它对象则也要转移,依次类推。扫描完火车1的所有对象后,剩下的没有转移的对象都是垃圾,可以把整个火车1都作为垃圾回收。注意如果在转移时,如果车厢2.2空间满了,则要在火车2末尾开辟新的车厢2.4,将新转移的对象都放到2.4,即火车的尾部)

 

补充说明:垃圾回收器一次只扫描一个车厢。图三中的对象B与C并不是立即被回收,而是先会被转移到火车1的尾部车厢。即扫描完1.1后,B被转移到火车1尾部,扫描完1.3后,C被转移到车尾。等垃圾收集器扫描到火车1尾部时,如果仍然没有外部对象引用它们,则B和C会被收集。

火车算法最大的好处是它可以保证大的循环结构可以被完全收集,因为成为垃圾的循环结构中的对象,无论多大,都会被移入同一列火车,最终一起被收集。还有一个好处是这种算法在大多数情况下可以保证一次垃圾收集所耗时间在一定限度之内,因为一次垃圾回收只收集一个车厢,而车厢的大小是有限度的。

 

finalize() 方法:

一个 Java 对象可以拥用终结方法:这个方法在垃圾收集器释放对象之前必须运行。但这么一个终结方法的引入并不明智,会使得 JVM 的垃圾收集的工作变得更得杂,所以别用它。

因为,存在终结方法时,垃圾收集器必须在每次在收集时执行一些额外的步骤。首先第一遍扫描时,垃圾收集器检测出不再被引用的对象,然后看那些对象上 是否声明了终结方法,有则执行。当执行了所有的终结方法后,垃圾收集器必须再次从根节点或是需要执行终结的对象开始检测不再被引用的对象,这称作第二遍扫 描。这个步骤是必要的,因为终结方法可能“复活”了第一遍扫描标记的对象。最后,垃圾收集器才能释放那些在第一遍和第二遍扫描中发现的都没有被引用的对 象。

如果第一遍扫描标记不再引用的对象的终结方法运行过了,而后这个对象被自己或其他对象的终结方法复活了,稍后再次被收集时,执行过的终结方法就不能再执行了。同时终结方法何时被执行也是无法预测的。

 

JVM堆中对象状态:

1.强可触及 -- 即原来的可触及,从根节点开始的任何直接引用,如一个局部变量或任何从强可触及对象的实例引用的对象

2.软可触及 -- 表现为 SoftReference 所引用的对象

3.弱可触及 -- 表现为 WeakReference 所引用的对象

4.影子可触及 -- 表现为 PhantomReference 所引用的对象

SoftReference、WeakReference、PhantomReference 都是 java.lang.ref.Reference 类的子类。强引用与这三种弱引用之间最基本的差别是,强引用禁止引用目标被垃圾收集,而那三种引用不禁止。

要创建某一对象的软引用、弱引用或是影子引用,只需简单的包装一下。例如,创建一个 cow 对象的软用就写成:

SoftReference softCow = new SoftReference(cow);  //对于 WeakReference 和 PhantomReference 都是一样的。这里 softCow 是一个强引用,从 softCow 到 cow 是一个软引用,也就预示着垃圾收集器从根节点开始只能通过一个软引用才能触及到这个 cow 对象。要切断到 cow 的软引用,使之不再软可触及,可调用 softCow.clear(),要获取 cow 对象用 softCow.get()。

可触及性状态的变化

引入三个这样的引用对于虚拟机是有用处的,垃圾收集器对强引用对象是不能肆意妄为,但是它可随意更改百强可触及对象的可触性状态。在软引用、弱引用或者影子引用指向对象的可触及状态被垃圾收集器改变时,你可以获得这变化发生的通知,方法是要把引用对象和引用队列关联起来。

引用队列是 java.lang.ref.ReferenceQueue 类的实例,垃圾收集器在改变可触及性状态时会把所涉及的引用对象编入到队列中。你只要设置并观察引用队列,便可异步得到通知了

 

转:http://www.bianceng.cn/Programming/Java/JVM_1.htm

 

posted on 2015-06-18 20:28  颓废的悠然  阅读(580)  评论(0编辑  收藏  举报

导航