JAVA內(nèi)存問(wèn)題定位方法總結(jié)

      網(wǎng)友投稿 1297 2022-05-29

      為了加深自己理解及方便日后復(fù)習(xí),將學(xué)到的一些Java內(nèi)存相關(guān)知識(shí)總結(jié)一下,如有錯(cuò)誤,歡迎大家指正

      1.JAVA內(nèi)存基礎(chǔ)

      1.1 Linux與進(jìn)程內(nèi)存模型

      硬件層面

      從硬件層面上看,Linux系統(tǒng)的內(nèi)存空間由兩部分構(gòu)成:物理內(nèi)存和磁盤(pán)SWAP分區(qū)。物理內(nèi)存是Linu使用的主要內(nèi)存區(qū)域,當(dāng)物理內(nèi)存不足時(shí),Linux會(huì)把一部分相對(duì)冷內(nèi)存數(shù)據(jù)放到磁盤(pán)的SWAP分區(qū),以便騰出更多的可用內(nèi)存空間;而當(dāng)命中數(shù)據(jù)處于SWAP分區(qū)中時(shí),就會(huì)將其置換回物理內(nèi)存中

      系統(tǒng)層面

      從Linux系統(tǒng)層面上看,除了引導(dǎo)系統(tǒng)的BIN區(qū),整個(gè)內(nèi)存空間被分為兩個(gè)部分:內(nèi)核態(tài)內(nèi)存(Kernel Space)、用戶(hù)態(tài)內(nèi)存(User Space)。內(nèi)核內(nèi)存是Linux自身使用的內(nèi)存空間,主要提供給程序調(diào)度、內(nèi)存分配、硬件資源驅(qū)動(dòng)等程序邏輯使用。用戶(hù)態(tài)內(nèi)存是提供給各進(jìn)程的主要內(nèi)存空間,Linux使用虛擬內(nèi)存技術(shù)給各個(gè)進(jìn)程提供相同的虛擬內(nèi)存空間,這機(jī)制確保進(jìn)程之間相互獨(dú)立、互不干擾。

      進(jìn)程層面

      從進(jìn)程的角度,進(jìn)程能直接訪問(wèn)的用戶(hù)態(tài)內(nèi)存(虛擬內(nèi)存空間)被劃分為5個(gè)部分

      代碼區(qū):存放了應(yīng)用程序的機(jī)器代碼,運(yùn)行過(guò)程中代碼不能被修改,具有只讀和固定大小的特點(diǎn)。

      數(shù)據(jù)區(qū):存放應(yīng)用程序中的全局?jǐn)?shù)據(jù),靜態(tài)數(shù)據(jù)和常量字符串等,其大小也是固定的。

      堆區(qū):運(yùn)行時(shí)程序動(dòng)態(tài)申請(qǐng)的空間,程序運(yùn)行時(shí)動(dòng)態(tài)申請(qǐng)、釋放的內(nèi)存資源。

      未使用區(qū):分配新內(nèi)存空間的預(yù)備區(qū)域。

      棧區(qū):存放函數(shù)的傳入?yún)?shù)、臨時(shí)變量、返回地址等數(shù)據(jù)。

      1.2 Java8 JVM內(nèi)存模型

      JAVA內(nèi)存問(wèn)題及定位方法總結(jié)

      內(nèi)存模型大致可以分為五個(gè)部分

      程序計(jì)數(shù)器(Program Counter Register)

      程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器,是線程私有的區(qū)域,各條線程之間的計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ)。在虛擬機(jī)概念模型里,字節(jié)碼解釋器通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)執(zhí)行不同的字節(jié)碼指令,分支、跳轉(zhuǎn)、循環(huán)、異常處理、線程恢復(fù)等基礎(chǔ)操作都會(huì)依賴(lài)這個(gè)計(jì)數(shù)器來(lái)完成。

      虛擬機(jī)棧(VM Stack)

      JVM棧是線程私有的內(nèi)存區(qū)域。它描述的是java方法的內(nèi)存,每個(gè)方法執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)棧幀從入棧到出棧就代表著一個(gè)方法從調(diào)用到返回的過(guò)程。

      本地方法棧(Native Method Stack)

      本地方法棧與虛擬機(jī)棧類(lèi)似,不同的是其為Native方法服務(wù),而虛擬機(jī)棧為java方法服務(wù)。是規(guī)范中指明的概念,但是實(shí)現(xiàn)并無(wú)規(guī)定,如HotSpot直接將本地方法棧和虛擬機(jī)棧合并實(shí)現(xiàn)。

      堆(Heap)

      堆區(qū)是用來(lái)分配實(shí)例對(duì)象的空間,各個(gè)線程共享使用,由垃圾收集器自動(dòng)回收管理。

      元空間(MetaSpace)

      用來(lái)存放類(lèi)的元數(shù)據(jù),也就是數(shù)據(jù)結(jié)構(gòu)信息,如類(lèi)常量,字符串常量,類(lèi)定義等數(shù)據(jù)。java8之前,是由永久代進(jìn)行存儲(chǔ)。

      2.JAVA OOM常見(jiàn)場(chǎng)景

      2.1 內(nèi)存溢出

      2.1.1 堆內(nèi)存溢出

      問(wèn)題現(xiàn)象為,創(chuàng)建對(duì)象失敗,提示異常為java.lang.OutOfMemoryError:Java heap space

      其導(dǎo)致原因?yàn)?/p>

      代碼BUG,使用的靜態(tài)容器類(lèi)(List等)沒(méi)有及時(shí)清除對(duì)象

      讀入數(shù)據(jù)過(guò)大,比如在讀取數(shù)據(jù)庫(kù)表時(shí),一次性撈出全部數(shù)據(jù)

      數(shù)據(jù)積壓,在生產(chǎn)者消費(fèi)者模型中,生產(chǎn)很快,消費(fèi)很慢,數(shù)據(jù)來(lái)不及處理,隊(duì)列又沒(méi)有限制,長(zhǎng)時(shí)間運(yùn)行導(dǎo)致內(nèi)存溢出

      解決方法為

      確保代碼沒(méi)BUG時(shí),通過(guò)壓測(cè)調(diào)整-Xms參數(shù),-Xmx參數(shù)

      大量數(shù)據(jù)如大文件加載,大批量數(shù)據(jù)查詢(xún)時(shí)分批處理

      隊(duì)列添加限制,提高執(zhí)行效率,加速垃圾回收,避免并發(fā)高時(shí)無(wú)足夠內(nèi)存空間

      import java.util.ArrayList; import java.util.List; /** * 堆內(nèi)存溢出 * —Xmx10M * * @since 2022-04-15 */ public class HeapOom { public static void main(String[] args) { List list = new ArrayList<>(); int count = 1; while (true) { list.add(new byte[1024 * 1024]); count++; } } }

      2.1.2 MetaSpace內(nèi)存溢出

      問(wèn)題現(xiàn)象為 程序運(yùn)行時(shí)報(bào)java.lang.OutOfMemoryError:Metaspace

      該問(wèn)題原因?yàn)?/p>

      應(yīng)用代碼過(guò)多

      引用三方庫(kù)多

      動(dòng)態(tài)生成加載類(lèi)

      解決方法為

      調(diào)整-XX:MaxMetaSpaceSize參數(shù)

      不需要的三方庫(kù)及時(shí)刪除

      動(dòng)態(tài)生成類(lèi)時(shí)做好壓力測(cè)試

      import java.util.ArrayList; import java.util.List; import javassist.CannotCompileException; import javassist.ClassPool; /** * 元數(shù)據(jù)溢出 * -XX:MaxMetaspaceSize=10m -XX:-UseCompressedClassPointers * * @since 2022-04-15 */ public class MetaspaceOom { public static ClassPool pool = ClassPool.getDefault(); public static void main(String[] args) throws CannotCompileException { List test = new ArrayList<>(); for (int i = 0; ; i++) { Class c = pool.makeClass("com.huawei.iit.Generated" + i).toClass(); } } }

      2.1.3 堆外內(nèi)存溢出

      問(wèn)題現(xiàn)象為,程序報(bào)java.lang.OutOfMemoryError … Native Method

      問(wèn)題原因就是采用了很多NIO相關(guān)操作,沒(méi)有及時(shí)釋放

      解決方法則是減少長(zhǎng)生命周期的堆外內(nèi)存引用,及時(shí)釋放空間

      import java.lang.reflect.Field; import sun.misc.Unsafe; /** * 堆外內(nèi)存溢出 * -Xmx10m * * @since 2022-04-15 */ public class DirectMemoryOomError { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); while (true) { unsafe.allocateMemory(1024 * 1024); } } }

      2.1.4 棧內(nèi)存溢出

      問(wèn)題現(xiàn)象為,應(yīng)用拋出異常java.lang.StackOverflowError

      問(wèn)題原因則是

      方法調(diào)用太深,導(dǎo)致棧中內(nèi)存被消耗殆盡,主要是遞歸操作

      問(wèn)題解決方法是

      合理設(shè)置-Xss值

      遞歸操作注意終止條件,關(guān)注遞歸層數(shù)

      /** * 棧溢出 * * @since 2022-04-15 */ public class StackOverflowOom { public static void testStack(){ Byte[] temp = new Byte[1024*1024]; testStack(); } public static void main(String[] args) { testStack(); } }

      2.1.5 本地線程內(nèi)存溢出

      問(wèn)題現(xiàn)象為,應(yīng)用拋出異常java.lang.OutOfMemoryError:unable to create new native thread error.

      問(wèn)題原因?yàn)?/p>

      線程數(shù)過(guò)多,在新創(chuàng)建線程時(shí),剩余內(nèi)存無(wú)法滿足線程創(chuàng)建需求

      線程數(shù)量超過(guò)操作系統(tǒng)限制

      問(wèn)題解決方法為

      檢查操作系統(tǒng)線程限制,如Linux,可以用ulimit -a查看

      應(yīng)用明確最大線程數(shù),使用線程池,不隨意創(chuàng)建線程及線程池

      import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * 本地線程內(nèi)存溢出 * -XX:ThreadStackSize=1G * * @since 2022-04-15 */ public class UnableCreateNativeThreadError { public static void main(String[] args) { while (true) { Executor pool = Executors.newCachedThreadPool(); pool.execute(()-> System.out.println("Test")); } } }

      2.1.6 數(shù)組超限內(nèi)存溢出

      問(wèn)題現(xiàn)象為,應(yīng)用拋出異常java.lang.OutOfMemoryError: Requested array size exceeds VM limit

      問(wèn)題原因?yàn)?/p>

      試圖分配超出該平臺(tái)下JVM最大數(shù)組限制大小的數(shù)組

      解決辦法

      避免分配大數(shù)組

      /** * 數(shù)組內(nèi)存超限制 * * @since 2022-04-15 */ public class ArrayLimitOom { public static void main(String[] args) { int[] arr = new int[Integer.MAX_VALUE-1]; } }

      2.1.7 超出交換分區(qū)

      問(wèn)題現(xiàn)象為,啟動(dòng)時(shí)拋出java.lang.OutOfMemoryError:Out of swap space

      問(wèn)題原因是

      jvm啟動(dòng)時(shí),按照-Xms及其他參數(shù)申請(qǐng)內(nèi)存,當(dāng)請(qǐng)求內(nèi)存大于可用內(nèi)存時(shí),進(jìn)程請(qǐng)求分配內(nèi)存失敗

      解決方法是

      清理環(huán)境內(nèi)存,騰出空間

      2.1.8 OS Killer進(jìn)程

      問(wèn)題現(xiàn)象為,java進(jìn)程突然消失了,日志未見(jiàn)明顯異常,用dmesg發(fā)現(xiàn)Out of memory: Kill process ** (java) score ** or sacrifice child

      問(wèn)題原因?yàn)?/p>

      Out-Of-Memory killer機(jī)制監(jiān)控機(jī)器的內(nèi)存資源,當(dāng)發(fā)現(xiàn)內(nèi)存臨近耗盡,就會(huì)掃描所有進(jìn)程(按照一定規(guī)則計(jì)算,內(nèi)存占用、時(shí)間等),然后kill掉最高得分的進(jìn)程,保護(hù)機(jī)器運(yùn)行環(huán)境

      2.1.8 GC回收超時(shí)

      問(wèn)題現(xiàn)象為,應(yīng)用響應(yīng)慢,拋出java.lang.OutOfMemoryError:GC overhead limit exceeded

      問(wèn)題原因?yàn)椋?/p>

      應(yīng)用在內(nèi)存不足時(shí)進(jìn)行GC操作回收內(nèi)存,而JVM花費(fèi)大量時(shí)間進(jìn)行GC卻只回收了些微內(nèi)存,超過(guò)一定次數(shù)便會(huì)觸發(fā)該錯(cuò)誤

      解決方法:

      減少對(duì)象生命周期,盡量做到朝生夕滅

      可分析Stop World停頓日志周?chē)鶪C情況

      import java.util.HashMap; import java.util.Map; import java.util.Random; /** * GC回收超時(shí) * -Xmx10m * * @since 2022-04-15 */ public class OverHeadLimitOom { public static void main(String[] args) { Map map = new HashMap<>(); Random r = new Random(); while(true){ map.put(r.nextInt(),"Test"); } } }

      2.2 內(nèi)存泄漏

      問(wèn)題現(xiàn)象為:實(shí)例數(shù)不斷增加

      問(wèn)題原因?yàn)?/p>

      從GC ROOT對(duì)象往下,無(wú)用對(duì)象依然是可達(dá)的

      實(shí)際就是一些不用的,無(wú)引用的,應(yīng)該被GC回收到的對(duì)象,無(wú)法被回收,一直保留,導(dǎo)致內(nèi)存泄漏,一般常見(jiàn)于HASH容器中,修改了KEY的值,導(dǎo)致后續(xù)刪除操作實(shí)際上無(wú)法刪除,一直存留。

      /** * 內(nèi)存泄漏 * -Xmx20m * * @since 2022-04-15 */ public class MemoryLeakOom { static class Email { private String address; public Email(String address) { this.address = address; } public int hashCode() { return address.hashCode(); } } public static void main(String[] args) { HashSet emails = new HashSet<>(); for (int i = 1; ; i++) { Email email = new Email(i + "."); emails.add(email); email.address = i + ".in"; emails.remove(email); } } }

      3 問(wèn)題定位

      問(wèn)題場(chǎng)景:應(yīng)用運(yùn)行一段時(shí)間后,悄無(wú)聲息結(jié)束了,或者容器重啟了

      定位方法;

      步驟1: dmesg|grep -i kill 觀察是否是OS kill了進(jìn)程

      步驟2: JVM啟動(dòng)參數(shù)查看

      jinfo -flags $PID

      步驟3: 堆內(nèi)存分析

      jmap -heap $PID 查看堆內(nèi)存

      jmap -histo $PID 查看堆內(nèi)對(duì)象

      步驟4: 線程情況分析

      jstack -m $PID 查看線程

      內(nèi)存占用分析

      在啟動(dòng)時(shí)加入啟動(dòng)參數(shù)-XX:NativeMemoryTracking=summary

      創(chuàng)建基線 jcmd $pid VM.native_memory baseline

      查看內(nèi)存變化 jcmd $pid VM.native_memory summary.diff

      需要注意的是NativeMemoryTracking功能大約會(huì)有5%-10%的性能損耗,測(cè)試環(huán)境添加尚可。

      Java

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶(hù)投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:推箱子小游戲的簡(jiǎn)易實(shí)現(xiàn)
      下一篇:matlab 2017幫助頁(yè)目錄
      相關(guān)文章
      亚洲毛片免费视频| jizzjizz亚洲| xvideos亚洲永久网址| 亚洲va久久久久| 91亚洲视频在线观看| 亚洲精品第五页中文字幕| 国产V亚洲V天堂A无码| 亚洲综合精品香蕉久久网| 亚洲综合色视频在线观看| 亚洲免费一区二区| 亚洲精品无码国产片| 亚洲精品宾馆在线精品酒店| 亚洲AV人无码综合在线观看| 亚洲AV午夜福利精品一区二区| 亚洲国产精品成人综合久久久| 亚洲免费视频在线观看| 国产午夜亚洲精品理论片不卡| 亚洲午夜福利精品久久 | 亚洲日韩欧洲无码av夜夜摸| 亚洲精品成人片在线观看| 亚洲午夜在线一区| 亚洲一区二区三区夜色| 亚洲天堂中文字幕| 亚洲中文字幕无码中文字在线| 亚洲免费人成在线视频观看| 亚洲AV无一区二区三区久久| 亚洲AV无码一区二区乱孑伦AS| 亚洲一二成人精品区| 亚洲成年人电影在线观看| 色老板亚洲视频免在线观| 亚洲午夜无码毛片av久久京东热| 亚洲heyzo专区无码综合| 亚洲国产天堂久久综合| 国产亚洲综合成人91精品| 亚洲视频在线视频| 亚洲伊人精品综合在合线| 亚洲av无码av在线播放| 亚洲午夜精品一级在线播放放| 亚洲成A人片在线观看无码不卡| 亚洲日本在线观看| 亚洲情A成黄在线观看动漫软件|