JVM の 4 つの参照状態
「Java 仮想マシン 5: Java ガベージ コレクション (GC) メカニズムの詳細な説明」の記事で、JVM の 4 つの参照状態について簡単に説明しました。は簡単に学んだばかりで、4 つの基準状態についてほとんど理解していない状態で、そのような概念があることを知りました。ここ 2 日間で仮想マシンの部分を読み直して、いくつかの JVM リファレンスを詳しく調べるためにサンプルをたくさん書きました。JVM リファレンスについての理解がかなり深まったので、それを要約して共有するために記事を書きました。
まず、JVM の 4 つの参照状態から始めましょう。この部分は、Zhou Zhiming 氏の書籍『Java 仮想マシンの詳細理解: JVM の高度な機能とベスト プラクティス』からの抜粋です。
JDK1.2 より前の Java における参照の定義は非常に伝統的でした: 参照型データに格納された値が別のメモリの開始アドレスを表す場合、このメモリは参照を表すと言われます 。この定義は非常に純粋ですが、オブジェクトはこの方法で参照できるか参照できないことしかできず、「食べるには味がなく、捨てるには惜しい」オブジェクトを説明するのは無力です。私たちはそのようなタイプのオブジェクトを記述したいと考えています: メモリ空間がまだ十分にある場合は、メモリ空間に保持できますが、ガベージ コレクション後にメモリ空間がまだ非常に不足している場合は、これらのオブジェクトを破棄できます (前段落と同じ青文字の対照学習)。多くのシステムのキャッシュ機能は、この参照シナリオに準拠しています。
JDK1.2以降、Javaでは参照の概念が拡張され、参照が強参照、ソフト参照、弱参照、ファントム参照の4種類に分けられ、この4つの引用強度が一気に弱まりました。
強い参照とは、「Object obj = new Object()」と同様に、プログラム コード内に遍在する参照を指します。 強い参照が存在する限り、ガベージ コレクターはそれらを再利用することはありません。オブジェクト
ソフト参照は、便利ではあるが必須ではないいくつかのオブジェクトを記述するために使用されます ソフト参照に関連付けられたオブジェクトの場合、これらはシステムがメモリ オーバーフロー例外を経験する前に保存されます。オブジェクトは2回目のリサイクルのリサイクル範囲に含まれます。このリサイクルに十分なメモリがない場合、メモリ オーバーフロー例外がスローされます。 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 21248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000) 9 the space 21248K, 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(21248K)], 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 21248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)11 the space 21248K, 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(21248K)], 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 21248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)11 the space 21248K, 20% used [0x00000000fae00000, 0x00000000fb243dc0, 0x00000000fb243e00, 0x00000000fc2c0000)12 No shared spaces configured.
从GC详情我们可以看到:
老年代只使用了1064K大小的内存
新生代只使用了284K大小的内存
很显然4M的byte数组被回收。
由这个例子我们回顾可以作为GC Roots的对象:
虚拟机栈(栈帧中的本地变量表)中引用的对象,比如在方法中定义"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(21248K)], 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 21248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)13 the space 21248K, 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(21248K)], 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(21248K)], 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 21248K, used 4366K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)22 the space 21248K, 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
所以,很多时候对一些非必需的对象,我们可以将直接将其与软引用关联,这样内存不够时会先回收软引用关联的对象而不会抛出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(21248K)], 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 21248K, used 4367K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)13 the space 21248K, 20% used [0x00000000fae00000, 0x00000000fb243dc8, 0x00000000fb243e00, 0x00000000fc2c0000)14 No shared spaces configured.
看到GC后bytes为null了,且新生代、老年代中也没见到有4M以上的大对象,从两个角度都证明了,GC之后4M的byte数组被回收了。
Reference与ReferenceQueue
前面用代码验证了强引用、软应用、弱引用三种引用状态,虚引用就不演示了,记住虚引用是用于跟踪对象的回收状态就够了。
下面再讲一个知识点ReferenceQueue,ReferenceQueue的作用分点讲解下:
SoftReference、WeakReference、PhantomReference,在构造的时候可以通过构造函数传入一个ReferenceQueue,但是只有PhantomReference,ReferenceQueue是必须的
以SoftReference为例,一个类型为SoftReference的sr关联了一个4M的byte数组,那么当内存不够的时候,回收此4M的byte数组,sr.get()为null,表示sr不再关联此4M的byte数组
当sr对应的4M的byte数组被回收之后,sr本身被加入ReferenceQueue中,表示此软引用关联的对象被回收
ReferenceQueue本身是一个Queue,可通过poll()方法不断拿到队列的头元素,如果是null表示没有被回收的软引用关联的对象,如果不是null表示有软引用关联的对象被回收
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
メモリ不足のため、最初の 3 つのソフト参照がリサイクルされ、リサイクルされた 3 つのソフト参照のメモリ アドレスがすべて一致していることがわかります。これは、上記のステートメントを証明しています。
以上がJVM の 4 つの参照状態の紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。