在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

頻繁FullGC的原因竟然是開源代碼?

OSC開源社區 ? 來源:OSCHINA 社區 ? 2023-07-30 15:56 ? 次閱讀

前言

首先 java 語言的特性是不需像 C 和 C++ 那樣自己手動釋放內存,因為 java 本身有垃圾回收機制(垃圾回收稱為 GC),顧名思義就是釋放垃圾占用的空間,防止內存泄露。JVM 運行時占用內存最大的空間就是堆內存,另外棧區和方法區也會占用空間但是占用有限本章就不探究了。

那么堆中的空間又分為年輕代和老年代,所以我們粗略的把垃圾回收分為兩種:年輕代的垃圾回收稱為 Young GC,老年代的垃圾回收稱為 Full GC,實際上此處的 Full GC 也包含了新生代,老年代,元空間等的回收。 因為 Full GC 的回收過程會使系統的所有線程 STW(Stop The World),那么我們一定希望讓系統盡量不要進行 Full GC,或者必須要進行 FullGC 的時候執行的時間越短越好。

下面我們主要探究 Full GC 的角度出發分析我在開發運營后臺的時候遇到的頻繁 Full GC 過程。

事件背景

項目介紹: 我們團隊做的是一個后臺管理系統,因為針對不同用戶負責的功能不同那么需要的權限也就不一樣,所以引入了主流的 shiro 框架做權限控制,該框架可以控制菜單欄,按鈕,操作框等。在引入這個框架時一并引入了輔助組件shiro-redis,該組件是一個緩存層方便管理用戶登錄信息,內存泄漏的問題也是就現在這個輔助組件上。

事件還原: 在周五的中午 11:30 分收到了監控的報警信息提示系統在頻繁 Full GC,此時我們立刻做兩件事情: 第一:登錄公司的 UMP 監控平臺(開源監控可以參考:【Prometheus+grafana 監控】)查看該機器的系統指標,發現確實在頻繁 FullGC 從 11 點持續到了 11 點半

4fdf7f5e-2d3a-11ee-815d-dac502259ad0.png

第二:保留一臺機器作為證據收集,其他機器進行重啟保障業務能正常訪問,重啟后 full gc 正常

第三:堆棧信息操作指令./jmap -F -dump:live,format=b,file=/jmapfile.hprof 18362(-F 操作是強制導出堆棧信息,18362 是應用 pid,通過 top -c 指令獲?。?br />
第四:因為個人無權限導出堆棧信息,馬上電話聯系運維通過上面指令導出該機器上的堆棧文件,就是抓取現場證據,因為過了這個時間堆內存可能就正常了 根據 JVM 知識分析,常見 Full GC 時的五種情況如下:

1. 老年代內存不足(大對象過多或內存泄漏)
2. Metaspace 空間不足
3. 代碼主動觸發 System.gc()
4. YGC 時的悲觀策略
5. dump live 的內存信息時,比如 jmap -dump:live

分析原因

1、查看公司 SGM 監控平臺(開源監控可以參考:【Prometheus+grafana 監控】),元空間最大內存 256M,FullGC 發生前后為 117M,排除 Metaspace 不足造成的原因

4fee4c50-2d3a-11ee-815d-dac502259ad0.png

2、在系統中搜索第三方 jar 包,沒有主動執行 System.gc () 操作的代碼 3、查看 JVM 啟動參數中有下面兩個參數,所以排除了 YGC 時候的悲觀策略原因

-XX:CMSInitiatingOccupancyFraction=70      # 堆內存達到 70%進行 FullGC
-XX:+UseCMSInitiatingOccupancyOnly         # 禁止 YGC 時的悲觀策略(YGC 前后判斷是否需要 FullGC),只有達到閾值才進行 FullGc

4、通過和運維、研發組溝通沒有人主動執行 dump 操作,查看系統的歷史執行指令也沒有 dump 操作,主動 dump 的原因排除 初步分析結果: 通過上面依靠監控平臺、JVM 啟動參數、代碼排除、指令分析,最終嫌疑最大的就是老年代內存空間不足造成頻繁 Full GC,但是作為技術者,排除法顯然不能作為原因定位的依據,我們還需要繼續確定我們的猜想,下面會結合 JVM 啟動參數,Tomcat 啟動參數,堆棧文件三大關鍵要素做具體分析。

下圖是進行 FullGC 時候的老年代內存情況,把下面的 72%、1794Mb、2496Mb、448Mb 先記住,下面會跟這些值做對比

5025338c-2d3a-11ee-815d-dac502259ad0.png

指標信息: JVM 核心參數:
-Xms2048M # 系統啟動初始化堆空間
-Xmx4096M # 系統最大堆空間
-Xmn1600M # 年輕代空間(包括 From 區和 To),From 和 To 默認占年輕代 20%
-XX:MaxPermSize=256M # 最大非堆內存,按需分配
-XX:MetaspaceSize=256M # 元空間大小,JDK1.8 取消了永久代(PermGen)新增元空間,元空間并不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制,存儲類和類加載器的元數據信息
-XX:CMSInitiatingOccupancyFraction=70 # 堆內存達到 70%進行 FullGC
-XX:+UseCMSInitiatingOccupancyOnly # 禁止 YGC 時的悲觀策略(YGC 前后判斷是否需要 FullGC),只有達到閾值才進行 FullGc
-XX:+UseConcMarkSweepGC # 使用 CMS 作為垃圾收集器

Tomcat 核心參數:
maxThreads=750# Tomcat 線程池最多能起的線程數
minSpareThreads=50# Tomcat 初始化的線程池大小或者說 Tomcat 線程池最少會有這么多線程
acceptCount=1000# Tomcat 維護最大的隊列數

wKgZomTGGH6AKSiCAAEjU8BCu1s356.jpg

5035b586-2d3a-11ee-815d-dac502259ad0.png

導出堆棧指令:jmap -dump:live,format=b,file=jmapfile.hprof [pid]。導出的文件需要使用 MAT 軟件分析,全稱 MemoryAnalyzer,主要分析堆內存。

從堆棧文件分析結果中發現有 50 個 org.apache.tomcat.util.threads.TaskThread 占用空間很大。共占用空間 96.16%

50644a22-2d3a-11ee-815d-dac502259ad0.png

每個 TaskThread 實例占用空間 36M 左右

50a6db3a-2d3a-11ee-815d-dac502259ad0.png

查看內存詳情保存最大最多的對象是 ThreadLocal 中存儲的 SessionInMemory 對象

511801e8-2d3a-11ee-815d-dac502259ad0.jpg

最終原因: 通過分析上面的 JVM 參數、Tomcat 參數、堆棧文件,內存泄漏的原因是每個線程中有一個 ThreadLocal 存儲大量 SessionInMemory,因為 Tomcat 的啟動核心線程數是 50 個,每個線程的內存占用 36M 左右,共占用 1.8G,老年代內存達到 70% 也就是 2496 * 0.7 = 1747.2M 就會進行垃圾回收,1.8G 剛好比 1747.2M 稍微大一些。但是線程中的對象又沒辦法被回收,所以就會看到系統再頻繁 FullGC。

定位問題

通過上面內存分析已經定位到內存泄漏的原因是每個線程中有大量 SessionInMemory,下面步驟就認真分析代碼找到其中創建如此多對象還不銷毀的原因。

經過初步分析發現 SessionInMemory 是引用 shiro-redis 的工具包里面的對象,主要封裝 Session 信息和創建時間。主要作用是在當前線程的 jvm 中做一層緩存當系統頻繁獲取 Session 時不用去 redis 獲取了。SessionInMemary 對象是 shiro 判斷用戶登錄成功時候存儲的數據,主要包括用戶信息,認證信息,權限信息等,因為用戶登錄成功后不會重復認證,shiro 會對不同用戶做權限判斷

分析代碼發現處理本地緩存 Session 的流程有明顯問題,我畫了一個簡易的流程圖,在介紹流程圖前我先描述一下 Session 和用戶登錄操作如何聯系起來

我們都知道運營后臺需要用戶登錄,登錄成功后會生成一個 cookie 保存到瀏覽器中,cookie 存儲一個關鍵字段 sessionId 用來標識用戶的狀態和信息,當用戶訪問頁面調用接口的時候 shiro 會從請求 Request 中獲取 cookie 中的 sessionId,根據這個唯一標識生成 Session 來存儲用戶的登錄態和登錄信息等,這些信息會保存到 redis 中。shiro-redis 組件負責從 redis 中獲取的 Session 信息通過 ThreadLoca 做到線程隔離。

51536b2a-2d3a-11ee-815d-dac502259ad0.png

上圖流程概括就是:用戶訪問頁面先從本地緩存獲取 Session,如果存在且沒有超過一秒就返回結果,如果沒有 Session 或者過期了就把現在的 Session 刪除并新建一個返回結果。整體看思路清晰,先獲取 Session,如果沒有就新建返回,如果過期了就刪除再新建返回。

流程圖隱藏的問題(核心問題)

1、多個線程會復制多份相同 Session 使內存成倍增加(Session 一樣線程不同) 舉個例子:用戶登錄后臺生成一個 Session,假設請求都到一臺機器上,第一次請求到線程 1,第二個請求到線程 2,因為 Session 一樣但是線程之間是隔離的,所以線程 1 和線程 2 都會創建一份相同 Session 存儲到 ThreaLocal 中,Tomcat 最小空閑線程數越多復制的 Session 份數也越多。因為 Tomcat 的核心線程數不會關閉,所以里面的資源也不會釋放。此處有個疑問 ThreadLocad 的 key 是弱引用但是為什么沒回收呢?下面統統解答

2、舊 Session 無法清除(線程一樣 Session 不同)

舉個例子 1:假設所有請求都到一臺機器的同一個線程,用戶第一次登錄后臺生成 Session1,第一次請求到線程 1,1 秒內所有請求都執行完了,此時 Session 沒有移除(因為 Session 移除策略是懶刪除,需要等下次同一個 Session 訪問時判斷過期條件再刪除),用戶重新登錄,生成了 Session2,因為 Session2 在線程 1 中還沒有就會重新創建,導致第一次登錄時候用到的 Session1 就一直保存到該線程中了

舉個例子 2:參考例子 1 的思路,如果用戶用 Session1 沒有在 1 秒內把所有請求執行完,就會執行懶刪除操作,但是刪除后又新建了一個,那么用戶重新登錄后剛才新建的那個 Session 還是沒有被刪除,所以總結出來只要用戶重新登錄必定有一個舊的 Session 會保留到線程中

代碼分析

1、在 RedisSessionDAO.java 文件中定義了一個 ThreadLocal 變量作為線程隔離

51639bd0-2d3a-11ee-815d-dac502259ad0.png

2、用戶訪問接口、js 文件、css 文件等資源的時候會進入 shiro 的攔截機制。在攔截過程中會頻繁調用 doReasSession () 方法獲取用戶的 Session 信息,主要是獲取信息校驗用戶的權限控制等。

下面的方法主要整合了獲取 Session 操作和設置 Session 操作,如果從 ThreadLocal 中沒有獲取到或者本地緩存超過 1 秒了就返回 null,判斷為 null 之后就會從 redis 中獲取并新建一個 Session 存儲到 ThreadLoca 中

518fc07a-2d3a-11ee-815d-dac502259ad0.png

3、從 ThreadLocal 中取出 sessionMap,根據 sessionId 在 sessionMap 中尋找 Session,如果沒找到直接返回 null,如果找到了再判斷時間是否超過了 1 秒,如果沒超過返回 Session,如果超過了移除返回 null

51ba5b6e-2d3a-11ee-815d-dac502259ad0.png

4、從 ThreadLocal 中獲取 sessionMap,如果為 null 就新建一個保存起來,因為用戶第一次訪問的時候線程中的 sessionMap 還沒有呢所以要新建。然后向 sessionMap 中存儲 Session 對象

5214b88e-2d3a-11ee-815d-dac502259ad0.png

所以代碼的完成流程總結:獲取 Session 的操作是調用 getSessionFromThreadLocal () 方法,如果沒有獲取到 Session 就返回 null,調用 setSessionToThreadLocal () 方法會重新設置一個 Session。如果 Session 在當前線程的保存時間超過 1 秒就 remove。

通過上面分析 JVM、Tomat、堆棧、代碼已經把問題定位了,因為 shiro-redis 中存儲的 SessionInMemory 對象處理不當導致線程間存儲越來越多,最終使內存泄漏進而導致了頻繁 FullGC。因為我們引用的 shiro-redis 版本是 3.2.2 版本,所以存在這個漏洞,作者已于 2019 年 3 月升級 jar 包到 3.2.3 版本把該問題解決。備注:3.2.2 及以下版本存在該問題

解決問題

解決問題的方案目前有四種。針對我們系統使用的是方案 1 + 方案 4

序號 方案描述 優點 缺點
方案 1 每次設置 session 時遍歷刪除以前過期或者為 null 的 session 主動刪除,刪除頻次依賴用戶的訪問頻次 如果在 1 秒內有大量用戶訪問,總 session 很多無效 session 很少,遍歷所有 session 做了很多無用功導致訪問變慢
方案 2 取消 threadLocal 策略,所有請求直接查詢緩存(redis) 減少本地內存使用 訪問緩存耗時比本地長,經過測試發現一個接口會調用 16 次左右的獲取 session 操作,一個頁面幾十個接口,直接查詢緩存性能存在問題
方案 3 使用本地緩存(guavaCache 或者 EhCache 等),并對緩存做移除策略 多個線程共用一份內存,節省內存空間,提升系統性能 對框架有深入了解,接入需要開發成本
方案 4 把 tomcat 的核心線程數減小,比如把原來的 50 改成 5 減少系統資源,減少相同 Session 的復制份數,大于 5 的線程銷毀資源也一起回收 處理并發能力略低

疑問解答

Q:在 RedisSessionDAO 里面只定義了一個 ThreadLocal 的變量 sessionsInThread,怎么就會是 50 個線程把相同的 Session 復制 50 份呢?

A:首先我們先理解 ThreadLocal 的結構,ThreadLocal 有一個靜態類 ThreadLocalMap,ThreadLocalMap 里面還有一個 Entry,我們的 key 和 value 就是保存在 Entry 的,key 是一個弱引用的 ThreadLocal 類型,,這個 key 在所有的線程中都是一樣的,實際上就是我們定義的靜態 sessionsInThread。那又是怎么做到線程隔離的呢?

這就講到 Thread 中的一個成員變量 threadLocals,這個對象就是 ThreadLocal.ThreadLocalMap 類型,也就是每次創建一個線程都會 new 一個 ThreadLocalMap,所以每個線程中的 ThreadLocalMap 都是不同的,但是里面 Entry 存儲的 key 都是一樣的,也就是我們前面定義的 sessionsInThread 靜態變量。

當一個線程需要獲取 Entry 中存儲的 value 時候,調用 sessionsInThread.get () 方法,這個方法做了三件事情,一是獲取當前線程的實例,二是從線程實例中獲取 ThreadLocalMap,三是從 ThreadLocalMap 中根據 ThreadLocal 這個 key 獲取指定的 value

52453d7e-2d3a-11ee-815d-dac502259ad0.png

獲取 Thread 中的 ThreadLocalMap

525fe52a-2d3a-11ee-815d-dac502259ad0.png

從 ThreadLocalMap 中獲取指定的 value,又有個疑問,獲取 Entry 為什么還要從一個 table 數組中拿呢?這個很好理解一個線程不一定只有一個 ThreadLocal 變量吧,多個 ThreadLocal 變量就是有多個 key,所以就放到 table 數組里面了

527baf26-2d3a-11ee-815d-dac502259ad0.png

Q:都說 ThreadLocal 的 key 是一個弱引用,如果內存不足了會被垃圾回收,咱們的 key 從堆??床]有回收呀?

A:這是個好問題,首先我們的 RedisSessionDAO 是 Spring 注入的單例模式,ThreadLocal 被定義成一個靜態變量,靜態變量在內存中是不會回收的。

補充:一般我們在使用 ThreadLocal 的時候都會定義成靜態變量,如果定義成非靜態變量創建一個對象就會 new 一個 ThreadLocal,那么 ThreadLocal 就沒有存在的意義了。

Q:已經結束的線程,為什么還會存活,里面的對象也不會消失?

A:因為設置的最小空閑線程數是 50,業務量不大并發數沒有超過 50,tomcat 會保留最小的線程數量不會新建也不用回收,ThreadLocalMap 是線程中的成員變量所以不會回收

Q:訪問一次接口就會生成一個 sessionId 嗎?

A:訪問接口先判斷用戶信息是否有效,無效才會重新登錄獲取新的 sessionId

Q:shiro-redis 在本地保存 Session 為什么設置 1 秒過期時間?

A:因為運營后臺不同于業務接口會持續調用,后臺接口大部分的場景是用戶訪問一個頁面并停留在頁面上做一些操作,訪問一個頁面的時候瀏覽器會加載多個資源,包括靜態資源 html,css,js 等,和接口的動態數據,整個資源加載過程盡量保持在一秒內完成,如果超過一秒的話系統體驗性能較差,所以本地緩存一秒足夠了。

收獲總結

報警前:

1. 熟悉第三方 jar 包的工作原理,尤其是個人開發工具包,因為沒有經過市場檢驗使用前要格外小心

2. 可以使用 jvisualvm 進行本地壓測觀察 jvm 情況

3. 關注監控報警,掌握監控平臺操作,能夠從監控中查詢系統各項指標信息

4. 根據業務合理配置 JVM 參數和 Tomcat 參數

報警后:

1. 能夠第一時間抓取系統的 JVM 信息,比如堆棧,GC 信息,線程棧等

2. 通過使用 MAT 內存輔助軟件幫助自己分析問題原因






審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 存儲器
    +關注

    關注

    38

    文章

    7636

    瀏覽量

    166452
  • JAVA語言
    +關注

    關注

    0

    文章

    138

    瀏覽量

    20525
  • cms
    cms
    +關注

    關注

    0

    文章

    60

    瀏覽量

    11181
  • 緩存器
    +關注

    關注

    0

    文章

    63

    瀏覽量

    11839
  • JVM
    JVM
    +關注

    關注

    0

    文章

    160

    瀏覽量

    12524

原文標題:頻繁FullGC的原因竟然是 “開源代碼”?

文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦
    熱點推薦

    N9H20 GPIO上電竟然是高電平,有沒有辦法解決?

    一直很納悶,GPIO上電竟然是高電平。有沒有辦法解決,是不是要改動啟動程序才能解決問題?
    發表于 01-17 08:27

    基于OMPL的運動規劃介紹與教程_ROS_開源代碼1 #開源代碼

    機器人開源硬件源代碼代碼控制算法開源代碼
    薪盡火傳
    發布于 :2022年08月24日 16:28:03

    基于OMPL的運動規劃介紹與教程_ROS_開源代碼 2#開源代碼

    機器人開源硬件源代碼代碼控制算法開源代碼
    薪盡火傳
    發布于 :2022年08月24日 16:28:41

    基于OMPL的運動規劃介紹與教程_ROS_開源代碼3 #開源代碼

    機器人開源硬件源代碼代碼控制算法開源代碼
    薪盡火傳
    發布于 :2022年08月24日 16:29:46

    基于OMPL的運動規劃介紹與教程_ROS_開源代碼4 #開源代碼

    機器人開源硬件源代碼代碼控制算法開源代碼
    薪盡火傳
    發布于 :2022年08月24日 16:29:51

    [轉帖]這樣的建站程序,竟然是免費的!

    這樣的建站程序,竟然是免費的!廢話就不多說了,大家看了圖再發表意見吧![/url][url=http://b86.net]這樣的程序,只需要,購買耐思尼克www.b86.net/?s=cartoon
    發表于 05-22 16:06

    SRTP,被刷下來了,原因竟然是抽簽被抽到

    本帖最后由 iiio051031448 于 2012-4-1 12:36 編輯 SRTP,被刷下來了,如果是項目不合理,或者沒有別人的好,我也就認了,自己能力不行。原因竟然是抽簽被抽到,就你不行我還能相信學校么?!
    發表于 04-01 01:00

    影響PCB價格的因素竟然是這些,表示都忽略了

    影響PCB價格的因素竟然是這些,表示都忽略了
    發表于 04-09 19:41

    Arduino 的開源代碼 在哪找

    各位哥哥姐姐,姐夫大嫂,有誰能告訴我 Arduino 的開源代碼 在哪找 ?小弟不勝感激?。?/div>
    發表于 08-25 13:52

    電磁爐干燒的原因竟然是最不起眼的小元件!

    `電磁爐干燒的原因竟然是不起眼的小元件你知道嗎隨著科技的發展,電磁爐使用頻率越來頻繁,加熱,炒菜,等等…大部分食材都會用到電磁爐,現如今電磁爐都有一套保護系統,可是還是會出現干燒,不加熱故障
    發表于 07-20 09:59

    四軸開源代碼IAR

    四軸飛行器開源 源代碼IAR需要的自行下載吧。
    發表于 03-31 15:26 ?5次下載

    蘋果系統泄密 來自iOS源代碼

    iPhone源碼泄漏,iOS也涼了?這個最嚴重的泄密竟然是來自 iOS 源代碼。當時其實他們只是在專門整代碼分享的網站 GitHub 上發現,這竟然是 iPhone 的操作系統的核心組
    的頭像 發表于 02-11 10:50 ?5997次閱讀

    MWC四軸開源代碼免費下載

    本文檔的主要內容詳細介紹的是MWC四軸開源代碼免費下載。
    發表于 03-31 08:00 ?20次下載

    你不知道的FPC,它的發展史竟然是這樣的!

    你不知道的FPC,它的發展史竟然是這樣的!
    的頭像 發表于 11-15 10:48 ?1784次閱讀

    UPS(不間斷電源)故障頻發?原因竟然是這樣

    UPS(不間斷電源)故障頻發?原因竟然是這樣
    的頭像 發表于 04-19 13:53 ?214次閱讀
    UPS(不間斷電源)故障頻發?<b class='flag-5'>原因</b><b class='flag-5'>竟然是</b>這樣
    主站蜘蛛池模板: 国产精品久久1024 | 日本口工全彩无遮拦漫画大 | 男人的天堂免费网站 | 色多多最新地址福利地址 | 五月婷婷六月丁香在线 | 日本一区二区三区在线 视频观看免费 | 成成人看片在线 | 午夜视频在线观看国产 | 欧美一级免费在线观看 | 黄色免费大全 | 在线观看深夜观看网站免费 | 影视精品网站入口 | 男女激情做爰叫床声视频偷拍 | 亚欧美视频 | 色综合久久综合欧美综合网 | 午夜在线视频免费 | 巨乳色网站 | 亚洲色图综合图片 | 国产裸体美女视频全黄 | 黄网站色视频免费看无下截 | 国产无套视频在线观看香蕉 | 免费视频网站在线看视频 | 1024手机看片国产 | 国产精品天天看天天爽 | 中文字幕有码视频 | 天天躁日日躁成人字幕aⅴ 天天躁夜夜躁 | 91极品女神嫩模在线播放 | 天天干夜夜爽 | 欧美四虎 | 国产黄色精品 | 羞羞漫画喷水漫画yy漫画 | 黄色日本网站 | 国产裸体美女视频全黄 | 色偷偷狠狠色综合网 | 五月婷婷六月天 | 欧美五月| 日本三级视频在线播放 | 在线视频 一区二区 | 欧美天天搞 | 久热国产在线 | 色婷婷六月丁香在线观看 |