成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

高并發服務優化篇:詳解一次由讀寫鎖引起的內存泄漏

云計算 虛擬化
JVM相關的異常,一直是一線研發比較頭疼的問題。因為對于業務代碼,JVM的運行基本算是黑盒,當異常發生時,較難直觀的看到和找到問題所在,這也是我們一直要研究其內部邏輯的原因。

[[414812]]

JVM相關的異常,一直是一線研發比較頭疼的問題。因為對于業務代碼,JVM的運行基本算是黑盒,當異常發生時,較難直觀的看到和找到問題所在,這也是我們一直要研究其內部邏輯的原因。

本篇就由一個近期線上JVM內存泄漏的例子,帶大家強行分析一波~

Part1 線上服務器報警了

某天,同事來找我幫忙,原來是某系統毫無征兆的來了一連串報警,一波機器的老年代內存占用率超過閾值~

1.1先看表現

老年代內存占用

可以看到,在7月中旬之前,內存占用還是比較正常的,每次GC都可以回收掉很大一部分的老年代對象。

而中旬之后,老年代內存一直緩慢增長而無法釋放。很明顯,應該是對象沒法被正常回收導致。

內存泄漏了~

1.2 怎么辦呢

如果是剛上線的項目爆出了此類問題,因為影響面比較小,可以直接先回滾代碼,止血為第一要務。

不過,這個項目明顯已經上線N多天,中間還不知道上過多少需求,而且,既然流量近期有上漲導致問題出現,說明,已經對客開流量了。

回滾是不可能了,抓緊時間定位問題,上線修復吧。

Part2 定位問題

一般的步驟:

  • 拿到dump文件
  • 用MAT等工具,找出內存占用過多的異常對象,以及引用關系
  • 分析異常對象關聯代碼的可能問題

不過,因為這次dump下來的文件十多G,太大的,MAT基本無能為力,只能打印出來人工分析了

2.1 定位問題代碼

jmap結果查看

很幸運,異常對象非常明顯。Point對象和GeoDispLocal對象,居然多達好幾百萬實例數,那就先看下代碼中這兩個對象是怎么用的。

  1. private static final CacheMap<String, List<GeoDispLocal>> NEAR_DISTRICT_CACHE = new CacheMap<String, List<GeoDispLocal>>(3600 * 1000, 1000); 
  2.  
  3. private static final CacheMap<Integer, Point> LOCAL_POINT_CACHE = new CacheMap<Integer, Point>(3600 * 1000, 6000); 

都是被存放在本次緩存CacheMap中(內存泄漏的一個常見原因,就是因為被靜態集合持有,無法回收導致),而dump文件中的CacheMap.Entry也是非常高的。

CacheMap就是我們的第一優先懷疑對象了。先看下這個緩存類是怎么回事:

  1. ublic class CacheMap<K, V> { 
  2.     private final long expireMs; 
  3.     private LRUMap<K, CacheMap.Entry<V>> valueMap; 
  4.     //其他略 

內部依賴一個帶LRU功能的map,怎么實現的呢:

  1. public class LRUMap<K, V> extends LinkedHashMap<K, V> { 
  2.     private static final long serialVersionUID = 1L; 
  3.     private final int maxCapacity; 
  4.     // 這個map不會擴容 
  5.     private static final float LOAD_FACTOR = 0.99f; 
  6.     private final ReadWriteLock lock = new ReentrantReadWriteLock(); 
  7.  
  8.     public LRUMap(int maxCapacity) { 
  9.         super(maxCapacity, LOAD_FACTOR, true); 
  10.         this.maxCapacity = maxCapacity; 
  11.     } 
  12.  
  13.     @Override 
  14.     protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) { 
  15.         return size() > maxCapacity; 
  16.     } 
  17.  
  18.     @Override 
  19.     public V get(Object key) { 
  20.         try { 
  21.             lock.readLock().lock(); 
  22.             return super.get(key); 
  23.         } finally { 
  24.             lock.readLock().unlock(); 
  25.         } 
  26.     } 
  27.  
  28.     @Override 
  29.     public V put(K key, V value) { 
  30.         try { 
  31.             lock.writeLock().lock(); 
  32.             return super.put(key, value); 
  33.         } finally { 
  34.             lock.writeLock().unlock(); 
  35.         } 
  36.     } 
  37.     //remove clear 略 

內部是一個依賴LinkedHashMap實現的LRU緩存。看注釋,目的是要構建一個限定容量、且不會進行擴容的MAP(百度了一波,和網上的實現一模一樣~)。那么,實際情況真的和想象中的一樣么?。

2.2 LinkedHashMap實現的LRUMap好使么

我們來看容量和擴容相關的設置:為什么設計者認為該LRUMap不會進行擴容?

  1. //**把容量和擴容相關的參數摘出來** 
  2. //用戶期望的最大容量 
  3. private final int maxCapacity; 
  4. //加載系數 
  5. private static final float LOAD_FACTOR = 0.99f; 
  6. //構造函數中調用LinkedHashMap進行初始化 
  7. super(maxCapacity, LOAD_FACTOR, true); 
  8.  
  9. @Override  //復寫刪除最久元素條件方法 
  10. protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) { 
  11.    //當LinkedHashMap.size 比 我們限定容量大時,執行刪除 
  12.    return size() > maxCapacity; 

按我們的實際使用實例化一下:

  • maxCapacity=6000,是我們希望的最大元素容量。
  • load_factor=0.99 加載因子。
  • Map內部threshold=8192*0.99=8110,是那么下次擴容時的容量大小。(map中table容量的真實大小是離6000最近的2的N次冪,即8192)。

因為復寫了LRU條件函數,當size>6000時會進行LRU替換。因此,理論上,size永遠不會達到8110。

怎么解決并發下的讀寫沖突呢?

  1. //讀寫鎖 
  2. private final ReadWriteLock lock = new ReentrantReadWriteLock(); 
  3.   
  4. public V get(Object key) { 
  5.    try { 
  6.        lock.readLock().lock(); 
  7.        return super.get(key); 
  8.    } finally { 
  9.        lock.readLock().unlock(); 
  10.    } 
  11.  
  12. public V put(K key, V value) { 
  13.    try { 
  14.       lock.writeLock().lock(); 
  15.       return super.put(key, value); 
  16.    } finally { 
  17.       lock.writeLock().unlock(); 
  18.    } 

設計者為了解決并發下的讀寫沖突,給查詢和修改方法加了鎖,為了兼顧性能,使用了讀寫鎖:在get的時候加讀鎖,在put/remove的時候加寫鎖。

看起來,整個設計很好的解決了LRUMap的固定容量和并發操作問題,那么事實是什么樣的呢?

其實,這個問題很早就有人分析過了[1] ,是因為LinkedHashMap在get讀操作的時候,會為了維護LRU從而進行元素修改,即將get到的元素轉移到鏈表最后。這樣,就導致了讀寫并發問題,但這個解釋感覺朦朦朧朧,因此,我決定在其基礎上對讀寫并發問題再講細致一些。

2.3 LinkedHashMap內存泄漏拆解

都加了讀寫鎖為什么不好使呢?

這里我們還是需要先明確,讀寫鎖的概念和適用場景:讀寫鎖,允許多個線程共享讀鎖,適用于讀多寫少的情況。(前提是,讀操作不會改變存儲結構)

所以,問題就發生在get操作上,LinkedHashMap的get操作被重寫,目的是為了實現LRU功能,在get之后,將當前節點移動到鏈表最后。

移動啊,同志們,這明顯是一個寫操作,所以,加讀鎖還有用么?

即允許多線程進入,又進行了修改,那還能起什么作用,能沒有并發問題么?

下面,對照節點移動的代碼,詳細拆解一下多線程下的并發問題:

get之后的節點移動,將節點移動到最后

實際拆解分析如下,為什么在多線程的情況下,會出現內存泄漏:

時間片下多線程的get執行

我們看到,在線程1執行完前兩句,讓出了時間片,當線程2執行到p.after=null之后又出讓了時間片,這樣,本來a應該是后面的<2,B>節點,結果多線程下變成了null,最終,后面兩個節點被踢出了鏈表,刪除操作無法觸達,造成內存泄漏。

驗證的代碼就不貼了,大家有興趣可以自己試一下~

Part3 總結

話說回來,既然定位到了問題,這個內存泄漏怎么修復呢?

可以把讀寫鎖改成互斥鎖。或者直接用分布式存儲,能慢多少呢,是不是,既方便,簡單,又免得為了節約機器內存自己構造LRUMap。 

每一個八股文都不只是為了面試,而是每次線上問題排查的基石。千萬別把八股文的作用定位錯了。。。

本文轉載自微信公眾號「Coder的技術之路  」,可以通過以下二維碼關注。轉載本文請聯系Coder的技術之路公眾號。

 

責任編輯:武曉燕 來源: Coder的技術之路
相關推薦

2022-11-03 16:10:29

groovyfullGC

2021-12-02 07:50:30

NFS故障內存

2020-08-28 08:55:32

商城系統高并發

2018-09-14 10:48:45

Java內存泄漏

2021-08-19 09:50:53

Java內存泄漏

2020-11-02 09:48:35

C++泄漏代碼

2022-09-13 17:46:19

STA模式內存

2020-10-27 10:35:38

優化代碼項目

2020-08-27 21:36:50

JVM內存泄漏

2019-03-26 08:52:51

2022-02-08 17:17:27

內存泄漏排查

2023-01-04 18:32:31

線上服務代碼

2022-05-17 11:46:48

高并發服務數據庫

2021-02-11 14:06:38

Linux內核內存

2019-02-20 09:29:44

Java內存郵件

2021-11-02 07:54:41

內存.NET 系統

2018-05-30 11:09:41

memcache服務器故障

2020-11-06 00:45:29

Linux服務器swap內存

2016-09-08 16:16:26

iOS移動應用內存泄漏

2015-07-17 10:04:33

MKMapView優化
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: h片在线播放 | 一区二区福利视频 | 91精品在线播放 | 国产精品免费观看视频 | 婷婷色国产偷v国产偷v小说 | 欧产日产国产精品视频 | 久久九九网站 | 国产免费又色又爽又黄在线观看 | 亚洲a毛片 | 久久久久国产成人精品亚洲午夜 | 国产色 | www.久久99 | jvid精品资源在线观看 | 亚洲精选一区二区 | 亚洲精品一区二区 | 北条麻妃视频在线观看 | 久久男人天堂 | 成人精品视频在线观看 | 精品成人av | 午夜av电影院 | 国产日产精品一区二区三区四区 | 久久精品国产一区二区电影 | 国产三区视频在线观看 | 亚洲国产精品成人 | 第四色狠狠| 天天操天天射综合 | 最近日韩中文字幕 | 中文字幕成人 | 在线中文字幕第一页 | 91精品久久久久久久久久 | 亚洲一区二区三区 | 精品国产一级 | 久久婷婷av | 亚洲一区二区在线免费观看 | 国产一级毛片精品完整视频版 | 日韩一区二区福利视频 | 97久久精品午夜一区二区 | 国产精品久久久久一区二区三区 | 91精品国产综合久久精品 | 欧美日韩在线精品 | 亚洲大片一区 |