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/

原文地址:https://www.cnblogs.com/ming-blogs/p/14491571.html