jvm 優(yōu)化篇-(5)-YongGC 回收WeakReference?ThreadLocal內(nèi)存泄漏原理分析,WeakReference<ThreadLocal<?>>為何采用弱引用?

死神---浦原喜助

引用分類:

  • 強引用(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才能回收。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容