垃圾回收算法
垃圾回收的意义:
Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄漏,有效的使用空闲的内存。
任何一种垃圾回收算法一般要做2件基本的事情:(1)发现无用信息对象;(2)回收被无用对象占用的内存空间,使该空间可被程序再次使用。
对象存活判定算法:
引用计数算法:每当一个地方引用一个对象,计数器就加1,引用失效的时候,计数器就减1。当计数器为0的时候就表示对象不会再被使用。
缺点:难以解决对象之间相互循环引用的问题。
可达性分析算法(主流):用过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,
当一个对象到GC Roots没有任何引用链相连时,则此对象是不可用的。
在 Java 语言里,可作为 GC Roots 的对象包括下面几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中的类静态属性引用的对象。
方法区中的常量引用的对象。
本地方法栈中 JNI(Native 方法)的引用对象。
不过那些发现不能到达 GC Roots 的对象并不会立即回收,在真正回收之前,对象至少要被标记两次。当第一次被发现不可达时,该对象会被标记一次,同时调用此对象的 finalize()方法(如果有);在第二次被发现不可达后,对象被回收。利用 finalisze() 方法,对象可以逃离一次被回收的命运,但是只有一次。逃命方法如下,只要在 finalize()方法中让该对象重引用链上的任何一个对象建立关联即可。而如果对象这时还没有关联到任何链上的引用,那它就会被回收掉。
垃圾回收算法:
标记-清除算法:
标记阶段:首先从根对象开始进行遍历,对从根对象可以访问到的对象都打上一个标记。
清除阶段:对堆内存从头到尾进行线性的遍历,如果发现某个对象没有标记为可达对象,则就将其回收。
缺点:效率不高。标记清除后会产生大量不连续的内存碎片。空间碎片太多会导致以后分配较大对象时,需要提前触发一次垃圾回收。
复制算法:用于回收新生代。将一块内存上还存活的对象复制到另一块上,再一次清除之前的那块。
通常用一块较大的Eden空间,和两块较小的Survivor空间。每次使用 Eden 和其中的一块 Survivor。
当回收时,将 Eden 和 Survivor 中还存活的对象一次性拷贝到另外一块 Survivor 空间上,最后清理掉 Eden 和刚才用过的 Survivor 空间。
Hotspot 虚拟机默认 Eden 和 Survivor 的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80% + 10%),只有10%的内存是会被“浪费”的。
缺点:存在空间浪费。
标记-整理算法:用于回收老年代。标记后,让存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
缺点:GC暂停时间增长。
分代收集算法:将java堆分为新生代和老年代,新生代只有少量存活,采用复制算法。而老年代有大批存活,所以采用标记-整理算法或者标记-清除算法。
增量回收算法:如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。
每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。
缺点:使用这种方式,由于在垃圾回收过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。
但是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。