• java对象引用与垃圾收集器回收算法


    1.如何判断对象可回收

    核心思想:堆内存中对象没有被任何引用。

    在c语言中没有自动化垃圾回收机制,需要开发者自己人工清理堆垃圾,在java中开发自动化方式清理堆垃圾。

    引用计数法

     引用计数法:每次当该对象引用一次的时候,引用次数都会+1,如果引用的次数为0 则认为没有被引用,直接被垃圾给回收清理掉。

     最大的缺陷:A如果引用B,B引用A  但是其他对象没有任何的引用A和B,相互存相互依赖,无法被垃圾回收。

    Java虚拟机采用可达性分析算法

    通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

    白话文解释:只要对象的引用和GCRoot没有引用关系则可以进行垃圾回收,反之则不能够进行垃圾回收。解决了循环依赖的问题

    哪些可以作为GC Roots

    1.虚拟机栈(栈帧中的本地变量表)中引用的对象。

    2.元空间中类静态属性引用的对象。

    3.本地方法栈中JNI(即一般说的Native方法)引用的对象。

    可以使用 MemoryAnalyzer 工具进行文件内存分析,下载地址,https://www.eclipse.org/mat/downloads.php

    代码测试:

    public class Test001 {
    
        public static void main(String[] args) throws IOException {
            ArrayList<Object> list = new ArrayList<>();
            mayikts.add("aaa");
            mayikts.add("bbb");
            System.out.println("存储成功..");
            System.in.read();
            list = null;
            System.out.println("list变为null");
            System.in.read();
            System.in.read();
            System.out.println("end");
        }
    }

    -- 打镜像文件命令

    jmap -dump:format=b,live,file=a.bin 53120

    2.java四种引用方法

    2.1:强引用

    -Xmx8m :设置堆内存

    -XX:+PrintGCDetails -verbose:gc  :打印 gc 回收信息

    强引用:被引用关联的对象永远不会被垃圾收集器回收

    Object object=new Object();那object就是一个强引用了。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

    public class Test01 {
    
        public static void main(String[] args) {
            // 强引用
            UserInfo userInfo1 = new UserInfo("ming");
            UserInfo userInfo2 = userInfo1;
            userInfo1 = null;
            System.out.println(userInfo2);
        }
    }

    控制台输出结果:com.example.javareference.UserInfo@77459877

    2.2:软引用(SoftReference)

    软引用:软引用关联的对象,在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常

    public class Test02 {

    public static void main(String[] args) {
    soft1();
    // soft2();
    }

    /**
    * 软引用:打印已经被清理的对象
    */
    private static void soft1() {
    ArrayList<SoftReference<byte[]>> objects = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
    SoftReference<byte[]> softReference = new SoftReference<>(new byte[4 * 1024 * 1024]);
    System.out.println(softReference.get());
    objects.add(softReference);
    }
    System.out.println("打印结果:");
    objects.forEach((t) -> {
    System.out.println(t.get());
    });
    }


    /**
    * 软引用:不打印已经被清理的对象
    */
    private static void soft2() {
    ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>();
    ArrayList<SoftReference<byte[]>> objects = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
    SoftReference<byte[]> softReference = new SoftReference<>(new byte[4 * 1024 * 1024], referenceQueue);
    objects.add(softReference);
    }
    Reference<? extends byte[]> poll = referenceQueue.poll();
    while (poll != null) {
    objects.remove(poll);
    poll = referenceQueue.poll();
    }
    System.out.println("打印结果:");
    objects.forEach((t) -> {
    System.out.println(t.get());
    });
    }
    }

    2.3:弱引用(WeakReference)

    无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。

    public class Test02 {
        public static void main(String[] args) {
            UserInfo userInfo1 = new UserInfo("ming");
            // 弱引用
            WeakReference<UserInfo> userInfoWeakReference = new WeakReference<>(userInfo1);
            userInfo1 = null;
            // 手动进行 gc 回收
            System.gc();
            System.out.println(userInfoWeakReference.get());
        }
    }
    
    控制台输出结果:null

    2.4:虚引用(PhantomReference)

    如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收

    public class Test04 {

    public static void main(String[] args) {
    UserInfo userInfo1 = new UserInfo("ming");
    ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
    // 虚引用
    PhantomReference<UserInfo> phantomReference = new PhantomReference<>(userInfo1, referenceQueue);
    System.out.println(phantomReference.get());
    }
    }
    控制台输出为:null

    引用队列(ReferenceQueue)

    软弱虚对应引用的指针放入到引用队列中,实现清理。

            ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>();
            ArrayList<SoftReference<byte[]>> objects = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                SoftReference<byte[]> softReference = new SoftReference<>(new byte[4 * 1024 * 1024], referenceQueue);
                objects.add(softReference);
            }

     

    3.垃圾收集器回收算法

    垃圾收集器在什么时候触发垃圾收集?

    当新生代或者是老年代内存满的情况下,开始触发垃圾收集。

    3.1 标记清除算法

    根据gcRoot对象的引用链,发现如果该对象没有被引用的情况下,则标记为垃圾,然后在清除。

    优点:算法非常简单。

    缺点:空间地址不连续、空间利用率不高 容易产生碎片化。

    标记清除之前效果图:

    绿色代表未被引用的空间,黄色代表被引用的空间。

    标记清除之后效果图:

    白色代表标记清除的空间,出现了空间不连续的情况,而这些不连续的空间只能存储少于等于自身空间大小的对象,所以空间利用率比较低。

    3.2 标记整理算法

    根据gcRoot对象的引用链,发现如果该对象没有被引用的情况下,则标记为垃圾。

    标记整理与标记清除区别在于:避免标记清除算法产生的碎片问题,清除垃圾过程中,会将可用的对象实现移动,内存空间更加具有连续性。

    优点:没有内存的碎片问题,空间地址保持连续。

    缺点:整理过程中会产生内存地址移动,在移动过程中其他线程无法访问堆内存,效率偏低。Stop-The-World

    效果图如下所示:

    3.3 标记复制算法

    当我们堆内存触发gc的时候,在from区中将可用对象拷贝到to中,在直接将整个from区清空,依次循环切换。

    优点:不会产生内存碎片

    缺点:比较占内存空间,有二块空间分为两个区域:from to区 相等。

    标记复制之前效果图:

    标记复制之后,经过清理后效果图:

     

    4.分代算法

    对我们堆内存空间实现分代,核心分为新生代、老年代。在整个堆内存中,新生代触发GC回收次数比老年代多。

    4.1:新生代

    刚创建的对象都存在新生代空间,存放生命周期较短的对象的区域。新生代中分为eden区、from区、to区。默认情况下,新生代中eden区、from区、to区比例为8:1:1.

    刚创建的对象存放在eden区,当eden区满的时候,幸存的对象晋升存放到from区或者是to区,from区和to区使用的是标记复制算法。

    4.2:老年代

    存放生命周期较长的对象的区域。

    哪些对象会晋升到老年代中

    1:年限达到。当 GC 多次回收的时候,如果一直引用能达到一个阈值的情况下,直接晋升到老年代。阈值可以自己设置

    例子:假如有二个对象A、B经过多次的新生代GC回收之后依然还被引用的话,就会晋升到老年代。

    2:大对象。如果存放的对象的内存大于新生代空间的内存,则直接存放到老年代中。

    例子:假如新生代空间为5M、老年代空间为10M,如果这时候创建的对象为6M,这时候对象则会直接存放到老年代空间中。如果创建的对象为11M,则会报OM(内存溢出)异常。

    相同点: 都存储在Java堆上。

    不同点:新生代触发的是MinorGC、老年代触发的是FullGC。默认情况下,新生代与老年代存储空间比例为1:2。

    4.3:程序发生内存溢出的原因

    存放对象的空间大小大于我们老年代的空间大小。

    4.4:为什么在分代算法中,新生代有from区和to区且大小一样

    因为新生代中 GC 触发非常频繁,为了能够更加高效的清理垃圾,所以采用标记复制算法。

    4.5:分代算法中,老年代为什么使用标记整理算法

    因为在老年代空间满的情况下会触发 FullGC进行垃圾回收,FullGC 垃圾回收会同时回收新生代、老年代、元空间三个区域。

    代码演示:

    -Xms18m -Xmx18m -XX:+PrintGCDetails -verbose:gc

    设置堆初始空间、最大空间为20M,并在控制台打印出详细的垃圾回收信息。

    18M空间:新生代与老年代空间比例为1:2,新生代占6M ,老年代占12M。

     1 public class Test05 {
     2 
     3     public static void main(String[] args) {
     4         // -Xms18m -Xmx18m  -XX:+PrintGCDetails -verbose:gc
     5         ArrayList<Object> objects = new ArrayList<>();
     6         objects.add(new byte[1024 * 1024 * 15]);
     7     }
     8 }
     9 
    10 
    11 控制台输出结果:
    12 [GC (Allocation Failure) [PSYoungGen: 2617K->504K(5632K)] 2617K->1011K(17920K), 0.0016494 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    13 [GC (Allocation Failure) [PSYoungGen: 504K->488K(5632K)] 1011K->1031K(17920K), 0.0006205 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    14 [Full GC (Allocation Failure) [PSYoungGen: 488K->0K(5632K)] [ParOldGen: 543K->981K(12288K)] 1031K->981K(17920K), [Metaspace: 3203K->3203K(1056768K)], 0.0069905 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
    15 [GC (Allocation Failure) [PSYoungGen: 0K->0K(5632K)] 981K->981K(17920K), 0.0003778 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    16 [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(5632K)] [ParOldGen: 981K->953K(12288K)] 981K->953K(17920K), [Metaspace: 3203K->3203K(1056768K)], 0.0062126 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    17 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    18     at com.example.javareference.Test05.main(Test05.java:17)
    19 Heap
    20  PSYoungGen      total 5632K, used 301K [0x00000000ffa00000, 0x0000000100000000, 0x0000000100000000)
    21   eden space 5120K, 5% used [0x00000000ffa00000,0x00000000ffa4b5b8,0x00000000fff00000)
    22   from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
    23   to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
    24  ParOldGen       total 12288K, used 953K [0x00000000fee00000, 0x00000000ffa00000, 0x00000000ffa00000)
    25   object space 12288K, 7% used [0x00000000fee00000,0x00000000feeee4c0,0x00000000ffa00000)
    26  Metaspace       used 3292K, capacity 4496K, committed 4864K, reserved 1056768K
    27   class space    used 358K, capacity 388K, committed 512K, reserved 1048576K

    由上面控制台打印信息看出第11、12、14行进行 GC 垃圾回收在新生代区域(PSYoungGen)。

    第13、15行进行 FullGC 垃圾回收,包括 新生代(PSYoungGen)、老年代(ParOldGen)、元空间(Metaspace) 三个区域

    [PSYoungGen: 2617K->504K(5632K)]:表示新生代空间回收之前是2617k,回收之后是504k,5632k是整个新生代空间的大小。等于20行:total 5632K。

    19行以下打印的是 新生代、老年代、元空间的内存使用情况。

    5.GC日志分析工具

    GCViewerGCEasyGCHistoGCLogViewer Hpjmetergarbagecat

    https://gceasy.io/

  • 相关阅读:
    iOS开源控件库收集
    Ruby中的几种除法
    Font
    PlaySound
    STL
    APIs
    cin and cout
    CreateWindow
    Introducing Direct2D
    VC 常用代码
  • 原文地址:https://www.cnblogs.com/ming-blogs/p/14491571.html
Copyright © 2020-2023  润新知