
死神---浦原喜助
引用分類:
-
強引用(StrongReference):強引用使用最普遍的引用,eg:
new Object()。 -
軟引用(SoftReference):軟引用可用來實現(xiàn)內(nèi)存敏感的高速緩存。一般用于系統(tǒng)內(nèi)部緩存。
eg:一個對象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器??就不會回收它;如果內(nèi)存空間不足了(FullGC),就會回收這些對象的內(nèi)存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。 -
弱引用(WeakReference):弱引用與軟引用的區(qū)別在于:只具有弱引用的對象擁有
更短暫的生命周期。
eg:垃圾回收器??一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。(YongGC) -
虛引用(PhantomReference):
?后補?
Java4種引用的級別由高到低依次為:
強引用 > 軟引用 > 弱引用 > 虛引用
Java4中引用在垃圾回收中區(qū)別:
| 引用類型 | 回收??時間 | 用途 | 生存周期 |
|---|---|---|---|
| 強引用(StrongReference) | -- | 正常使用 | JVM停止? |
| 軟引用(SoftReference) | FullGC(?cms-oldgc?) | 系統(tǒng)內(nèi)部緩存 | OOM前 |
| 弱引用(WeakReference) | YongGC | 對象緩存 | YongGC發(fā)生前 |
| 虛引用(PhantomReference) | -- | -- | -- |
今天重點討論--->WeakReference
第一個問題:如何證明WeakReference在YongGC被回收???
啟動參數(shù):
-Xmx50M -Xms50M -Xmn10M -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=90 -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCApplicationStoppedTime
-XX:+PrintReferenceGC -XX:+PrintTenuringDistribution
public static void main(String[] args) throws InterruptedException {
WeakReference<User> weakReference = new WeakReference<User>(new User("xiali", "123", "男", 18));
System.out.println("beforeGC:" + weakReference.get());
// 觸發(fā)yonggc
allactionMemory();
// 防止yonggc耗時過長,先睡5s
TimeUnit.SECONDS.sleep(5);
System.out.println("afterGC:" + weakReference.get());
}
public static void allactionMemory(){
int size =1024*1024*12;
int len =size/(10*1024);
List<byte[]> list = new ArrayList<byte[]>();
for (int i =0;i<len;i++) {
try {
byte[] bytes = new byte[10*1024];
list.add(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
static class User {
private String userName;
private String password;
private String sex;
private int age;
public User(String userName, String password, String sex, int age) {
this.userName = userName;
this.password = password;
this.sex = sex;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
運行結(jié)果:
beforeGC:User{userName='xiali', password='123', sex='男', age=18}
{Heap before GC invocations=0 (full 0):
par new generation total 9216K, used 8192K [0x00000007bce00000, 0x00000007bd800000, 0x00000007bd800000)
eden space 8192K, 100% used [0x00000007bce00000, 0x00000007bd600000, 0x00000007bd600000)
from space 1024K, 0% used [0x00000007bd600000, 0x00000007bd600000, 0x00000007bd700000)
to space 1024K, 0% used [0x00000007bd700000, 0x00000007bd700000, 0x00000007bd800000)
concurrent mark-sweep generation total 40960K, used 0K [0x00000007bd800000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 3198K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 354K, capacity 388K, committed 512K, reserved 1048576K
2019-11-30T23:05:49.532-0800: [GC (Allocation Failure) 2019-11-30T23:05:49.532-0800: [ParNew2019-11-30T23:05:49.541-0800: [SoftReference, 0 refs, 0.0000247 secs]2019-11-30T23:05:49.541-0800: [WeakReference, 8 refs, 0.0000108 secs]2019-11-30T23:05:49.541-0800: [FinalReference, 0 refs, 0.0000083 secs]2019-11-30T23:05:49.541-0800: [PhantomReference, 0 refs, 0 refs, 0.0000104 secs]2019-11-30T23:05:49.541-0800: [JNI Weak Reference, 0.0000098 secs]
Desired survivor size 524288 bytes, new threshold 1 (max 6)
- age 1: 1039264 bytes, 1039264 total
: 8192K->1024K(9216K), 0.0086417 secs] 8192K->6662K(50176K), 0.0086800 secs] [Times: user=0.01 sys=0.01, real=0.01 secs]
Heap after GC invocations=1 (full 0):
par new generation total 9216K, used 1024K [0x00000007bce00000, 0x00000007bd800000, 0x00000007bd800000)
eden space 8192K, 0% used [0x00000007bce00000, 0x00000007bce00000, 0x00000007bd600000)
from space 1024K, 100% used [0x00000007bd700000, 0x00000007bd800000, 0x00000007bd800000)
to space 1024K, 0% used [0x00000007bd600000, 0x00000007bd600000, 0x00000007bd700000)
concurrent mark-sweep generation total 40960K, used 5638K [0x00000007bd800000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 3198K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 354K, capacity 388K, committed 512K, reserved 1048576K
}
2019-11-30T23:05:49.541-0800: Total time for which application threads were stopped: 0.0088985 seconds, Stopping threads took: 0.0000389 seconds
2019-11-30T23:05:50.544-0800: Total time for which application threads were stopped: 0.0000546 seconds, Stopping threads took: 0.0000167 seconds
2019-11-30T23:05:53.560-0800: Total time for which application threads were stopped: 0.0001106 seconds, Stopping threads took: 0.0000233 seconds
afterGC:null
Heap
par new generation total 9216K, used 7756K [0x00000007bce00000, 0x00000007bd800000, 0x00000007bd800000)
eden space 8192K, 82% used [0x00000007bce00000, 0x00000007bd493310, 0x00000007bd600000)
from space 1024K, 100% used [0x00000007bd700000, 0x00000007bd800000, 0x00000007bd800000)
to space 1024K, 0% used [0x00000007bd600000, 0x00000007bd600000, 0x00000007bd700000)
concurrent mark-sweep generation total 40960K, used 5638K [0x00000007bd800000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 3352K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 371K, capacity 388K, committed 512K, reserved 1048576K
重點部分:
beforeGC:User{userName='xiali', password='123', sex='男', age=18}
afterGC:null
其他日志均為gclog。
第二個問題:ThreadLocalMap中Entry的Key是WeakReference,如果Yonggc時Key就會被回收??掉,應(yīng)該會影響程序運行?但是現(xiàn)實中問題Yonggc又沒有回收掉呢?為什么ThreadLocal被稱為線程本地變量?
了解ThreadLocal,前提就要搞明白:Thread、ThreadLocalMap、ThreadLocal、Entry的關(guān)系是什么?

image.png
- 紅色關(guān)系線表示:Entry是ThreadLocalMap的靜態(tài)內(nèi)部類,ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類。
- 藍色關(guān)系線表示:Entry是繼承弱引用類的,并且Key是采用弱引用修飾包裝。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
- Thread保有ThreadLocalMap引用。
總結(jié):Thread持有--->ThreadLocalMap引用,其中:
?????????????????????????????????????????????????????? ThreadLocalMap的Entry中(Key是ThreadLocal類型的弱引用) ,value是要保存的值。
Eg:創(chuàng)建四個ThreadLocal變量,其中前三個是Yonggc前操作的復制,第四個變量是Yonggc后進行操作賦值方便觀察ThreadLocalMap中的變化。
啟動參數(shù):-Xmx50M -Xms50M -Xmn10M -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=90 -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCApplicationStoppedTime
-XX:+PrintReferenceGC -XX:+PrintTenuringDistribution
public class TestYGCCleanTLKey {
static ThreadLocal<TestThreadLocalWeakReferenceMain.User> threadLocal = new ThreadLocal<>();
static ThreadLocal<TestThreadLocalWeakReferenceMain.User> threadLocal1 = new ThreadLocal<>();
static ThreadLocal<TestThreadLocalWeakReferenceMain.User> threadLocal2 = new ThreadLocal<>();
static ThreadLocal<TestThreadLocalWeakReferenceMain.User> threadLocal3 = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(new TestThreadLocalWeakReferenceMain.User("xiali", "123", "男", 18));
threadLocal1.set(new TestThreadLocalWeakReferenceMain.User("xiali", "111", "男", 18));
threadLocal2.set(new TestThreadLocalWeakReferenceMain.User("xiali", "222", "男", 18));
// 觸發(fā)yonggc,嘗試回收wkr
allactionMemory();
// debug觀察Thread中的Entry情況:
threadLocal3.set(new TestThreadLocalWeakReferenceMain.User("kobe", "888", "男", 38));
System.out.println("結(jié)束");
System.out.println(threadLocal.get());
}
public static void allactionMemory(){
int size =1024*1024*12;
int len =size/(10*1024);
List<byte[]> list = new ArrayList<byte[]>();
for (int i =0;i<len;i++) {
try {
byte[] bytes = new byte[10*1024];
list.add(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
}

debug過程截圖
經(jīng)過debug發(fā)現(xiàn),
Yonggc是不能回收??ThreadLocalMap中Entry的弱引用Key的。
但是如果在執(zhí)行Yonggc回收??前將前三個Threadlocal變量置為null呢?結(jié)果會怎樣呢???
調(diào)整代碼:

Yonggc執(zhí)行前ThreadLocalMap中對象狀態(tài)

Yonggc執(zhí)行之后ThreadLocalMap中對象的狀態(tài)
通過將變量置為null,經(jīng)過Yonggc后ThreadLocalMap中Entry的Key被成功釋放,但是Value依然存在!這也就是ThreadLocal常說的存在內(nèi)存泄漏風險?。?!
threadLocal = null;
threadLocal1 = null;
threadLocal2 = null;
到底是什么原因造成了這個顯現(xiàn)的呢?
JVM運行時內(nèi)存分配如下圖:

JVM未回收??前內(nèi)存分布

JVM進行Yonggc回收??后內(nèi)存分布
結(jié)論:
- 1、從內(nèi)存分配上可以看出ThreadLocalMap是Thread線程的本地變量,也就是說
ThreadLocalMap的生命周期與Thread同生同死。所以如果真的將ThreadLocal置為null,會發(fā)生內(nèi)存泄漏問題。 - 2、Entry中的Key是將ThreadLocal對象進行包裝成WeakReference,所以ThreadLocal對象沒有被回收自然包裝的WKR對象也不會被回收??。
第三個問題:既然存在內(nèi)存泄漏風險,為何ThreadLocal還采用WKR的方式設(shè)計Entry的key呢?
首先要澄清ThreadLocal存在內(nèi)存泄露風險原因是復雜的,多種場景柔和在一起導致的(Web應(yīng)用前端是使用tomcat線程池,首先線程不會釋放,那么同生同死的ThreadLocalMap就一直存活,加大了內(nèi)存泄漏的風險 )和Entry的Key設(shè)計成弱引用沒有關(guān)系!。
原因是:ThreadLocalMap的設(shè)計中已經(jīng)考慮到這種情況,也加上了一些防護措施:在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap里所有key為null的value。
同時ThreadLocalMap采用弱引用的的設(shè)計優(yōu)勢:
-
引用的ThreadLocal的對象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。【
WKR加速了ThreadLocal回收??】 -
如果設(shè)計成強引用:引用的ThreadLocal的對象被回收了,但是
ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動刪除,ThreadLocal不會被回收,導致Entry內(nèi)存泄漏。
第四個問題:眼尖的同學會問了為什么第一個例子中WeakReference<User> weakReference = new WeakReference<User>(new User("xiali", "123", "男", 18)); 為甚么會被Yonggc回收??掉呢?
將第一個列子改一下:
public class WeakReferenceTest {
public static void main(String[] args) throws InterruptedException {
User user = new User("xiali", "123", "男", 18);
WeakReference<User> weakReference = new WeakReference<User>(user);
System.out.println("beforeGC:" + weakReference.get());
// 觸發(fā)yonggc
allactionMemory();
TimeUnit.SECONDS.sleep(5);
System.out.println("afterGC:" + weakReference.get());
}
public static void allactionMemory() {
int size = 1024 * 1024 * 12;
int len = size / (10 * 1024);
List<byte[]> list = new ArrayList<byte[]>();
for (int i = 0; i < len; i++) {
try {
byte[] bytes = new byte[10 * 1024];
list.add(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
static class User {
private String userName;
private String password;
private String sex;
private int age;
public User(String userName, String password, String sex, int age) {
this.userName = userName;
this.password = password;
this.sex = sex;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
}
結(jié)果和ThreadLocal原理是一樣的:User經(jīng)過Yonggc回收??依然不能進行清理掉。
強引用與弱引用同時指向user的內(nèi)存地址!
有且只有弱引用指向user內(nèi)存地址,YongGC執(zhí)行??時才能將WeakReference回收掉。
第五個問題:從WKR & ThreadLocal 設(shè)計中思考??日后開發(fā)中能提升什么?
-
生命周期比較長的對象,與其相關(guān)的對象也會導致生命周期變長!通過WKR可以實現(xiàn)解耦,從而加速無關(guān)對象的GC回收??。
第六個問題:ThreadLocal 設(shè)計中Key為啥不用SoftReference包裝?
- 這就和觸發(fā)GC回收??時點有關(guān)聯(lián)了,SoftReference就需要FullGC才能回收。