上篇文章介绍了JVM内存模型的相关知识,其实还有些内容能够更深刻的介绍下,好比运行时常量池的动态插入,直接内存等,后期抽空再完善下上篇博客,今天来介绍下JVM中的一些垃圾回收策略。html
1、finailize()方法 java
在介绍GC策略前,先介绍下GC中的finailize方法。当对象没有任何引用的时候,一般这个对象会被回收掉,但若是咱们想在对象被回收前进行一些操做,好比关闭一些资源,或者让这个对象复活,不让他被回收怎么办?这时候就要用到finailize方法了。finailize方法是Object类中定义的方法,意味着任何一个对象都有这个方法。但这个方法只会调用一次,若是把这个对象复活后再次让这个对象死亡,那第2次回收该对象的时候是不会调用finailize方法的,并且优先级比较低,并不能保证必定会被执行,所以不建议使用finalize方法。总结起来就是3个特性: ①、GC以前被调用 。②、只会被调用一次。③、不可靠,不能保证被执行,不建议使用。关于finalize使用方法,参考以下代码:算法
1 public class FinalizeTest { 2 3 private static FinalizeTest test; 4 /** 5 * VM参数:-XX: +PrintGCDetails -Xmx=1M -Xms=1M 6 * 7 * @param args 8 */ 9 public static void main(String[] args) { 10 //先对test对象赋值 11 test = new FinalizeTest(); 12 int _1m = 1024 * 1024; 13 //将test置为null,便于回收 14 test = null; 15 try { 16 System.gc(); 17 //模拟睡眠5s,finalize优先级较低,保证finalize能执行 18 Thread.sleep(5000); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 if (test != null) { 23 System.out.println("first,i am alive"); 24 }else{ 25 System.out.println("first,i am dead"); 26 } 27 //因为test在finalize方法里复活了,再次将test置为null 28 test = null; 29 try { 30 System.gc(); 31 Thread.sleep(5000);//模拟睡眠5s,让GC回收 32 } catch (InterruptedException e) { 33 e.printStackTrace(); 34 } 35 if (test != null) { 36 System.out.println("second,i am alive"); 37 }else{ 38 System.out.println("second,i am dead"); 39 } 40 41 } 42 @Override 43 protected void finalize() throws Throwable { 44 test = this ; 45 System.out.println("finalize excuted"); 46 super.finalize(); //调用父类的finailize方法 47 } 48 }
该代码运行结果以下:ide
能够看到,finalize方法执行后,test对象又被从新激活了,所以打印了first,i am alive。可是第二次GC的时候,finalize方法并未被执行,所以打印了second,i am dead。前面提到finalize是优先级低不可靠的,那若是没有Thread.sleep(5000),再来看下代码和结果:this
1 public class FinalizeTest { 2 3 private static FinalizeTest test; 4 /** 5 * VM参数:-XX: +PrintGCDetails -Xmx=1M -Xms=1M 6 * 7 * @param args 8 */ 9 public static void main(String[] args) { 10 //先对test对象赋值 11 test = new FinalizeTest(); 12 int _1m = 1024 * 1024; 13 //将test置为null,便于回收 14 test = null; 15 try { 16 System.gc(); 17 //模拟睡眠5s,finalize优先级较低,保证finalize能执行 18 //不执行睡眠操做,Thread.sleep(5000); 19 } catch (Exception e) { 20 e.printStackTrace(); 21 } 22 if (test != null) { 23 System.out.println("first,i am alive"); 24 }else{ 25 System.out.println("first,i am dead"); 26 } 27 //因为test在finalize方法里复活了,再次将test置为null 28 test = null; 29 try { 30 System.gc(); 31 //不执行睡眠操做,Thread.sleep(5000);//模拟睡眠5s,让GC回收 32 } catch (Exception e) { 33 e.printStackTrace(); 34 } 35 if (test != null) { 36 System.out.println("second,i am alive"); 37 }else{ 38 System.out.println("second,i am dead"); 39 } 40 41 } 42 @Override 43 protected void finalize() throws Throwable { 44 test = this ; 45 System.out.println("finalize excuted"); 46 super.finalize(); //调用父类的finailize方法 47 } 48 }
运行结果以下: spa
这里能够很清楚地看到,finalize方法的优先级是比较低的。3d
关于这个例子的反思:这个例子中第一段代码是参考《深刻理解java虚拟机》里的代码实现的,可是总感受有2点疑问:为何test对象是以static修饰的成员变量方式存在?若是是static修饰,那就是存在方法区了,而方法区的GC一般效果不太好的。另外一个是以成员变量的方式存在,这样finalize回收的时候,体现不出是对当前对象自己的回收,因此感受这个例子并非很好。code
2、引用计数法htm
引用计数法是一种比较早的GC回收算法,目前通常不采用,其主要思想是:每一个对象都维持一个引用计数器,初始值为0,当一个对象被引用的时候,该对象的引用计数器就加1,当不被引用的时候,该对象的引用计数器就减1,若是一个对象的引用计数器变为了0,则该对象被认为是能够回收的。采用这种方式的优缺点都很明显,优势是实现简单,效率高,缺点是可能存在循环引用,致使内存溢出。对象
3、标记-清除法
标记-清除法按名字分为“标记”和“清除”2个阶段,其基本思想是:首先标记出全部存活的对象,标记完成后,统一清除全部被标记的对象。那怎么判断某个对象是能够回收的呢?GC时,从一系列GC Roots根节点开始遍历,遍历时走过的路径即称为引用链,若是一个对象和GC Roots没有任何引用链相关,那么这个对象就不可用,就会被断定为可回收,这种算法也叫根搜索算法。那么哪些对象能够成为GC Roots对象呢?在java语言里,能够做为GC Roots的对象包括下面4种:
虚拟机栈中的引用变量
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI(即native方法)的引用的对象
标记-清除法的算法示意图以下:
注:本文的GC回收算法图片转自一个网友的文章(点这里),该网友的图片内容也与原著一致,只是颜色不一样。
4、新生代的复制法
复制法的基本思想是:将内存分为大小相等的2块,每次只使用其中一块,GC时每次将全部存活的对象复制到另外一块区域,而后清理该内存。
这几种都是方法区和栈中的引用对象。复制法的优势是:实现简单,回收速度快,且不会产生内存碎片。但因为每次只使用其中一块,致使内存利用率较低。复制算法的示意图以下:
如今的商业虚拟机都采用复制法来回收新生代,因为新生代的对象98%以上都是朝生夕死的,因此并不须要按照1:1来分配,而是将内存分为较大的Eden区和2块较小的Survivor区(一般Eden和Survivor区大小的比值为8:1:1,能够根据SurvivorRationJVM内存参数来设置比值),每次使用Eden区和其中一块Survivor区类分配对象,GC时,将Eden区和Survivor区中的存活对象复制到另外一块Survivor区域,这样一来,内存利用率就高了,并且运行速度也很快。
5、老年代的标记-整理法
复制法在对象存活率较高时,回收效率就变低了,而在老年代中,大部分的对象都是存活期较高的对象,所以就不适宜采用复制法进行老年代的GC。根据老年代的特色,并结合标记-清除法的思路,因而提出了标记-整理法。其主要思路是:标记过程与标记-清除法一致,只是标记完成后,不直接对未存活进行清除,而是将全部存活的对象都向一端移动,而后清理掉端边界之外的全部内存区域。这种方法的优势是不会产生内存碎片。标记-整理法的算法示意图以下: