還不懂JVM內(nèi)存管理?
一、物理內(nèi)存與虛擬內(nèi)存
物理內(nèi)存RAM(隨機(jī)存儲(chǔ)器),寄存單元為寄存器,用于存儲(chǔ)計(jì)算單元執(zhí)行指令的中間結(jié)果。
連接處理器和RAM或者處理器和寄存器的是地址總線,這個(gè)地址的寬度影響了物理地址的索引范圍,總線的寬度決定了處理器一次可以從寄存器或者內(nèi)存中獲取多少個(gè)bit。
虛擬內(nèi)存的出現(xiàn)使不同進(jìn)程在同時(shí)運(yùn)行時(shí)可以共享物理內(nèi)存,提高內(nèi)存利用率,而且能擴(kuò)展內(nèi)存的地址空間。
二、內(nèi)核空間與用戶空間
一個(gè)電腦4GB的地址空間被劃分為內(nèi)核空間和用戶空間,程序只能使用用戶空間的內(nèi)存。
內(nèi)核空間主要是指操作系統(tǒng)運(yùn)行時(shí)所使用的用于程序調(diào)度、虛擬內(nèi)存的使用或者連接硬件資源等的程序邏輯。
三、Java中那些組件需要使用內(nèi)存
Java堆
Java堆是用于存儲(chǔ)Java對象的內(nèi)存區(qū)域,堆的大小在JVM啟動(dòng)時(shí)就一次向操作系統(tǒng)申請完成,通過-Xmx(最大)和-Xms(初始)兩個(gè)選項(xiàng)來控制大小,一旦分配完成后就固定了。Java堆中內(nèi)存空間的管理由JVM來控制,對象創(chuàng)建由Java應(yīng)用程序控制,對象所占的空間釋放由管理內(nèi)存的垃圾收集器來完成。
線程
JVM運(yùn)行實(shí)際程序的實(shí)體是線程,而線程需要內(nèi)存空間來存儲(chǔ)必要的數(shù)據(jù)。每個(gè)線程創(chuàng)建時(shí)JVM都會(huì)為它創(chuàng)建一個(gè)堆棧。
類和類的加載器
Java中類和加載類本身同樣需要存儲(chǔ)空間,這個(gè)區(qū)域叫永久帶(PermGen區(qū))。
卸載類(內(nèi)存回收)條件:
1、Java堆中沒有對表示該加載器的java.lang.ClassLoader對象引用
2、Java堆沒有對表示類加載器的類的任何java.lang.Class對象的引用
3、在Java堆上該類加載器加載的任何類的所有對象都不在存活(被引用)
NIO
NIO使用java.nio.ByteBuffer.allocateDirect()方法分配內(nèi)存,是本機(jī)內(nèi)存而不是Java堆上的內(nèi)存,增加了一次系統(tǒng)調(diào)用。直接ByteBuffer產(chǎn)生的數(shù)據(jù)和網(wǎng)絡(luò)或者磁盤交互都在操作系統(tǒng)的內(nèi)核空間中發(fā)生,不需要將數(shù)據(jù)復(fù)制到Java內(nèi)存中,加快數(shù)據(jù)處理速度。
JNI
JNI技術(shù)使得本機(jī)代碼(C語言)可以調(diào)用Java方法,即native memory
四、JVM內(nèi)存結(jié)構(gòu)
在Java虛擬機(jī)規(guī)范中將Java運(yùn)行時(shí)數(shù)據(jù)劃分為6種
PC寄存器數(shù)據(jù)
保存當(dāng)前執(zhí)行的程序的內(nèi)存地址
Java棧
Java棧總是和線程關(guān)聯(lián),每當(dāng)創(chuàng)建一個(gè)線程時(shí),JVM就會(huì)為這個(gè)線程創(chuàng)建一個(gè)對應(yīng)的Java棧,這個(gè)棧中又會(huì)含有多個(gè)棧幀,這些棧幀是與每個(gè)方法關(guān)聯(lián)起來,每運(yùn)行一個(gè)方法就創(chuàng)建一個(gè)棧幀,每一個(gè)棧幀會(huì)含有一些內(nèi)存變量,操作棧和方法返回值等信息。
堆
堆是存放Java對象的地方,是JVM管理Java對象的核心內(nèi)存區(qū)域,每個(gè)存儲(chǔ)在堆中的Java對象都是這個(gè)對象的類的一個(gè)副本,它會(huì)復(fù)制包括繼承自它父類的所有非靜態(tài)屬性
方法區(qū)
JVM方法區(qū)用于存儲(chǔ)類結(jié)構(gòu)信息的地方,在Java堆中的永久區(qū)內(nèi),在啟動(dòng)程序后一段時(shí)間就固定餓了
本地方法棧
是為JVM運(yùn)行Native方法準(zhǔn)備的空間,由于很多Native方法是C語言實(shí)現(xiàn)的,所以也叫C棧
運(yùn)行時(shí)常量池
代表運(yùn)行時(shí)每個(gè)class文件中的常量表
五、JVM內(nèi)存結(jié)構(gòu)
在Java虛擬機(jī)規(guī)范中將Java運(yùn)行時(shí)數(shù)據(jù)劃分為6種
PC寄存器數(shù)據(jù)
保存當(dāng)前執(zhí)行的程序的內(nèi)存地址
Java棧
Java棧總是和線程關(guān)聯(lián),每當(dāng)創(chuàng)建一個(gè)線程時(shí),JVM就會(huì)為這個(gè)線程創(chuàng)建一個(gè)對應(yīng)的Java棧,這個(gè)棧中又會(huì)含有多個(gè)棧幀,這些棧幀是與每個(gè)方法關(guān)聯(lián)起來,每運(yùn)行一個(gè)方法就創(chuàng)建一個(gè)棧幀,每一個(gè)棧幀會(huì)含有一些內(nèi)存變量,操作棧和方法返回值等信息。
堆
堆是存放Java對象的地方,是JVM管理Java對象的核心內(nèi)存區(qū)域,每個(gè)存儲(chǔ)在堆中的Java對象都是這個(gè)對象的類的一個(gè)副本,它會(huì)復(fù)制包括繼承自它父類的所有非靜態(tài)屬性
方法區(qū)
JVM方法區(qū)用于存儲(chǔ)類結(jié)構(gòu)信息的地方,在Java堆中的永久區(qū)內(nèi),在啟動(dòng)程序后一段時(shí)間就固定餓了
本地方法棧
是為JVM運(yùn)行Native方法準(zhǔn)備的空間,由于很多Native方法是C語言實(shí)現(xiàn)的,所以也叫C棧
運(yùn)行時(shí)常量池
代表運(yùn)行時(shí)每個(gè)class文件中的常量表
六、JVM內(nèi)存回收策略
靜態(tài)內(nèi)存分配和回收
Java中靜態(tài)內(nèi)存分配指在Java被編譯時(shí)就已經(jīng)能夠確定需要的內(nèi)存空間,當(dāng)程序加載時(shí)系統(tǒng)把內(nèi)存一次性分配給它。這些內(nèi)存只有在程序結(jié)束時(shí)才被收回。
動(dòng)態(tài)內(nèi)存分配和回收
Java中對象的內(nèi)存空間是動(dòng)態(tài)分配的,就是在程序執(zhí)行時(shí)才知道要分配的存儲(chǔ)空間大小,只有等到對象不再使用時(shí)才會(huì)被回收。
如何檢測垃圾
只要某個(gè)對象不再被其他活動(dòng)對象引用,那么這個(gè)對象就可以被回收?;顒?dòng)對象指能夠被一個(gè)根對象集合到達(dá)的對象。
根對象集合:
1、方法中局部變量區(qū)中對象的引用
2、Java操作棧中的對象引用
3、常量池中對象引用
4、本地方法中持有的對象引用
5、類的Class對象
基于分代的垃圾收集算法

Young區(qū) 分為Eden區(qū)和兩個(gè)Survivor區(qū),其中新創(chuàng)建的對象都在Eden區(qū),當(dāng)Eden區(qū)滿后觸發(fā)minor GC 將Eden區(qū)仍然存活的對象復(fù)制到Survivor區(qū)中,另外一個(gè)Survivor區(qū)中的存活對象也復(fù)制到這個(gè)Survivor中,保證始終有一個(gè)Survivor區(qū)是空的。
Old區(qū)存放的是Young區(qū)的Survivor滿后觸發(fā)minno GC后仍然存活的對象,當(dāng)Eden區(qū)滿后將對象存放到Survivor區(qū)中,如果Survivor中仍然存放不下這些對象,GC收集器會(huì)將這些對象直接存放到Old區(qū)。如果Survivor區(qū)中對象足夠老,也直接存放到Old區(qū)。如果Old區(qū)也滿了,將會(huì)觸發(fā)Full GC回收整個(gè)堆內(nèi)存。
Perm區(qū)存放的主要是類的Class對象,如果一個(gè)類被頻繁地加載,也可能會(huì)導(dǎo)致Perm區(qū)滿,Perm區(qū)的垃圾回收也是Full GC觸發(fā)的。
三類垃圾收集算法:
1、Serial Collector
是JVM在client模式下的默認(rèn)的GC方式,通過JVM配置參數(shù)-XX:+UseSerialGC來指定GC使用該手機(jī)算法。當(dāng)Eden空間不足時(shí)就觸發(fā)Minor GC,觸發(fā)Minor GC時(shí)首先檢查之前每次Minor GC時(shí)晉升到Old區(qū)的平均對象大小是否大于Old區(qū)的剩余空間,如果大于,則直接觸發(fā)Full GC,如果小于,則看HandlePromotionFailur參數(shù)的值。如果為true,僅觸發(fā)Minor GC,否則再觸發(fā)一次Full GC。
Java參數(shù):java -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails
2、Parallel Collector
Parallel GC根據(jù)Minor GC 和Full GC的不同分為三種,分別是ParNewGC、ParallelGC和ParallelOldGC。
1)ParNewGC:通過-XX:+UseParNewGC參數(shù)來指定,對象分配和回收策略與Serial Collector類似,只是回收的線程是多線程的。
2)ParallelGC:Server下默認(rèn)的GC方式,當(dāng)在Eden區(qū)申請內(nèi)存空間時(shí),如果Eden區(qū)不夠,那么看當(dāng)前申請的空間是否大于等于Eden的一半,如果大于則這次申請的空間直接在Old中分配,小于則觸發(fā)Minor GC。在觸發(fā)GC之前首先會(huì)檢查每次晉升到Old區(qū)的平均大小是否大于Old區(qū)的剩余空間,如果大于則再出發(fā)Full GC。在這次觸發(fā)GC后仍然會(huì)按照這個(gè)規(guī)則重新檢查一次。
JVM參數(shù):java -Xms20M -Xmx20M -Xmn10M -XX:+UsePaallelGC -XX:+PrintGCDetails
3)ParallelOldGC
和ParallelGC的區(qū)別:前者Full GC進(jìn)行的動(dòng)作為清空整個(gè)Heap堆中的垃圾對象,清楚Perm區(qū)中已經(jīng)被卸載的類信息,并進(jìn)行壓縮。而后者是清楚Heap堆中部分垃圾對象,并進(jìn)行部分的空間壓縮。
3、CMS Collector
觸發(fā)規(guī)則:檢查Old區(qū)或者Perm區(qū)的使用率,當(dāng)達(dá)到一定比例時(shí)觸發(fā)CMS GC,觸發(fā)時(shí)會(huì)回收Old區(qū)中的內(nèi)存空間。觸發(fā)Full GC:1)Eden分配失敗,Minor GC后分配到To Space,To
Space不夠再分配到Old區(qū),Old區(qū)不夠再出發(fā)Full GC 2)當(dāng)CMS GC正在進(jìn)行時(shí)向Old申請內(nèi)存失敗則會(huì)直接觸發(fā)Full GC。
4、三種GC優(yōu)缺點(diǎn)對比

七、內(nèi)存問題分析
GC日志分析

<collector>GC 表示收集器的名稱
<starting occupancy1>表示Young區(qū)在GC前占用的內(nèi)存。
<ending occupancy1>表示Young區(qū)在GC后占用的內(nèi)存。
<pause time1>表示Young區(qū)局部收集時(shí)JVM暫停處理的時(shí)間。
<starting occupany2>表示JVM Heap在GC前占用的內(nèi)存
<ending occupany2>表示JVM Heap在GC后占用的內(nèi)存
<pause time2>表示GC過程中JVM暫停處理的總時(shí)間
根據(jù)日志判斷是否存在內(nèi)存泄漏,
如果<ending occupancy1>-<starting occupany1>=<ending occupancy2>-<starting occupany2>,則表明這次GC對象100%被回收,沒有對象進(jìn)入Old區(qū)或者Perm區(qū)。
如果是大于號(hào)則這次回收對象進(jìn)入Old區(qū)或者Perm區(qū)。如果<ending occupany2>一直增長,而且Full GC很頻繁,則可能內(nèi)存泄漏。
需要更多java學(xué)習(xí)資料的小伙伴可以在評論區(qū)回復(fù)“666”~

