jvm의 네 가지 참조 상태 소개
JVM의 4가지 참조 상태
Java Virtual Machine 5: Java Garbage Collection(GC) 메커니즘에 대한 자세한 설명에서 JVM의 4가지 참조 상태에 대해 간략하게 언급했습니다. 단지 간략하게 배운 것 뿐이었고, 네 가지 참조 상태에 대한 이해가 거의 없는 개념이 있다는 것을 알았습니다. 지난 이틀 동안 가상 머신 부분을 다시 읽으면서 여러 JVM 레퍼런스를 자세히 공부하기 위해 많은 예제를 작성했습니다. JVM 레퍼런스에 대한 이해가 많이 깊어져서 이를 요약하고 공유하기 위해 글을 썼습니다.
먼저 JVM의 네 가지 참조 상태부터 시작하겠습니다. 이 부분은 Zhou Zhiming의 저서 "Java Virtual Machine에 대한 심층적 이해: JVM 고급 기능 및 모범 사례"에서 발췌한 내용입니다.
JDK1.2 이전에는 Java에서 참조의 정의가 매우 전통적이었습니다. 참조 유형 데이터에 저장된 값이 다른 메모리의 시작 주소를 나타내는 경우 이 메모리는 참조를 나타낸다고 합니다. 이 정의는 매우 순수하지만 너무 좁습니다. 객체는 이렇게만 참조할 수 있거나 참조할 수 없으며 "먹기에는 맛이 없고 버리기에는 아까운" 일부 객체를 설명하는 것은 무력합니다. 우리는 다음과 같은 유형의 개체를 설명하고자 합니다. 메모리 공간이 여전히 충분하면 메모리에 보관할 수 있습니다. 가비지 수집 후에도 메모리 공간이 여전히 부족하면 이러한 개체를 삭제할 수 있습니다. (참고) 이전 단락 파란색 글자의 대조 학습과 동일). 많은 시스템의 캐싱 기능은 이 참조 시나리오를 따릅니다.
JDK1.2 이후 Java에서는 참조의 개념을 확장하여 참조를 Strong Reference, Soft Reference, Weak Reference, Phantom Reference의 네 가지 유형으로 나누었는데, 이 네 가지의 인용 강도가 한꺼번에 약해졌습니다.
강한 참조는 "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
sr = new SoftReference "这句代码,因为内存不够了就回收4M的byte数组,永远没有内存溢出的可能(new byte[4 * _1MB]);
所以,很多时候对一些非必需的对象,我们可以将直接将其与软引用关联,这样内存不够时会先回收软引用关联的对象而不会抛出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
메모리 부족으로 인해 처음 세 개의 소프트 참조가 재활용되었으며, 재활용된 세 개의 소프트 참조의 메모리 주소가 모두 일치하는 것을 확인하면 위의 진술이 증명됩니다.
위 내용은 jvm의 네 가지 참조 상태 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











이 기사에서는 가장 많이 묻는 Java Spring 면접 질문과 자세한 답변을 보관했습니다. 그래야 면접에 합격할 수 있습니다.

Java 8은 스트림 API를 소개하여 데이터 컬렉션을 처리하는 강력하고 표현적인 방법을 제공합니다. 그러나 스트림을 사용할 때 일반적인 질문은 다음과 같은 것입니다. 기존 루프는 조기 중단 또는 반환을 허용하지만 스트림의 Foreach 메소드는이 방법을 직접 지원하지 않습니다. 이 기사는 이유를 설명하고 스트림 처리 시스템에서 조기 종료를 구현하기위한 대체 방법을 탐색합니다. 추가 읽기 : Java Stream API 개선 스트림 foreach를 이해하십시오 Foreach 메소드는 스트림의 각 요소에서 하나의 작업을 수행하는 터미널 작동입니다. 디자인 의도입니다

PHP는 서버 측에서 널리 사용되는 스크립팅 언어이며 특히 웹 개발에 적합합니다. 1.PHP는 HTML을 포함하고 HTTP 요청 및 응답을 처리 할 수 있으며 다양한 데이터베이스를 지원할 수 있습니다. 2.PHP는 강력한 커뮤니티 지원 및 오픈 소스 리소스를 통해 동적 웹 컨텐츠, 프로세스 양식 데이터, 액세스 데이터베이스 등을 생성하는 데 사용됩니다. 3. PHP는 해석 된 언어이며, 실행 프로세스에는 어휘 분석, 문법 분석, 편집 및 실행이 포함됩니다. 4. PHP는 사용자 등록 시스템과 같은 고급 응용 프로그램을 위해 MySQL과 결합 할 수 있습니다. 5. PHP를 디버깅 할 때 error_reporting () 및 var_dump ()와 같은 함수를 사용할 수 있습니다. 6. 캐싱 메커니즘을 사용하여 PHP 코드를 최적화하고 데이터베이스 쿼리를 최적화하며 내장 기능을 사용하십시오. 7

Java의 TimeStamp to Date 안내. 여기서는 소개와 예제와 함께 Java에서 타임스탬프를 날짜로 변환하는 방법에 대해서도 설명합니다.

캡슐은 3 차원 기하학적 그림이며, 양쪽 끝에 실린더와 반구로 구성됩니다. 캡슐의 부피는 실린더의 부피와 양쪽 끝에 반구의 부피를 첨가하여 계산할 수 있습니다. 이 튜토리얼은 다른 방법을 사용하여 Java에서 주어진 캡슐의 부피를 계산하는 방법에 대해 논의합니다. 캡슐 볼륨 공식 캡슐 볼륨에 대한 공식은 다음과 같습니다. 캡슐 부피 = 원통형 볼륨 2 반구 볼륨 안에, R : 반구의 반경. H : 실린더의 높이 (반구 제외). 예 1 입력하다 반경 = 5 단위 높이 = 10 단위 산출 볼륨 = 1570.8 입방 단위 설명하다 공식을 사용하여 볼륨 계산 : 부피 = π × r2 × h (4

PHP와 Python은 각각 고유 한 장점이 있으며 선택은 프로젝트 요구 사항을 기반으로해야합니다. 1.PHP는 간단한 구문과 높은 실행 효율로 웹 개발에 적합합니다. 2. Python은 간결한 구문 및 풍부한 라이브러리를 갖춘 데이터 과학 및 기계 학습에 적합합니다.

Java는 초보자와 숙련된 개발자 모두가 배울 수 있는 인기 있는 프로그래밍 언어입니다. 이 튜토리얼은 기본 개념부터 시작하여 고급 주제를 통해 진행됩니다. Java Development Kit를 설치한 후 간단한 "Hello, World!" 프로그램을 작성하여 프로그래밍을 연습할 수 있습니다. 코드를 이해한 후 명령 프롬프트를 사용하여 프로그램을 컴파일하고 실행하면 "Hello, World!"가 콘솔에 출력됩니다. Java를 배우면 프로그래밍 여정이 시작되고, 숙달이 깊어짐에 따라 더 복잡한 애플리케이션을 만들 수 있습니다.

Spring Boot는 강력하고 확장 가능하며 생산 가능한 Java 응용 프로그램의 생성을 단순화하여 Java 개발에 혁명을 일으킨다. Spring Ecosystem에 내재 된 "구성에 대한 협약"접근 방식은 수동 설정, Allo를 최소화합니다.
