• 技术文章 >Java >java教程

    JAVA虚拟机学习笔记:JVM内存模型中垃圾回收方法

    php是最好的语言php是最好的语言2018-08-08 10:47:14原创566
    上篇文章介绍了JVM内存模型的相关知识,其实还有些内容可以更深入的介绍下,比如运行时常量池的动态插入,直接内存等,后期抽空再完善下上篇博客,今天来介绍下JVM中的一些垃圾回收策略。

    一、finailize()方法

    在介绍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 }

    复制代码

    该代码运行结果如下:

    可以看到,finalize方法执行后,test对象又被重新激活了,因此打印了first,i am alive。但是第二次GC的时候,finalize方法并未被执行,因此打印了second,i am dead。前面提到finalize是优先级低不可靠的,那如果没有Thread.sleep(5000),再来看下代码和结果:

    复制代码

     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 }

    复制代码

    运行结果如下:

    这里可以很清楚地看到,finalize方法的优先级是比较低的。

    关于这个例子的反思:这个例子中第一段代码是参考《深入理解java虚拟机》里的代码实现的,但是总感觉有2点疑问:为什么test对象是以static修饰的成员变量方式存在?如果是static修饰,那就是存在方法区了,而方法区的GC通常效果不太好的。另一个是以成员变量的方式存在,这样finalize回收的时候,体现不出是对当前对象本身的回收,所以感觉这个例子并不是很好。

    二、引用计数法

    引用计数法是一种比较早的GC回收算法,目前一般不采用,其主要思想是:每个对象都维持一个引用计数器,初始值为0,当一个对象被引用的时候,该对象的引用计数器就加1,当不被引用的时候,该对象的引用计数器就减1,如果一个对象的引用计数器变为了0,则该对象被认为是可以回收的。采用这种方式的优缺点都很明显,优点是实现简单,效率高,缺点是可能存在循环引用,导致内存溢出。

    三、标记-清除法

    标记-清除法按名字分为“标记”和“清除”2个阶段,其基本思想是:首先标记出所有存活的对象,标记完成后,统一清除所有被标记的对象。那怎么判断某个对象是可以回收的呢?GC时,从一系列GC Roots根节点开始遍历,遍历时走过的路径即称为引用链,如果一个对象和GC Roots没有任何引用链相关,那么这个对象就不可用,就会被判定为可回收,这种算法也叫根搜索算法。那么哪些对象可以成为GC Roots对象呢?在java语言里,可以作为GC Roots的对象包括下面4种:

    虚拟机栈中的引用变量

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

    方法区中的常量引用的对象

    本地方法栈中JNI(即native方法)的引用的对象

    标记-清除法的算法示意图如下:

    注:本文的GC回收算法图片转自一个网友的文章(点这里),该网友的图片内容也与原著一致,只是颜色不同。

    四、新生代的复制法

    复制法的基本思想是:将内存分为大小相等的2块,每次只使用其中一块,GC时每次将所有存活的对象复制到另一块区域,然后清理该内存

    这几种都是方法区和栈中的引用对象。复制法的优点是:实现简单,回收速度快,且不会产生内存碎片。但由于每次只使用其中一块,导致内存利用率较低。复制算法的示意图如下:

    相关推荐:

    jvm垃圾回收算法

    分享Java垃圾回收机制学习总结

    以上就是JAVA虚拟机学习笔记:JVM内存模型中垃圾回收方法的详细内容,更多请关注php中文网其它相关文章!

    声明:本文原创发布php中文网,转载请注明出处,感谢您的尊重!如有疑问,请联系admin@php.cn处理
    上一篇:java类的初始化什么时候进行?(附代码) 下一篇:​Java集合:Set、List、Queue、Map四个体系的归纳总结
    大前端线上培训班

    相关文章推荐

    • 理解java8中java.util.function.*pojo反射新方法(附代码)• 浅析安卓app和微信授权登录及分享完整对接(代码分享)• 一招教你使用java快速创建Map(代码分享)• 教你一招搞定时序数据库在Spring Boot中的使用• 一文讲解Java中初始化List集合的8种方式(附代码)

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网