0

0

jvm的四种引用状态介绍

零下一度

零下一度

发布时间:2017-06-28 10:00:05

|

1678人浏览过

|

来源于php中文网

原创

jvm的四种引用状态

在Java虚拟机5:Java垃圾回收(GC)机制详解一文中,有简单提到过JVM的四种引用状态,当时只是简单学习,知道有这么一个概念,对四种引用状态理解不深。这两天重看虚拟机这部分的时候,写了很多例子详细研究了一下JVM的几种引用,对于JVM的引用理解加深了不少,因此总结写一篇文章总结并分享下。

首先,还是先从JVM四种引用状态开始,这部分摘抄自周志明老师的《深入理解Java虚拟机:JVM高级特性与最佳实践》一书。

在JDK1.2之前,Java中的引用的定义很传统:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。这种定义很纯粹,但是太过狭隘,一个对象在这种顶一下只有被引用或者没有被引用两种状态,对于如何描述一些"食之无味,弃之可惜"的对象就显得无能为力。我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象(注意和前面一段蓝字的对比学习)。很多系统的缓存功能都符合这样的引用场景。

在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4中引用强度一次减弱。

  • 强引用就是指在程序代码之中普遍存在的,类似"Object obj = new Object()"这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象

  • 软引用是用来描述一些还有用但并非必需的对象,对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用

  • 弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象,只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供了WeakReference类来实现弱引用

  • 虚引用也成为幽灵引用或者幻影引用,它是最弱的一中引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK1.2之后,提供给了PhantomReference类来实现虚引用

 

写于代码开始前

在通过代码研究几种引用状态之前,先定义一些参数,后面所有部分的代码示例都使用这些参数。

首先是JVM的参数,这里我使用的是:

-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+UseParNewGC -verbose:gc -XX:+PrintGCDetails

这意味着:

  • 堆大小固定为20M

  • 新生代大小为10M,SurvivorRatio设置为8,则Eden区大小=8M,每个Survivor区大小=1M,每次有9M的新生代内存空间可用来new对象

  • 新生代使用使用ParNew收集器,Server模式下默认是Parallel收集器,不过这个收集器的GC日志我看着没有ParNew收集器的GC日志舒服,因此就改成ParNew收集器了

  • 当发生GC的时候打印GC的简单信息,当程序运行结束打印GC详情

其次,再定义一个常量类"_1MB":

    _1MB = 1024 * 1024

代码示例使用byte数组,每个byte为1个字节,因此定义一个"_1MB"的常量就可以方便得到1M、2M、3M...的内存空间了。

 

强引用的研究

关于强引用的研究,研究的重点在于验证"当一个对象到GC Roots没有任何引用链相连,则证明此对象是不可用的(要被回收)"这句话的正确性。虚拟机参数上面列了,首先写一个空方法:

 1 /** 2  * @author 五月的仓颉 3  */ 4 @Test 5 public void testStrongReference0() { 6  7 }

程序运行结果为:

 1 Heap 2  par new generation   total 9216K, used 3699K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) 3   eden space 8192K,  45% used [0x00000000f9a00000, 0x00000000f9d9cdc0, 0x00000000fa200000) 4   from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) 5   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) 6  tenured generation   total 10240K, used 0K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) 7    the space 10240K,   0% used [0x00000000fa400000, 0x00000000fa400000, 0x00000000fa400200, 0x00000000fae00000) 8  compacting perm gen  total 21241248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) 9    the space 21241248K,  20% used [0x00000000fae00000, 0x00000000fb243d88, 0x00000000fb243e00, 0x00000000fc2c0000)10 No shared spaces configured.

这意味着新生代中本身就有3699K的内存空间。很好理解,因为虚拟机启动的时候就会加载一部分数据到内存中,这部分数据的大小为3699K。

下一步我们放一个4M的byte数组进去(4M是因为找一个相对大点的数字,结果会比较明显),4M=4096K,加上原来的3966K等于8062K。对这8062K内存空间触发一次GC:

 1 /** 2  * @author 五月的仓颉 3  */ 4 @Test 5 public void testStrongReference0() { 6     System.out.println("**********强引用测试(放一个4M的数组,触发GC)**********"); 7          8     byte[] bytes = new byte[4 * _1MB]; 9         10     // 手动触发GC11     System.gc();12 }

运行结果为:

 1 **********强引用测试(放一个4M的数组,触发GC)********** 2 [Full GC[Tenured: 0K->5161K(10240K), 0.0085630 secs] 7958K->5161K(19456K), [Perm : 4354K->4354K(21241248K)], 0.0086002 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
 3 Heap 4  par new generation   total 9216K, used 284K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) 5   eden space 8192K,   3% used [0x00000000f9a00000, 0x00000000f9a47300, 0x00000000fa200000) 6   from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) 7   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) 8  tenured generation   total 10240K, used 5161K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) 9    the space 10240K,  50% used [0x00000000fa400000, 0x00000000fa90a548, 0x00000000fa90a600, 0x00000000fae00000)10  compacting perm gen  total 21241248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)11    the space 21241248K,  20% used [0x00000000fae00000, 0x00000000fb243dc0, 0x00000000fb243e00, 0x00000000fc2c0000)12 No shared spaces configured.

总结一下这次GC的结果(由于这篇不是研究内存分配的文章,因此我们只关注结果,至于过程到底为什么就不细究了):

  • 新生代中只留下了284K大小的对象

  • 7958K大小的对象被移到了老年代中

  • 7958K大小的对象被进行了一次回收,剩余5161K大小的对象

总结起来就是4M的byte数组并没有被回收(因为总共有5161K的对象,虚拟机启动的时候也才加载了3699K不到5161K,那4M的byte数组肯定是在的),原因是有bytes引用指向4M的byte数组。既然如此,我们把bytes置空看看结果如何:

 1 /** 2  * @author 五月的仓颉 3  */ 4 @Test 5 public void testStrongReference0() { 6     System.out.println("**********强引用测试(放一个4M的数组,bytes置空,触发GC)**********"); 7          8     byte[] bytes = new byte[4 * _1MB]; 9         10     bytes = null;11         12     // 手动触发GC13     System.gc();14 }

运行结果为:

 1 **********强引用测试(放一个4M的数组,bytes置空,触发GC)********** 2 [Full GC[Tenured: 0K->1064K(10240K), 0.0096213 secs] 7958K->1064K(19456K), [Perm : 4354K->4354K(21241248K)], 0.0096644 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
 3 Heap 4  par new generation   total 9216K, used 284K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) 5   eden space 8192K,   3% used [0x00000000f9a00000, 0x00000000f9a47300, 0x00000000fa200000) 6   from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) 7   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000) 8  tenured generation   total 10240K, used 1064K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000) 9    the space 10240K,  10% used [0x00000000fa400000, 0x00000000fa50a368, 0x00000000fa50a400, 0x00000000fae00000)10  compacting perm gen  total 21241248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)11    the space 21241248K,  20% used [0x00000000fae00000, 0x00000000fb243dc0, 0x00000000fb243e00, 0x00000000fc2c0000)12 No shared spaces configured.

从GC详情我们可以看到:

  • 老年代只使用了1064K大小的内存

  • 新生代只使用了284K大小的内存

很显然4M的byte数组被回收

由这个例子我们回顾可以作为GC Roots的对象:

PHP房产程序[BBWPS]
PHP房产程序[BBWPS]

[PHP房产程序|BBWPS]功能介绍 1、5种信息类别发布:出租、求租、出售、求购、楼盘信息,支持会员发布信息审核; 2、灵活的信息参数设置; 3、充足的信息字段; 4、简单易用的发布/编辑功能,支持配图上传; 5、灵活的信息管理功能; 6、信息输出伪静态,方便搜索引擎抓取数据; 7、支持RSS输出; 8、内置数据高速缓冲技术,可灵活设置缓冲功能是否启动及过期时间; 9、支持 Google 地图

下载
  • 虚拟机栈(栈帧中的本地变量表)中引用的对象,比如在方法中定义"Object obj = new Object();"

  • 方法区中类静态属性引用的对象,比如在类中定义"private static Object lock = new Object();",将Object对象作为一个锁,所有类共享

  • 方法区中常量引用的对象,比如在接口中定义"public static final char c = 'a';",字符'a'是一个常量

  • 本地方法栈中JNI(即一般说的Native方法)引用的对象,这个不好找例子

这次的回收正是因为第一条。本身有bytes(在虚拟机栈中)指向4M的byte数组,由于将bytes置空。因此4M的byte数组此时没有任何一个可以作为GC Roots对象的引用指向它,即4M的byte数组被虚拟机标记为可回收的垃圾,在GC时被回收。

稍微扩展一下,这里上面代码的做法是手动将bytes置空,其实方法调用结束也是一样的,栈帧消失,栈帧消失意味着bytes消失,那么4M的byte数组同样没有任何一个可以作为GC Roots对象的引用指向它,因此方法调用结束之后,4M的byte数组同样会被虚拟机标记为可回收的垃圾,在GC时被回收。

 

软引用的研究

软引用之前说过了,JDK提供了SoftReference类共开发者使用,那我们就利用SoftReference研究一下软引用,测试代码为:

 1 /** 2  * @author 五月的仓颉 3  */ 4 @Test 5 public void testSoftReference0() { 6     System.out.println("**********软引用测试**********"); 7          8     byte[] bytes = new byte[4 * _1MB]; 9     SoftReference<byte[]> sr = new SoftReference<byte[]>(bytes);10     System.out.println("GC前:" + sr.get());11         12     bytes = null;13         14     System.gc();15     System.out.println("GC后:" + sr.get());16 }

同样的new一个4M的byte数组,通过SoftReference构造方法放到SoftReference中。

这段代码最值得注意的是第9行"bytes=null"这一句,如果不将bytes置空,那么4M的byte数组还与强引用关联着,内存不够虚拟机将抛出异常而不会尝试回收它;将bytes置空则不一样,4M的byte数组失去了强引用,但是它又在SoftReference中,这意味着这个4M的byte数组目前仅仅与软引用关联

运行一下程序,结果为:

 1 **********软引用测试********** 2 GC前:[B@76404629 3 [Full GC[Tenured: 0K->5161K(10240K), 0.0094088 secs] 7953K->5161K(19456K), [Perm : 4354K->4354K(21241248K)], 0.0094428 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
 4 GC后:[B@76404629 5 Heap 6  par new generation   total 9216K, used 284K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) 7   eden space 8192K,   3% used [0x00000000f9a00000, 0x00000000f9a47330, 0x00000000fa200000) 8   from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) 9   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)10  tenured generation   total 10240K, used 5161K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)11    the space 10240K,  50% used [0x00000000fa400000, 0x00000000fa90a778, 0x00000000fa90a800, 0x00000000fae00000)12  compacting perm gen  total 21241248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)13    the space 21241248K,  20% used [0x00000000fae00000, 0x00000000fb243f10, 0x00000000fb244000, 0x00000000fc2c0000)14 No shared spaces configured.

看到GC前后,bytes都是"[B@76404629",很显然4M的byte数组并没有被回收。从内存空间来看,老年代中使用了5161K,和之前强引用测试是一样的,证明了这一点。

那我们怎么能看到弱引用的回收呢?既然弱引用是发生在内存不够之前,那只需要不断实例化byte数组,然后将之与软引用关联即可,代码为:

 1 /** 2  * @author 五月的仓颉 3  */ 4 @Test 5 public void testSoftReference1() { 6     System.out.println("**********软引用测试**********"); 7              8     SoftReference<byte[]> sr0 = new SoftReference<byte[]>(new byte[4 * _1MB]); 9     SoftReference<byte[]> sr1 = new SoftReference<byte[]>(new byte[4 * _1MB]);10     SoftReference<byte[]> sr2 = new SoftReference<byte[]>(new byte[4 * _1MB]);11     SoftReference<byte[]> sr3 = new SoftReference<byte[]>(new byte[4 * _1MB]);12     SoftReference<byte[]> sr4 = new SoftReference<byte[]>(new byte[4 * _1MB]);13     SoftReference<byte[]> sr5 = new SoftReference<byte[]>(new byte[4 * _1MB]);14             15     System.out.println(sr0.get());16     System.out.println(sr1.get());17     System.out.println(sr2.get());18     System.out.println(sr3.get());19     System.out.println(sr4.get());20     System.out.println(sr5.get());21 }

运行结果为:

 1 **********软引用测试********** 2 [GC[ParNew: 7958K->1024K(9216K), 0.0041103 secs] 7958K->5187K(19456K), 0.0041577 secs] [Times: user=0.05 sys=0.00, real=0.00 secs] 
 3 [GC[ParNew: 5203K->331K(9216K), 0.0036532 secs] 9366K->9481K(19456K), 0.0036694 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 4 [GC[ParNew: 4427K->4427K(9216K), 0.0000249 secs][Tenured: 9149K->9149K(10240K), 0.0054937 secs] 13577K->13246K(19456K), [Perm : 4353K->4353K(21241248K)], 0.0055600 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
 5 [Full GC[Tenured: 9149K->783K(10240K), 0.0071252 secs] 13246K->783K(19456K), [Perm : 4353K->4352K(21241248K)], 0.0071560 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
 6 [GC[ParNew: 4096K->41K(9216K), 0.0010362 secs] 4879K->4921K(19456K), 0.0010745 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 7 [GC[ParNew: 4137K->10K(9216K), 0.0009216 secs] 9017K->8986K(19456K), 0.0009366 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 8 null 9 null10 null11 [B@4783165b12 [B@6f30d50a13 [B@6ef2bc8d14 Heap15  par new generation   total 9216K, used 4307K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)16   eden space 8192K,  52% used [0x00000000f9a00000, 0x00000000f9e32560, 0x00000000fa200000)17   from space 1024K,   1% used [0x00000000fa200000, 0x00000000fa202978, 0x00000000fa300000)18   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)19  tenured generation   total 10240K, used 8975K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)20    the space 10240K,  87% used [0x00000000fa400000, 0x00000000facc3f40, 0x00000000facc4000, 0x00000000fae00000)21  compacting perm gen  total 21241248K, used 4366K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)22    the space 21241248K,  20% used [0x00000000fae00000, 0x00000000fb2439e0, 0x00000000fb243a00, 0x00000000fc2c0000)23 No shared spaces configured.

从第8行~第13行的结果来看,前三个4M的byte数组被回收了,后三个4M的byte数组还在,这就证明了"被软引用关联的对象会在内存不够时被回收"。

这段代码我们可以做一个对比思考:

  • 如果4M的byte数组没有被软引用关联而是被强引用关联,且不释放强引用,那么new到第4个4M的byte数组时就会报错,因为老年代总共只有10M,前两个4M的byte数组可以进入老年代,第3个4M的byte数组new出来的时候放入新生代,但是当第四个4M的byte数组new出来的时候,第3个4M的byte数组却没法进入老年代(因为3个4M=12M,大于老年代的10M),虚拟机抛出OutOfMemoryError

  • 如果4M的byte数组被软引用关联且强引用已经释放,那么可以无限写"SoftReference sr = new SoftReference(new byte[4 * _1MB]);"这句代码,因为内存不够了就回收4M的byte数组,永远没有内存溢出的可能

所以,很多时候对一些非必需的对象,我们可以将直接将其与软引用关联,这样内存不够时会先回收软引用关联的对象而不会抛出OutOfMemoryError,毕竟抛出OutOfMemoryError意味着整个应用将停止运行。

 

弱引用的研究

JDK给我们提供的了WeakReference用以将一个对象关联到弱引用,弱引用的测试比较简单,代码为:

 1 /** 2  * @author 五月的仓颉 3  */ 4 @Test 5 public void testWeakReference() { 6     System.out.println("**********弱引用测试**********"); 7          8     WeakReference<byte[]> wr = new WeakReference<byte[]>(new byte[4 * _1MB]); 9     System.out.println("GC前:" + wr.get());10         11     System.gc();12     System.out.println("GC后:" + wr.get());13 }

我这里不定义一个强引用直接关联4M的byte数组(避免忘了将对象与强引用的关联取消),这也是使用SoftReference、WeakReference时我个人比较推荐的做法。程序运行的结果为:

 1 **********弱引用测试********** 2 GC前:[B@21dd63a8 3 [Full GC[Tenured: 0K->1065K(10240K), 0.0080353 secs] 7958K->1065K(19456K), [Perm : 4353K->4353K(21241248K)], 0.0080894 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
 4 GC后:null 5 Heap 6  par new generation   total 9216K, used 284K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000) 7   eden space 8192K,   3% used [0x00000000f9a00000, 0x00000000f9a47318, 0x00000000fa200000) 8   from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000) 9   to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)10  tenured generation   total 10240K, used 1065K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)11    the space 10240K,  10% used [0x00000000fa400000, 0x00000000fa50a6e8, 0x00000000fa50a800, 0x00000000fae00000)12  compacting perm gen  total 21241248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)13    the space 21241248K,  20% used [0x00000000fae00000, 0x00000000fb243dc8, 0x00000000fb243e00, 0x00000000fc2c0000)14 No shared spaces configured.

看到GC后bytes为null了,且新生代、老年代中也没见到有4M以上的大对象,从两个角度都证明了,GC之后4M的byte数组被回收了。

 

Reference与ReferenceQueue

前面用代码验证了强引用、软应用、弱引用三种引用状态,虚引用就不演示了,记住虚引用是用于跟踪对象的回收状态就够了。

下面再讲一个知识点ReferenceQueue,ReferenceQueue的作用分点讲解下:

  1. SoftReference、WeakReference、PhantomReference,在构造的时候可以通过构造函数传入一个ReferenceQueue,但是只有PhantomReference,ReferenceQueue是必须的

  2. 以SoftReference为例,一个类型为SoftReference的sr关联了一个4M的byte数组,那么当内存不够的时候,回收此4M的byte数组,sr.get()为null,表示sr不再关联此4M的byte数组

  3. 当sr对应的4M的byte数组被回收之后,sr本身被加入ReferenceQueue中,表示此软引用关联的对象被回收

  4. ReferenceQueue本身是一个Queue,可通过poll()方法不断拿到队列的头元素,如果是null表示没有被回收的软引用关联的对象,如果不是null表示有软引用关联的对象被回收

  5. SoftReference是这样的,WeakReference与PhantomReference同理

讲完理论,用代码验证一下,还是使用软引用,不过为了显示更清楚把GC显示相关参数(-verbose:gc  -XX:+PrintGCDetails)去掉,代码为:

 1 /** 2  * @author 五月的仓颉 3  */ 4 @Test 5 public void testReferenceQueue() { 6     System.out.println("**********引用队列测试**********\n"); 7          8     ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<byte[]>(); 9         10     SoftReference<byte[]> sr0 = new SoftReference<byte[]>(new byte[4 * _1MB], referenceQueue);11     SoftReference<byte[]> sr1 = new SoftReference<byte[]>(new byte[4 * _1MB], referenceQueue);12     SoftReference<byte[]> sr2 = new SoftReference<byte[]>(new byte[4 * _1MB], referenceQueue);13     SoftReference<byte[]> sr3 = new SoftReference<byte[]>(new byte[4 * _1MB], referenceQueue);14     SoftReference<byte[]> sr4 = new SoftReference<byte[]>(new byte[4 * _1MB], referenceQueue);15     SoftReference<byte[]> sr5 = new SoftReference<byte[]>(new byte[4 * _1MB], referenceQueue);16         17     System.out.println("**********软引用关联的对象展示**********");18     System.out.println(sr0 + "---" + sr0.get());19     System.out.println(sr1 + "---" + sr1.get());20     System.out.println(sr2 + "---" + sr2.get());21     System.out.println(sr3 + "---" + sr3.get());22     System.out.println(sr4 + "---" + sr4.get());23     System.out.println(sr5 + "---" + sr5.get());24         25     System.out.println("**********引用队列中的SoftReference展示**********");26     System.out.println(referenceQueue.poll());27     System.out.println(referenceQueue.poll());28     System.out.println(referenceQueue.poll());29     System.out.println(referenceQueue.poll());30     System.out.println(referenceQueue.poll());31     System.out.println(referenceQueue.poll());32 }

运行结果为:

 1 **********引用队列测试********** 2  3 **********软引用关联的对象展示********** 4 java.lang.ref.SoftReference@50ed0a5---null 5 java.lang.ref.SoftReference@fa4033b---null 6 java.lang.ref.SoftReference@58d01e82---null 7 java.lang.ref.SoftReference@4783165b---[B@6f30d50a 8 java.lang.ref.SoftReference@6ef2bc8d---[B@23905e3 9 java.lang.ref.SoftReference@6db17b38---[B@1f10d1cb10 **********引用队列中的SoftReference展示**********11 java.lang.ref.SoftReference@50ed0a512 java.lang.ref.SoftReference@fa4033b13 java.lang.ref.SoftReference@58d01e8214 null15 null16 null

看到由于内存不够,回收了前三个软引用,且回收的三个软引用内存地址都能对得上,这证明了上面的说法。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

智谱清言 - 免费全能的AI助手
智谱清言 - 免费全能的AI助手

智谱清言 - 免费全能的AI助手

相关专题

更多
batoto漫画官网入口与网页版访问指南
batoto漫画官网入口与网页版访问指南

本专题系统整理batoto漫画官方网站最新可用入口,涵盖最新官网地址、网页版登录页面及防走失访问方式说明,帮助用户快速找到batoto漫画官方平台,稳定在线阅读各类漫画内容。

24

2026.02.25

Steam官网正版入口与注册登录指南_新手快速进入游戏平台方法
Steam官网正版入口与注册登录指南_新手快速进入游戏平台方法

本专题系统整理Steam官网最新可用入口,涵盖网页版登录地址、新用户注册流程、账号登录方法及官方游戏商店访问说明,帮助新手玩家快速进入Steam平台,完成注册登录并管理个人游戏库。

1

2026.02.25

TypeScript全栈项目架构与接口规范设计
TypeScript全栈项目架构与接口规范设计

本专题面向全栈开发者,系统讲解基于 TypeScript 构建前后端统一技术栈的工程化实践。内容涵盖项目分层设计、接口协议规范、类型共享机制、错误码体系设计、接口自动化生成与文档维护方案。通过完整项目示例,帮助开发者构建结构清晰、类型安全、易维护的现代全栈应用架构。

0

2026.02.25

Python数据处理流水线与ETL工程实战
Python数据处理流水线与ETL工程实战

本专题聚焦 Python 在数据工程场景下的实际应用,系统讲解 ETL 流程设计、数据抽取与清洗、批处理与增量处理方案,以及数据质量校验与异常处理机制。通过构建完整的数据处理流水线案例,帮助开发者掌握数据工程中的性能优化思路与工程化规范,为后续数据分析与机器学习提供稳定可靠的数据基础。

0

2026.02.25

Java领域驱动设计(DDD)与复杂业务建模实战
Java领域驱动设计(DDD)与复杂业务建模实战

本专题围绕 Java 在复杂业务系统中的建模与架构设计展开,深入讲解领域驱动设计(DDD)的核心思想与落地实践。内容涵盖领域划分、聚合根设计、限界上下文、领域事件、贫血模型与充血模型对比,并结合实际业务案例,讲解如何在 Spring 体系中实现可演进的领域模型架构,帮助开发者应对复杂业务带来的系统演化挑战。

0

2026.02.25

Golang 生态工具与框架:扩展开发能力
Golang 生态工具与框架:扩展开发能力

《Golang 生态工具与框架》系统梳理 Go 语言在实际工程中的主流工具链与框架选型思路,涵盖 Web 框架、RPC 通信、依赖管理、测试工具、代码生成与项目结构设计等内容。通过真实项目场景解析不同工具的适用边界与组合方式,帮助开发者构建高效、可维护的 Go 工程体系,并提升团队协作与交付效率。

18

2026.02.24

Golang 性能优化专题:提升应用效率
Golang 性能优化专题:提升应用效率

《Golang 性能优化专题》聚焦 Go 应用在高并发与大规模服务中的性能问题,从 profiling、内存分配、Goroutine 调度、GC 机制到 I/O 与锁竞争逐层分析。结合真实案例讲解定位瓶颈的方法与优化策略,帮助开发者建立系统化性能调优思维,在保证代码可维护性的同时显著提升服务吞吐与稳定性。

9

2026.02.24

Golang 面试题精选:高频问题与解答
Golang 面试题精选:高频问题与解答

Golang 面试题精选》系统整理企业常见 Go 技术面试问题,覆盖语言基础、并发模型、内存与调度机制、网络编程、工程实践与性能优化等核心知识点。每道题不仅给出答案,还拆解背后的设计原理与考察思路,帮助读者建立完整知识结构,在面试与实际开发中都能更从容应对复杂问题。

5

2026.02.24

Golang 运行与部署实战:从本地到云端
Golang 运行与部署实战:从本地到云端

《Golang 运行与部署实战》围绕 Go 应用从开发完成到稳定上线的完整流程展开,系统讲解编译构建、环境配置、日志与配置管理、容器化部署以及常见运维问题处理。结合真实项目场景,拆解自动化构建与持续部署思路,帮助开发者建立可靠的发布流程,提升服务稳定性与可维护性。

5

2026.02.24

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 3.9万人学习

C# 教程
C# 教程

共94课时 | 10.2万人学习

Java 教程
Java 教程

共578课时 | 72万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号