右鍵+清除本來(lái)好好的按一個(gè)N就可以,現(xiàn)在整的啥?清除個(gè)內(nèi)容還要按3個(gè)鍵?
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)存模型
內(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
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
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
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
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)容。