JVM中的各类引用

发布于:2025-06-06 ⋅ 阅读:(30) ⋅ 点赞:(0)

JVM中的各类引用


欢迎来到我的博客:TWind的博客

我的CSDN::Thanwind-CSDN博客

我的掘金:Thanwinde 的个人主页


对象

众所不周知,Java中基本所有的对象都是分配在堆内存之中的,除开基本数据类型在栈帧中以外,其他的对象全部都分配在堆中

众所不周知,堆内存是JVM中十分重要的一个区域,以至于想尽办法的开发出更加有效率,精巧的GC来回收这一部分的空间

你new了一个引用类型的对象之后,引用会留在栈帧(类似指针),指向堆中的一块内存,其就被这个对象“占用”了

但是随着程序的运行,你new的对象会越来越多,堆内存也会越来越小,直到到一个临界值:堆内存爆了。

实际上,在你的内存不够时:(新生代爆,老年代爆等等),JVM会先通过GC来尝试回收一部分内存,然而,GC会通过可达性算法来判断这个对象应不应该被回收,而可达性算法会遍历你的GC root,对于每一个被GC root引用或间接引用的 对象,都不会被GC回收掉

那就造成了一个问题:当你想实现一些“不是很重要”的大对象,在空间不够时可以选择性的先释放掉他们来供给其他对象使用来避免堆爆掉时,就不能用常规的定义方法来定义这些方法:因为一旦你对这些对象有操作,那它大概率会变成GC root(当然实际上没有这么随意) 这就导致了,GC根本不会把他们当成垃圾来处理,但还好,Java为我们提供了一种方法来解决这个问题:更改引用类型


引用类型

Java中的引用类型可以分为:强应用软引用弱引用虚引用,让我们一一解释:

强引用

强引用就像它的名字一样,是最强的引用:有点类似于化学中的共价键一样,很难断开

它普遍的存在:Object obj = new Object()便是一个典型的强引用

无论如何,一个强引用只要存在了就不会被回收,这里的“存在”指的是一个强引用直接或间接的被new出来了或是被间接的被其他强引用new出来

也就是说,基本你写代码时正常创建的对象,就全是强引用,永远不会被GC回收

那什么时候强引用对象会被回收呢?

当其除去软,虚之外的来自GC root引用都被销毁,就会被回收:最显而易见的就是引用所在的栈帧被销毁:栈帧里面的局部变量表存储着所有基本类型对象和引用类型在堆中的引用,当栈帧被销毁后,这个“引用”也会被直接销毁,这个强引用也就被断开了。这时候,还是堆中的这个对象只有这个强引用的话,他就会因为没有来自GC root的引用而被GC回收掉,但如果它拥有其他的来自GC root的强引用的话就不会被回收,但如果只拥有软引用的话那就得看空间紧不紧张了。

[!IMPORTANT]

GC root包含:

  • 虚拟机栈引用的对象
    例如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等

  • 处于存活状态的线程对象
    例如Thread

  • 方法区静态属性引用的对象
    例如java类的引用类型静态变量

  • 方法区常量引用的对象
    例如字符串常量池的引用

  • 本地native方法jni引用的对象

  • Java虚拟机内部的引用
    例如基本数据类型对应的class对象,一些常驻的异常对象(如NullPointerException、OutOfMemoryError),还有类加载器

  • 所有被同步锁持有的对象

  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

另外有一个显而易见的例子:

Object obj = new Object(); // 强引用 obj 指向一个新对象
obj = null;  // 强引用 obj 被置为 null,对象变成不可达对象

当这个强引用obj被设成null,就会导致刚才new 的Object()就没有引用指向它了,下次GC就会把它回收掉

值得一提的是,强引用不等于GC root,就比如你有一个类:

public class objClass(){
	Object obj = new Object();
	/*
	...
    */
}

但如果这个objClass根本就没有被用到,没有被加载,那里面的obj肯定就不能作为一个GC root了


软引用

软引用就是一个我们之前提到的:在空间不够时可以选择性的先释放掉他们来供给其他对象使用来避免堆爆掉的对象

相比起强引用,他更容易被回收:只要堆内存不够时,触发了GC,但这次GC并不会回收软引用,只有当第一次GC回收的内存仍然不够使用,进行第二次GC时才会将其回收掉

举个例子:

我们可以在虚拟机选项中加上: -Xms20m -Xmx20m -XX:+PrintGCDetails

这个选项会开启GC的输出,以及设定堆内存为20m

然后你可以运行类似于这样的代码:

    static int _5MB = 1024 * 1024 * 5;

    public static void main(String[] args) throws IOException {

        List<SoftReference<byte[]>> bytesList = new ArrayList<>();

        for(int i = 0; i < 5; i++) {
            bytesList.add(new SoftReference<>(new byte[_5MB]));
            for(int j = 0; j <= i;j++){
                System.out.print(bytesList.get(j).get());
            }
            System.out.println();
        }

    }

这样你就会看到如下结果:

[B@75b84c92
[B@75b84c92[B@6bc7c054
[GC (Allocation Failure) [PSYoungGen: 1738K->480K(6144K)] 11978K->11008K(19968K), 0.0007511 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@75b84c92[B@6bc7c054[B@232204a1
[GC (Allocation Failure) --[PSYoungGen: 5825K->5825K(6144K)] 16353K->16417K(19968K), 0.0006529 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 5825K->5460K(6144K)] [ParOldGen: 10592K->10499K(13824K)] 16417K->15960K(19968K), [Metaspace: 3301K->3301K(1056768K)], 0.0029366 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) --[PSYoungGen: 5460K->5460K(6144K)] 15960K->15992K(19968K), 0.0005618 secs] [Times: user=0.00 sys=0.20, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 5460K->0K(6144K)] [ParOldGen: 10531K->582K(13824K)] 15992K->582K(19968K), [Metaspace: 3301K->3301K(1056768K)], 0.0038522 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
nullnullnull[B@4aa298b7
nullnullnull[B@4aa298b7[B@7d4991ad

可以看到,在内存不够时触发了GC,第一次GC都没有清理出空间,都是第二次的full GC才清理掉这些软引用(变成了null)来腾出了空间


弱引用

顾名思义,弱引用是比软引用更加弱的一种引用,有多弱呢?

对于软引用,GC不会回收,只有full GC才会将其回收

但是弱引用,GC就会将其全部回收

同样的实例程序:

static int _5MB = 1024 * 1024 * 5;

public static void main(String[] args) throws IOException {

    List<WeakReference<byte[]>> bytesList = new ArrayList<>();

    for(int i = 0; i < 5; i++) {
        bytesList.add(new WeakReference<>(new byte[_5MB]));
        for(int j = 0; j <= i;j++){
            System.out.print(bytesList.get(j).get());
        }
        System.out.println();
    }

}

执行结果:

[B@75b84c92
[B@75b84c92[B@6bc7c054
[GC (Allocation Failure) [PSYoungGen: 1738K->488K(6144K)] 11978K->11008K(19968K), 0.0006141 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@75b84c92[B@6bc7c054[B@232204a1
[GC (Allocation Failure) [PSYoungGen: 5833K->504K(6144K)] 16353K->11088K(19968K), 0.0007155 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@75b84c92[B@6bc7c054null[B@4aa298b7
[GC (Allocation Failure) [PSYoungGen: 5792K->504K(6144K)] 16376K->11152K(19968K), 0.0004317 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@75b84c92[B@6bc7c054nullnull[B@7d4991ad
Heap
 PSYoungGen      total 6144K, used 5828K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
  eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3220,0x00000000fff00000)
  from space 512K, 98% used [0x00000000fff00000,0x00000000fff7e010,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 13824K, used 10648K [0x00000000fec00000, 0x00000000ff980000, 0x00000000ff980000)
  object space 13824K, 77% used [0x00000000fec00000,0x00000000ff666060,0x00000000ff980000)
 Metaspace       used 3329K, capacity 4564K, committed 4864K, reserved 1056768K
  class space    used 357K, capacity 388K, committed 512K, reserved 1048576K

可以看到,完全没有触发full GC,说明在GC阶段就已经把弱引用回收了


虚引用

虚引用是一类特殊的引用,特殊到它实际上没有引用:比起引用,它更像是一个监听器,当它指向的对象被回收时,他会被加入到引用队列中起到了一个“通知”的作用

比如如下程序:

static int _5MB = 1024 * 1024 * 5;

public static void main(String[] args) throws IOException, InterruptedException {

    byte[] data = new byte[_5MB];

    ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>();

    PhantomReference<byte[]> phantomReference = new PhantomReference<>(data, referenceQueue);

    List<PhantomReference<byte[]>> phantomList = new ArrayList<>();
    phantomList.add(phantomReference);

    System.out.println("Before GC: Phantom Reference = " + phantomReference.get());
    
    data = null;
    
    System.gc();
    
    Thread.sleep(1000);
    
    System.out.println("After GC: Phantom Reference = " + phantomReference.get());
    
    Reference<? extends byte[]> refFromQueue = referenceQueue.poll();
    if (refFromQueue != null) {
        System.out.println("Object has been garbage collected and added to the queue.");
    } else {
        System.out.println("Object is not yet garbage collected.");
    }

}

结果是:

Before GC: Phantom Reference = null
After GC: Phantom Reference = null
Object has been garbage collected and added to the queue.

因为其是虚引用,所以尝试get当然获得不到对象,返回null

而当其引用的对象被GC回收后,其也被加入到了referenceQueue之中


Java的引用类型大概就是这些类容,但是这这是JVM内存管理的冰山一角,后面我会写一些文章来介绍Java最为重要的GC部分


网站公告

今日签到

点亮在社区的每一天
去签到