<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      ThreaLocal內存泄露的問題

      在最近一個項目中,在項目發布之后,發現系統中有內存泄漏問題。表象是堆內存隨著系統的運行時間緩慢增長,一直沒有辦法通過gc來回收,最終于導致堆內存耗盡,內存溢出。開始是懷疑ThreadLocal的問題,因為在項目中,大量使用了線程的ThreadLocal保存線程上下文信息,在正常情況下,在線程開始的時候設置線程變量,在線程結束的時候,需要清除線程上下文信息,如果線程變量沒有清除,會導致線程中保存的對象無法釋放。

      從這個正常的情況來看,假設沒有清除線程上下文變量,那么在線程結束的時候(線程銷毀),線程上下文變量所占用的內存會隨著線程的銷毀而被回收。至少從程序設計者角度來看,應該如此。實際情況下是怎么樣,需要進行測試。

      但是對于web類型的應用,為了避免產生大量的線程產生堆棧溢出(默認情況下一個線程會分配512K的棧空間),都會采用線程池的設計方案,對大量請求進行負載均衡。所以實際應用中,一般都會是線程池的設計,處理業務的線程數一般都在200以下,即使所有的線程變量都沒有清理,那么理論上會出現線程保持的變量最大數是200,如果線程變量所指示的對象占用比較少(小于10K),200個線程最多只有2M(200*10K)的內存無法進行回收(因為線程池線程是復用的,每次使用之前,都會從新設置新的線程變量,那么老的線程變量所指示的對象沒有被任何對象引用,會自動被垃圾回收,只有最后一次線程被使用的情況下,才無法進行回收)。

      以上只是理論上的分析,那么實際情況下如何了,我寫了一段代碼進行實驗。

      • 硬件配置:

      處理器名稱: Intel Core i7 2.3 GHz  4核

      內存: 16 GB

      • 軟件配置

      操作系統:OS X 10.8.2

      java版本:"1.7.0_04-ea"

      • JVM配置

      -Xms128M -Xmx512M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xloggc:gc.log

      測試代碼:Test.java 

      import java.io.BufferedReader;
      import java.io.InputStreamReader;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      
      public class Test {
      
          public static void main(String[] args) throws Exception {
              
              BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
              int testCase= Integer.parseInt(br.readLine());
              br.close();
              
              switch(testCase){
                  // 測試情況1. 無線程池,線程不休眠,并且清除thread_local 里面的線程變量;測試結果:無內存溢出
                  case 1 :testWithThread(true, 0); break;
                  // 測試情況2. 無線程池,線程不休眠,沒有清除thread_local 里面的線程變量;測試結果:無內存溢出
                  case 2 :testWithThread(false, 0); break;
                  // 測試情況3. 無線程池,線程休眠1000毫秒,清除thread_local里面的線程的線程變量;測試結果:無內存溢出,但是新生代內存整體使用高
                  case 3 :testWithThread(false, 1000); break;
                  // 測試情況4. 無線程池,線程永久休眠(設置最大值),清除thread_local里面的線程的線程變量;測試結果:無內存溢出
                  case 4 :testWithThread(true, Integer.MAX_VALUE); break;
                  // 測試情況5. 有線程池,線程池大小50,線程不休眠,并且清除thread_local 里面的線程變量;測試結果:無內存溢出
                  case 5 :testWithThreadPool(50,true,0); break;
                  // 測試情況6. 有線程池,線程池大小50,線程不休眠,沒有清除thread_local 里面的線程變量;測試結果:無內存溢出
                  case 6 :testWithThreadPool(50,false,0); break;
                  // 測試情況7. 有線程池,線程池大小50,線程無限休眠,并且清除thread_local 里面的線程變量;測試結果:無內存溢出
                  case 7 :testWithThreadPool(50,true,Integer.MAX_VALUE); break;
                  // 測試情況8. 有線程池,線程池大小1000,線程無限休眠,并且清除thread_local 里面的線程變量;測試結果:無內存溢出
                  case 8 :testWithThreadPool(1000,true,Integer.MAX_VALUE); break;
                  
                  default :break;
              
              }        
          }
      
          public static void testWithThread(boolean clearThreadLocal, long sleepTime) {
      
              while (true) {
                  try {
                      Thread.sleep(100);
                      new Thread(new TestTask(clearThreadLocal, sleepTime)).start();
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }
      
          public static void testWithThreadPool(int poolSize,boolean clearThreadLocal, long sleepTime) {
      
              ExecutorService service = Executors.newFixedThreadPool(poolSize);
              while (true) {
                  try {
                      Thread.sleep(100);
                      service.execute(new TestTask(clearThreadLocal, sleepTime));
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }
      
          public static final byte[] allocateMem() {
              // 這里分配一個1M的對象
              byte[] b = new byte[1024 * 1024];
              return b;
          }
      
          static class TestTask implements Runnable {
      
              /** 是否清除上下文參數變量 */
              private boolean clearThreadLocal;
              /** 線程休眠時間 */
              private long sleepTime;
      
              public TestTask(boolean clearThreadLocal, long sleepTime) {
                  this.clearThreadLocal = clearThreadLocal;
                  this.sleepTime = sleepTime;
              }
      
              public void run() {
                  try {
                      ThreadLocalHolder.set(allocateMem());
                      try {
                          // 大于0的時候才休眠,否則不休眠
                          if (sleepTime > 0) {
                              Thread.sleep(sleepTime);
                          }
                      } catch (InterruptedException e) {
      
                      }
                  } finally {
                      if (clearThreadLocal) {
                          ThreadLocalHolder.clear();
                      }
                  }
              }
          }
      
      }

      ThreadLocalHolder.java

      public class ThreadLocalHolder {
          
          public static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(); 
          
          public static final void set(byte [] b){
              threadLocal.set(b);
          }
          
          public static final void clear(){
              threadLocal.set(null);
          }
      }

       

      • 測試結果分析:

      無線程池的情況:測試用例1-4

      下面是測試用例1 的垃圾回收日志

      下面是測試用例2 的垃圾回收日志

      對比分析測試用例1 和 測試用例2 的GC日志,發現基本上都差不多,說明是否清楚線程上下文變量不影響垃圾回收,對于無線程池的情況下,不會造成內存泄露

       

      對于測試用例3,由于業務線程sleep 一秒鐘,會導致業務系統中有產生大量的阻塞線程,理論上新生代內存會比較高,但是會保持到一定的范圍,不會緩慢增長,導致內存溢出,通過分析了測試用例3的gc日志,發現符合理論上的分析,下面是測試用例3的垃圾回收日志

      通過上述日志分析,發現老年代產生了一次垃圾回收,可能是開始大量線程休眠導致內存無法釋放,這一部分線程持有的線程變量會在重新喚醒之后運行結束被回收,新生代的內存內存一直維持在4112K,也就是4個線程持有的線程變量。

       

      對于測試用例4,由于線程一直sleep,無法對線程變量進行釋放,導致了內存溢出。

       

      有線程池的情況:測試用例5-8

      對于測試用例5,開設了50個工作線程,每次使用線程完成之后,都會清除線程變量,垃圾回收日志和測試用例1以及測試用例2一樣。

      對于測試用例6,也開設了50個線程,但是使用完成之后,沒有清除線程上下文,理論上會有50M內存無法進行回收,通過垃圾回收日志,符合我們的語氣,下面是測試用例6的垃圾回收日志

      通過日志分析,發現老年代回收比較頻繁,主要是因為50個線程持有的50M空間一直無法徹底進行回收,而新生代空間不夠(我們設置的是128M內存,新生代大概36M左右)。所有整體內存的使用量肯定一直在50M之上。

       

      對于測試用例7,由于工作線程最多50個,即使線程一直休眠,再短時間內也不會導致內存溢出,長時間的情況下會出現內存溢出,這主要是因為任務隊列空間沒有限制,和有沒有清除線程上下文變量沒有關系,如果我們使用的有限隊列,就不會出現這個問題。

      對于測試用例8,由于工作線程有1000個,導致至少1000M的堆空間被使用,由于我們設置的最大堆是512M,導致結果溢出。系統的堆空間會從開始的128M逐步增長到512M,最后導致溢出,從gc日志來看,也符合理論上的判斷。由于gc日志比較大,就不在貼出來了。

       

      所以從上面的測試情況來看,線上上下文變量是否導致內存泄露,是需要區分情況的,如果線程變量所占的空間的比較小,小于10K,是不會出現內存泄露的,導致內存溢出的。如果線程變量所占的空間比較大,大于1M的情況下,出現的內存泄露和內存溢出的情況比較大。以上只是jdk1.7版本情況下的分析,個人認為jdk1.6版本的情況和1.7應該差不多,不會有太大的差別。

       

      -----------------------下面是對ThreadLocal的分析-------------------------------------

      對于ThreadLocal的概念,很多人都是比較模糊的,只知道是線程本地變量,而具體這個本地變量是什么含義,有什么作用,如何使用等很多java開發工程師都不知道如何進行使用。從JDK的對ThreadLocal的解釋來看

      該類提供了線程局部 (thread-local) 變量。這些變量不同于它們的普通對應物,因為訪問某個變量(通過其 get 或 set 方法)的每個線程都有自己的局部變量,

      它獨立于變量的初始化副本。ThreadLocal 實例通常是類中的 private static 字段,它們希望將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯。 

      ThreadLocal有一個ThreadLocalMap靜態內部類,你可以簡單理解為一個MAP,這個‘Map’為每個線程復制一個變量的‘拷貝’存儲其中。每一個內部線程都有一個ThreadLocalMap對象。

      當線程調用ThreadLocal.set(T object)方法設置變量時,首先獲取當前線程引用,然后獲取線程內部的ThreadLocalMap對象,設置map的key值為threadLocal對象,value為參數中的object。

      當線程調用ThreadLocal.get()方法獲取變量時,首先獲取當前線程引用,以threadLocal對象為key去獲取響應的ThreadLocalMap,如果此‘Map’不存在則初始化一個,否則返回其中的變量。

      也就是說每個線程內部的 ThreadLocalMap對象中的key保存的threadLocal對象的引用,從ThreadLocalMap的源代碼來看,對threadLocal的對象的引用是WeakReference,也就是弱引用。

      下面一張圖描述這三者的整體關系

      對于一個正常的Map來說,我們一般會調用Map.clear方法來清空map,這樣map里面的所有對象就會釋放。調用map.remove(key)方法,會移除key對應的對象整個entry,這樣key和value 就不會任何對象引用,被java虛擬機回收。

      而Thread對象里面的ThreadLocalMap里面的key是ThreadLocal的對象的弱引用,如果ThreadLocal對象會回收,那么ThreadLocalMap就無法移除其對應的value,那么value對象就無法被回收,導致內存泄露。但是如果thread運行結束,整個線程對象被回收,那么value所引用的對象也就會被垃圾回收。

      什么情況下 ThreadLocal對象會被回收了,典型的就是ThreadLocal對象作為局部對象來使用或者每次使用的時候都new了一個對象。所以一般情況下,ThreadLocal對象都是static的,確保不會被垃圾回收以及任何時候線程都能夠訪問到這個對象。

       寫了下面一段代碼進行測試,發現兩個方法都沒有導致內存溢出,對于沒有使用線程池的方法來說,因為每次線程運行完就退出了,Map里面引用的所有對象都會被垃圾回收,所以沒有關系,但是為什么線程池的方案也沒有導致內存溢出了,主要原因是ThreadLocal.set方法的實現,會做一個將Key== null 的元素清理掉的工作。導致線程之前由于ThreadLocal對象回收之后,ThreadLocalMap中的value 也會被回收,可見設計者也注意到這個地方可能出現內存泄露,為了防止這種情況發生,從而清空ThreadLocalMap中null為空的元素。

      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      
      public class ThreadLocalLeakTest {
      
          public static void main(String[] args) {
              // 如果控制線程池的大小為50,不會導致內存溢出
              testWithThreadPool(50);
              // 也不會導致內存泄露
              testWithThread();
          }
      
          static class TestTask implements Runnable {
      
              public void run() {
                  ThreadLocal tl = new ThreadLocal();
                  // 確保threadLocal為局部對象,在退出run方法之后,沒有任何強引用,可以被垃圾回收
                  tl.set(allocateMem());
              }
          }
      
          public static void testWithThreadPool(int poolSize) {
              ExecutorService service = Executors.newFixedThreadPool(poolSize);
              while (true) {
                  try {
                      Thread.sleep(100);
                      service.execute(new TestTask());
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }
      
          public static void testWithThread() {
      
              try {
                  Thread.sleep(100);
              } catch (InterruptedException e) {
      
              }
              new Thread(new TestTask()).start();
      
          }
      
          public static final byte[] allocateMem() {
              // 這里分配一個1M的對象
              byte[] b = new byte[1024 * 1024 * 1];
              return b;
          }
      
      }

       

      主站蜘蛛池模板: 国产精品白嫩初高生免费视频| 黄色舔女人逼一区二区三区| 色狠狠色婷婷丁香五月| 一本精品99久久精品77| 亚洲午夜无码久久久久蜜臀av| 人妻少妇偷人精品一区| 在线一区二区中文字幕| 欧美精品一产区二产区| 少妇又爽又刺激视频| 午夜精品区| 狠狠精品久久久无码中文字幕| 亚洲 欧美 中文 日韩aⅴ| 免费看男女做好爽好硬视频| 在线精品另类自拍视频| 精品熟女少妇免费久久| 欧美一区二区三区欧美日韩亚洲| 麻豆一区二区中文字幕| 国产AV国片精品有毛| 无码人妻丰满熟妇区毛片| 色偷偷成人综合亚洲精品| 开封市| 精品一二三四区在线观看| 四虎国产精品永久入口| 成熟妇女性成熟满足视频| 激情无码人妻又粗又大| 久久热这里这里只有精品| 午夜三级成人在线观看| 无码人妻丝袜在线视频| 非会员区试看120秒6次 | 久久无码中文字幕免费影院蜜桃| 久久综合九色综合97伊人| 妺妺窝人体色www看美女| 99国产精品一区二区蜜臀| 日韩中文字幕国产精品| 欧洲精品色在线观看| 欧美人与动牲猛交A欧美精品| 青青热在线精品视频免费观看| 国产精品成熟老女人| 丰满少妇人妻久久久久久| аⅴ天堂中文在线网| 内射中出无码护士在线|