zoukankan      html  css  js  c++  java
  • JVM-内存分配机制and垃圾对象判断

      本文章分为两部分,一部分讲述jvm内存分配,还有一部分讲述会被回收类的解析。

      b话不多说,直接进入主题

      一切概念性的东西,代码&图解搞起;

      芜湖起飞~

       

      1.JVM内存分配机制

      开始之前,我们先到idea配置个jvm参数。

    public class GcTest {
      //新建一个类,创建一个main函数,什么都不干,然后加入参数   -XX:+PrintGCDetails
        public static void main(String[] args) {
    
        }
    
    }

      我们来运行一些程序,看看控制台输出

    Heap
     PSYoungGen      total 38400K, used 8209K [0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)
      eden space 33280K, 24% used [0x00000000d5f00000,0x00000000d67044f8,0x00000000d7f80000)
      from space 5120K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8980000)
      to   space 5120K, 0% used [0x00000000d7f80000,0x00000000d7f80000,0x00000000d8480000)
     ParOldGen       total 87552K, used 0K [0x0000000081c00000, 0x0000000087180000, 0x00000000d5f00000)
      object space 87552K, 0% used [0x0000000081c00000,0x0000000081c00000,0x0000000087180000)
     Metaspace       used 3352K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

      上面输出的是我本地jvm的堆内存信息,可以看到,我的年轻代一共是35MB左右,已使用8MB左右;老年代为85MB左右,使用量为0;剩下的是一些原空间信息。

      1.1对象优先在Eden区分配

      对象在堆内存开辟空间,优先分配在eden区。我们来运行下面代码看看。

    public class GcTest {
    
        public static void main(String[] args) {
            byte[] bytes = new byte[1024*1024*5];
        }
    
    }

      我new了一个5MB的对象,我们看控制台,这个对象被放到那里了:

    Heap
     PSYoungGen      total 38400K, used 13329K [0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)
      eden space 33280K, 40% used [0x00000000d5f00000,0x00000000d6c04508,0x00000000d7f80000)
      from space 5120K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8980000)
      to   space 5120K, 0% used [0x00000000d7f80000,0x00000000d7f80000,0x00000000d8480000)
     ParOldGen       total 87552K, used 0K [0x0000000081c00000, 0x0000000087180000, 0x00000000d5f00000)
      object space 87552K, 0% used [0x0000000081c00000,0x0000000081c00000,0x0000000087180000)
     Metaspace       used 3352K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

      上面我们可以清楚的看到,new的byte数组被放到了eden区,原本只占24%的eden区现在被占用了40%。由此可见,我们新new的对象默认都是往eden区存放。

      1.2大对象直接进入老年代

      大对象直接进入老年代,什么情况下新new的对象会直接进入老年代呢?我们看下面代码:

    public class GcTest {
    
        public static void main(String[] args) {
            byte[] bytes = new byte[1024*1024*50];
        }
    
    }

      执行上面代码,看控制台输出:

    Heap
     PSYoungGen      total 38400K, used 8209K [0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)
      eden space 33280K, 24% used [0x00000000d5f00000,0x00000000d67044f8,0x00000000d7f80000)
      from space 5120K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8980000)
      to   space 5120K, 0% used [0x00000000d7f80000,0x00000000d7f80000,0x00000000d8480000)
     ParOldGen       total 87552K, used 51200K [0x0000000081c00000, 0x0000000087180000, 0x00000000d5f00000)
      object space 87552K, 58% used [0x0000000081c00000,0x0000000084e00010,0x0000000087180000)
     Metaspace       used 3352K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

      上面我们可以看出,eden区还是只被占用了默认的24%,但是我们看老年代,已经被使用了51200kb,由此可见,我们上面new 的bytes直接进入到老年代了。

      那么是多大的对象会直接进入到老年代呢?我们把50MB改成30MB看看,被分配到哪:

    public class GcTest {
    
        public static void main(String[] args) {
            byte[] bytes = new byte[1024*1024*30];
        }
    
    }

      执行结果:

    Heap
     PSYoungGen      total 38400K, used 8209K [0x00000000d5f00000, 0x00000000d8980000, 0x0000000100000000)
      eden space 33280K, 24% used [0x00000000d5f00000,0x00000000d67044f8,0x00000000d7f80000)
      from space 5120K, 0% used [0x00000000d8480000,0x00000000d8480000,0x00000000d8980000)
      to   space 5120K, 0% used [0x00000000d7f80000,0x00000000d7f80000,0x00000000d8480000)
     ParOldGen       total 87552K, used 30720K [0x0000000081c00000, 0x0000000087180000, 0x00000000d5f00000)
      object space 87552K, 35% used [0x0000000081c00000,0x0000000083a00010,0x0000000087180000)
     Metaspace       used 3352K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

      我们可以看到,数组被分配到了老年代,30MB+38400*0.24/1024这个结果是要大于eden区内存的,所以数组直接被分配到了老年代。

      由此,我们可以得出结论:新new 的对象如果比较大,大于eden区剩余空间,这个对象就会被直接分配到老年代。

      再来看jvm控制大对象分配参数:-XX:PretenureSizeThreshold

      我们在idea加上这个参数:(设置对象如果超过5mb,直接放入老年代)-XX:PretenureSizeThreshold=5m -XX:+UseSerialGC(为啥要加后面这个参数呢?因为大对象直接进老年代只有在Serial 和ParNew两个收集 器下有效)

      我们把bytes改成6MB:

    public class GcTest {
    
        public static void main(String[] args) {
            byte[] bytes = new byte[1024*1024*6];
        }
    
    }

      运行结果:

    Heap
     def new generation   total 39296K, used 8440K [0x0000000081c00000, 0x00000000846a0000, 0x00000000abd50000)
      eden space 34944K,  24% used [0x0000000081c00000, 0x000000008243e2d0, 0x0000000083e20000)
      from space 4352K,   0% used [0x0000000083e20000, 0x0000000083e20000, 0x0000000084260000)
      to   space 4352K,   0% used [0x0000000084260000, 0x0000000084260000, 0x00000000846a0000)
     tenured generation   total 87424K, used 6144K [0x00000000abd50000, 0x00000000b12b0000, 0x0000000100000000)
       the space 87424K,   7% used [0x00000000abd50000, 0x00000000ac350010, 0x00000000ac350200, 0x00000000b12b0000)
     Metaspace       used 3321K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 361K, capacity 388K, committed 512K, reserved 1048576K

      bytes直接被分配到了老年代。

      1.3长期存活的对象将进入老年代

      如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1。对象 在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定 程度(默认为15岁),就会被晋升到老年代中。可以通过参数:-XX:MaxTenuringThreshold 来控制分带年龄进入老年代大小。

      我们先把上面大对象去老年代参数去掉,然后添加参数:-Xms180M -Xmx180M -XX:NewSize=60M -Xmn60M;

      然后运行空的main函数,可以看到下面结果:

    Heap
     PSYoungGen      total 53760K, used 9232K [0x00000000fc400000, 0x0000000100000000, 0x0000000100000000)
      eden space 46080K, 20% used [0x00000000fc400000,0x00000000fcd04380,0x00000000ff100000)
      from space 7680K, 0% used [0x00000000ff880000,0x00000000ff880000,0x0000000100000000)
      to   space 7680K, 0% used [0x00000000ff100000,0x00000000ff100000,0x00000000ff880000)
     ParOldGen       total 122880K, used 0K [0x00000000f4c00000, 0x00000000fc400000, 0x00000000fc400000)
      object space 122880K, 0% used [0x00000000f4c00000,0x00000000f4c00000,0x00000000fc400000)
     Metaspace       used 3352K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

      我们可以看到eden区大小为46080kb,我们要写一个程序,搞点对象,让对象发生15次gc。

    public class GcTest {
    
    
        static byte[] bytes = new byte[1024*1024*6];
    
        //-XX:+PrintGCDetails
        public static void main(String[] args) {
            //-XX:PretenureSizeThreshold=5m -XX:+UseSerialGC
    
    
    
            //(3*15)*1024 = 46080 = eden区大小46080发生一次gc
            //所以要放入 15*15-2-(46080/1024)*0.2-2*5=204个3MB大小的对象
    
            for (int i =0;i <= 204; i++) {
                byte[] bs = new byte[1024*1024*3];
                if (i == 204) {
                    System.out.println(15+"次gc了");
                }
            }
    
    
        }
    
    }

      我们看控制台输出:

    [GC (Allocation Failure) [PSYoungGen: 45175K->7678K(53760K)] 45175K->7694K(176640K), 0.0045698 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
    [GC (Allocation Failure) [PSYoungGen: 51570K->7303K(53760K)] 51586K->7319K(176640K), 0.0062331 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
    [GC (Allocation Failure) [PSYoungGen: 52104K->7287K(53760K)] 52120K->7311K(176640K), 0.0051494 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
    [GC (Allocation Failure) [PSYoungGen: 51171K->7287K(53760K)] 51195K->7311K(176640K), 0.0048894 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    [GC (Allocation Failure) [PSYoungGen: 51181K->7287K(53760K)] 51205K->7319K(176640K), 0.0056883 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    [GC (Allocation Failure) [PSYoungGen: 51187K->7343K(53760K)] 51219K->7375K(176640K), 0.0111028 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    [GC (Allocation Failure) [PSYoungGen: 51247K->32K(46592K)] 51279K->7319K(169472K), 0.0107118 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
    [GC (Allocation Failure) [PSYoungGen: 43938K->0K(50688K)] 51225K->7295K(173568K), 0.0003178 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 40735K->0K(41472K)] 48031K->7295K(164352K), 0.0004945 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 40737K->0K(49664K)] 48032K->7295K(172544K), 0.0003646 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 37625K->0K(50176K)] 44921K->7295K(173056K), 0.0007728 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 37626K->0K(50176K)] 44921K->7295K(173056K), 0.0010482 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 37626K->0K(50176K)] 44922K->7295K(173056K), 0.0004554 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 37626K->0K(50688K)] 44922K->7295K(173568K), 0.0003000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 37636K->0K(50176K)] 44932K->7295K(173056K), 0.0003343 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    15次gc了
    Heap
     PSYoungGen      total 50176K, used 35353K [0x00000000fc400000, 0x0000000100000000, 0x0000000100000000)
      eden space 39424K, 89% used [0x00000000fc400000,0x00000000fe686560,0x00000000fea80000)
      from space 10752K, 0% used [0x00000000fea80000,0x00000000fea80000,0x00000000ff500000)
      to   space 10752K, 0% used [0x00000000ff580000,0x00000000ff580000,0x0000000100000000)
     ParOldGen       total 122880K, used 7295K [0x00000000f4c00000, 0x00000000fc400000, 0x00000000fc400000)
      object space 122880K, 5% used [0x00000000f4c00000,0x00000000f531fed8,0x00000000fc400000)
     Metaspace       used 3357K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

      老年代对象使用空间为5%,122880KB*5%不就正好是6MB吗,不就是我们上面的静态byte数组吗?15次gc到达了老年代!

      1.4对象动态年龄判断

      当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总 大小大于这块Survivor区域内存大小的50%,那么此时大于等于这批对象年龄最 大值的对象,就可以直接进入老年代了,例如Survivor区域里现在有一批对象, 年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就 会把年龄n以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活 的对象,尽早进入老年代。对象动态年龄判断机制一般是在minor gc之后触发 的。

      这个如果需要用代码来展示,确实太麻烦,一下子也想不出这个程序,来画图吧:

       1.5Minor gc后存活的对象Survivor区放不下,会把存活的对象部分挪到老年代,部分可能还会放在Survivor区

      先来回顾一下上面参数加了以后的内存情况:

    Heap
     PSYoungGen      total 53760K, used 9232K [0x00000000fc400000, 0x0000000100000000, 0x0000000100000000)
      eden space 46080K, 20% used [0x00000000fc400000,0x00000000fcd04380,0x00000000ff100000)
      from space 7680K, 0% used [0x00000000ff880000,0x00000000ff880000,0x0000000100000000)
      to   space 7680K, 0% used [0x00000000ff100000,0x00000000ff100000,0x00000000ff880000)
     ParOldGen       total 122880K, used 0K [0x00000000f4c00000, 0x00000000fc400000, 0x00000000fc400000)
      object space 122880K, 0% used [0x00000000f4c00000,0x00000000f4c00000,0x00000000fc400000)
     Metaspace       used 3351K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

      eden区为46080KB,使用了20%。

      上面的参数不改,我们来看下面代码:

    public class GcTest {
    
    
    //    static byte[] bytes = new byte[1024*1024*6];
    
        //-XX:+PrintGCDetails
        public static void main(String[] args) {
            //-XX:PretenureSizeThreshold=5m -XX:+UseSerialGC
    
            byte[] bytes = new byte[1024*1024*30];
            byte[] bs = new byte[1024*1024*6];
    
            //(3*15)*1024 = 46080 = eden区大小46080发生一次gc
            //所以要放入 15*15-2-(46080/1024)*0.2-2*5=204个3MB大小的对象
    
    //        for (int i =0;i <= 204; i++) {
    //            byte[] bs = new byte[1024*1024*3];
    //            if (i == 204) {
    //                System.out.println(15+"次gc了");
    //            }
    //        }
    
    
        }
    
    }

      执行结果:

    Heap
     PSYoungGen      total 53760K, used 46080K [0x00000000fc400000, 0x0000000100000000, 0x0000000100000000)
      eden space 46080K, 100% used [0x00000000fc400000,0x00000000ff100000,0x00000000ff100000)
      from space 7680K, 0% used [0x00000000ff880000,0x00000000ff880000,0x0000000100000000)
      to   space 7680K, 0% used [0x00000000ff100000,0x00000000ff100000,0x00000000ff880000)
     ParOldGen       total 122880K, used 0K [0x00000000f4c00000, 0x00000000fc400000, 0x00000000fc400000)
      object space 122880K, 0% used [0x00000000f4c00000,0x00000000f4c00000,0x00000000fc400000)
     Metaspace       used 3352K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

      这个时候刚好把eden区塞满了,我们在new一个6BM的数组:

    public class GcTest {
    
    
    //    static byte[] bytes = new byte[1024*1024*6];
    
        //-XX:+PrintGCDetails
        public static void main(String[] args) {
            //-XX:PretenureSizeThreshold=5m -XX:+UseSerialGC
    
            byte[] bytes = new byte[1024*1024*30];
            byte[] bs1 = new byte[1024*1024*6];
            byte[] bs2 = new byte[1024*1024*6];
    
            //(3*15)*1024 = 46080 = eden区大小46080发生一次gc
            //所以要放入 15*15-2-(46080/1024)*0.2-2*5=204个3MB大小的对象
    
    //        for (int i =0;i <= 204; i++) {
    //            byte[] bs = new byte[1024*1024*3];
    //            if (i == 204) {
    //                System.out.println(15+"次gc了");
    //            }
    //        }
    
    
        }
    
    }

      执行结果:

    [GC (Allocation Failure) [PSYoungGen: 45174K->7659K(53760K)] 45174K->38448K(176640K), 0.0294891 secs] [Times: user=0.02 sys=0.00, real=0.03 secs] 
    Heap
     PSYoungGen      total 53760K, used 14110K [0x00000000fc400000, 0x0000000100000000, 0x0000000100000000)
      eden space 46080K, 14% used [0x00000000fc400000,0x00000000fca4ce50,0x00000000ff100000)
      from space 7680K, 99% used [0x00000000ff100000,0x00000000ff87ac70,0x00000000ff880000)
      to   space 7680K, 0% used [0x00000000ff880000,0x00000000ff880000,0x0000000100000000)
     ParOldGen       total 122880K, used 30789K [0x00000000f4c00000, 0x00000000fc400000, 0x00000000fc400000)
      object space 122880K, 25% used [0x00000000f4c00000,0x00000000f6a116b0,0x00000000fc400000)
     Metaspace       used 3356K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 366K, capacity 388K, committed 512K, reserved 1048576K

      可以看到发生了一次gc,老年代被使用了30MB,不就是我们第一个new的30MB的数组吗?我们的Survivor区大小只有7680KB,肯定是放不下这30MB的对象啊,所以直接把30MB的bytes直接挪到了老年代。

      1.6Eden与Survivor区默认8:1:1

      大量的对象被分配在eden区,eden区满了后会触发minor gc,可能会有99% 以上的对象成为垃圾被回收掉,剩余存活的对象会被挪到为空的那块survivor 区,下一次eden区满了后又会触发minor gc,把eden区和survivor去垃圾对象 回收,把剩余存活的对象一次性挪动到另外一块为空的survivor区,因为新生代 的对象都是朝生夕死的,存活时间很短,所以JVM默认的8:1:1的比例是很合适 的,让eden区尽量的大,survivor区够用即可 JVM默认有这个参数-XX:+UseAdaptiveSizePolicy,会导致这个比例自动变 化,如果不想这个比例有变化可以设置参数-XX:-UseAdaptiveSizePolicy

      1.7老年代空间分配担保机制

      年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间 如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象) 就会看一个“-XX:-HandlePromotionFailure”(jdk1.8默认就设置了)的参数是 否设置了 如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minor gc后进入老年代的对象的平均大小。 如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次Full gc,对老年代和年轻代一起回收一次垃圾,如果回收完还是没有足够空间存放 新的对象就会发生"OOM" 当然,如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老 年代可用空间,那么也会触发full gc,full gc完之后如果还是没用空间放minor gc之后的存活对象,则也会发生“OOM”

      我们来画个图吧:

       老年代 空间分配机制可能会触发full gc,这个在调优中也是需要考虑到的,后面会更。

      2.判断对象为垃圾对象

       2.1引用计数法

      给对象中添加一个引用计数器(放在类的头部信息中),每当有一个地方引用它,计数器就加1;当引用 失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。 这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管 理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。

      相互引用问题我们来看下面代码:

    public class QuoteTest {
    
        Object object;
    
        public static void main(String[] args) {
            QuoteTest quoteTest1 = new QuoteTest();    //quoteTest1的引用计数器为0+1
            QuoteTest quoteTest2 = new QuoteTest();    //quoteTest2的引用计数器为0+1
            quoteTest1.object = quoteTest2;        //quoteTest1的引用计数器为1+1;
            quoteTest2.object = quoteTest1;        //quoteTest2的引用计数器为1+1;
            quoteTest1 = null;              //quoteTest1的引用计数器为1;

         quoteTest2 = null;             //quoteTest2的引用计数器为1;
       } 
    }

      从上面程序可以看出,quoteTest1和quoteTest2又被指向里面的属性object,quoteTest1和quoteTest2被相互引用着。引用计数器不会为0,如果是使用引用计数器法,一直不会被标记为垃圾对象,直到进入老年代,给老年代增加内存压力!

      在quoteTest1和quoteTest2被置为null之前:

      然后把 quoteTest1和quoteTest2被置为null后:

       d堆里面的对象任然被引用着,引用计数器为1。

      2.2可达性分析算法

      这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点, 从这些节点开始向下搜索,找到的对象都标记为非垃圾对象,其余未标记的对象 都是垃圾对象 GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等。

       如下图所示:

       没有被gc root所引用的都是游离 状态的对象,这些对象都会被标记为垃圾对象,在下一次gc的时候会被回收掉。

      2.3 finalize()方法最终判定对象是否存活

      使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们 暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程。 标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。

      2.3.1 第一次标记并进行一次筛选。

         筛选的条件是此对象是否有必要执行finalize()方法。

        当对象没有覆盖finalize方法,对象将直接被回收。

      2.3.2第二次标记

        如果这个对象覆盖了finalize方法,finalize方法是对象脱逃死亡命运的最后一次 机会,如果对象要在finalize()中成功拯救自己,只要重新与引用链上的任何的一 个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第 二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本 上它就真的被回收了。

      一个对象如果是游离状态了,但是重写了finalize方法,重新被gc root指向,那么这个对象将不会被回收。

      我们看下面代码:

    public class OOMTest {
    
    
        public static void main(String[] args) {
            List<Finalize> list = new ArrayList<>();
            int i = 0;
            int j = 0;
            while (true) {
                list.add(new Finalize(i++));
                new Finalize(j--);
            }
    
        }
    
    }
    
    
    public class Finalize {
    
        private int id;
    
        public Finalize(int id) {
            this.id = id;
        }
    
    
        @Override
        protected void finalize() throws Throwable {
            System.out.println("id为:" + id + "即将被回收");
        }
    }

      执行上面代码,控制台输出:

    id为:-198768即将被回收
    id为:-199793即将被回收
    id为:-199919即将被回收
    id为:-199984即将被回收
    id为:-200050即将被回收
    id为:-200370即将被回收
    id为:-200430即将被回收
    id为:-200512即将被回收
    id为:-200576即将被回收
    id为:-200607即将被回收
    id为:-202625即将被回收
    id为:-202673即将被回收
    id为:-202708即将被回收
    id为:-202743即将被回收
    id为:-202785即将被回收
    id为:-202823即将被回收
    id为:-202854即将被回收
    id为:-202887即将被回收
    id为:-202920即将被回收

      id都是负数,因为下面new出来的Finalize是游离状态的,上面new出来的Finalize在gc root list的引用链上,所以没有调用finalize方法。

      那么对象如何进行自救呢?我们看下面代码:

    public class OOMTest {
    
        public static List<Finalize> finalizeList = new ArrayList<>();
    
    
        public static void main(String[] args) {
    //        List<Finalize> list = new ArrayList<>();
    //        int i = 0;
            int j = 0;
            while (true) {
    //            list.add(new Finalize(i++));
                new Finalize(j--);
            }
    
        }
    
    }
    
    
    public class Finalize {
    
        private int id;
    
        public Finalize(int id) {
            this.id = id;
        }
    
    
        @Override
        protected void finalize() throws Throwable {
            OOMTest.finalizeList.add(this);
    //        System.out.println("id为:" + id + "即将被回收");
        }
    }

      我们打开cmd,执行jps,找到main函数的pid,然后执行JConsole pid命令:如下图

     

       

      上面图中,我们可以看到,堆内存使用一直在增长,这个时候就可以说明,上面new的Finalize并没有被gc回收,上面在finalize方法中实现了对象自救。

      2.4如何判断一个类是无用的类

        2.4.1该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例(在堆里面已经找不到对象{对象头里面有指针指向xxx.class}去指向xxx.class了)

        2.4.2加载该类的 ClassLoader 已经被回收(这个很好理解)

        2.4.3该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何 地方通过反射访问该类的方法(反射生成的Class对象在堆里面都已经被回收)

      上面情况都满足就是无用的类。

      2.5 常见引用类型

        2.5.1强引用:普通的变量引用

    QuoteTest quoteTest1 = new QuoteTest();

        2.5.2软引用:将对象用SoftReference软引用类型的对象包裹

    SoftReference<QuoteTest> srf = new SoftReference<QuoteTest>(new QuoteTest());

        正常情况不会被回 收,但是GC做完后发现释放不出空间存放新的对象,则会把这些软引用的对象 回收掉。软引用可用来实现内存敏感的高速缓存。

        软引用作用在:

          (1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过 的页面时,需要重新构建

          (2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内 存溢出

        2.5.3将对象用WeakReference软引用类型的对象包裹

    WeakReference<QuoteTest> srf = new WeakReference<QuoteTest>(new QuoteTest());

        弱引用跟没引用差 不多,GC会直接回收掉,很少用

        2.5.4虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系,几乎不用

      生而为人,我很抱歉。

      

  • 相关阅读:
    思考的乐趣
    编程的知识体系
    Android用Intent来启动Service报“java.lang.IllegalArgumentException: Service Intent must be explicit”错误的解决方法
    手拼SQL小技巧,WHERE 1=1
    n+1 < n , are you sure?
    java swing 去掉按钮文字周围的焦点框
    MyBatis使用动态SQL标签的小陷阱
    String、StringBuffer、StringBuilder的一些小经验……
    SQL Server 2012安装后找不到服务器名称的解决办法!!!
    CSS元素水平垂直居中方法总结(主要对大漠以及张鑫旭博客所述方法进行了归纳)
  • 原文地址:https://www.cnblogs.com/ghsy/p/13396557.html
Copyright © 2011-2022 走看看