一、Java內(nèi)存區(qū)域
1.Java虛擬機運行時數(shù)據(jù)區(qū)
- 程序計數(shù)器(線程私有):如果執(zhí)行的是Java方法,則這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址,如果是Native方法,這個計數(shù)器值為空。
- Java虛擬機棧(線程私有):每個方法在執(zhí)行時都會創(chuàng)建一個棧幀,用于存儲局部變量表,操作數(shù)棧,動態(tài)鏈接,方法出口、常量池引用等信息。通過-Xss來指定虛擬機棧內(nèi)存的大小。
- 本地方法棧:為Native方法服務(wù)。
- Java堆(線程共享):所有對象在這里分配內(nèi)存,是垃圾收集的主要區(qū)域。-Xms設(shè)置堆初始大小,-Xmx設(shè)置堆最大內(nèi)存。堆可以細(xì)分為新生代和老年代,再細(xì)致一點可以分為Eden,F(xiàn)rom Survivor,To Survivor。
- 方法區(qū)(線程共享):用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。
2.對象的創(chuàng)建過程
- 類加載檢查
- 為對象分配內(nèi)存
- 將分配到的內(nèi)存空間初始化置零(不包括對象頭)
- 設(shè)置對象頭
- 初始化成員變量,并執(zhí)行構(gòu)造函數(shù)
3.OOM異常(內(nèi)存溢出)
- Java堆溢出
- 虛擬機棧和本地方法棧溢出
- 方法區(qū)和運行時常量池溢出
- 本機直接內(nèi)存溢出
3.1 JVM調(diào)優(yōu)
- 首先把OOM文件dump下來
- 使用jprofiler工具加載dump文件,查看是否有大對象占用很大內(nèi)存,此外還可以定位到哪行代碼出現(xiàn)問題
- 嘗試調(diào)大堆內(nèi)存
內(nèi)存泄漏:程序申請內(nèi)存后,無法釋放已申請的內(nèi)存空間,一次內(nèi)存泄漏危害可以忽略,但內(nèi)存泄漏堆積后果很嚴(yán)重,無論內(nèi)存多大都會被占光。
二、垃圾收集
1.判斷對象是否已死
- 引用計數(shù)法:為對象添加計數(shù)器,引用為0時可被回收。缺點:當(dāng)存在循環(huán)引用時,所占用的內(nèi)存永遠(yuǎn)無法被回收。
- 可達(dá)性分析算法:以GC Roots為起點搜索,可達(dá)對象都是存貨的,不可達(dá)對象都被回收。
可作為GC Roots對象的:
- 虛擬機棧中引用的對象
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法棧中Native方法引用的對象
2.引用
- 強引用:
被強引用關(guān)聯(lián)的對象不會被回收。使用 new 一個新對象的方式來創(chuàng)建強引用。
Object obj = new Object();
- 軟引用:只有在內(nèi)存不夠的情況下才回收。使用 SoftReference 類來創(chuàng)建軟引用。
假設(shè)有一個應(yīng)用需要大量讀取本地的圖片:每次讀取圖片都從磁盤讀取會嚴(yán)重影響性能,如果一次性全部加載到內(nèi)存中又可能造成溢出,此時用軟引用就可以解決這個問題。
- 弱引用:被弱引用關(guān)聯(lián)的對象一定會被回收。使用 WeakReference 類來創(chuàng)建弱引用。
- 虛引用(幽靈引用/幻影引用):為一個對象設(shè)置虛引用的唯一目的是能在這個對象被回收時收到一個系統(tǒng)通知。一個對象是否有虛引用的存在,不會對其生存時間造成影響,也無法通過虛引用得到一個對象。使用 PhantomReference 來創(chuàng)建虛引用。
3.finalize()
在可達(dá)性分析中不可達(dá)的對象,也并不是一定會被回收的,真正宣告它死亡,至少需要兩次標(biāo)記過程??蛇_(dá)性分析中的不可達(dá)對象會被進(jìn)行第一次標(biāo)記,并根據(jù)此對象是否有必要執(zhí)行finalize()方法進(jìn)行一次篩選。如果沒有覆蓋finalize()方法或已被執(zhí)行過,則都被視為沒有必要執(zhí)行。
如果有必要執(zhí)行finalize()方法,會被放入F-Queue中,有可能會通過finalize()方法實現(xiàn)自救。
4.回收方法區(qū)
常量池的回收和類的卸載
5.垃圾收集算法
- 標(biāo)記-清除:會產(chǎn)生很多內(nèi)存碎片,后續(xù)會因為沒有連續(xù)內(nèi)存空間分配給大對象而提前進(jìn)行垃圾收集。
- 復(fù)制算法:不會產(chǎn)生內(nèi)存碎片,但只用到一半的內(nèi)存。
- 標(biāo)記-整理算法:沒有內(nèi)存碎片,效率偏低。
6.垃圾收集器
- Serial 收集器(新生代,復(fù)制算法;老年代,標(biāo)記整理算法):
Serial 翻譯為串行,也就是說它以串行的方式執(zhí)行。它是單線程的收集器,只會使用一個線程進(jìn)行垃圾收集工作。 會暫停所有的用戶線程,不適合服務(wù)器環(huán)境。 - ParNew收集器(新生代,復(fù)制算法;老年代,標(biāo)記整理算法):
Serial收集器的多線程版本。 - Parallel Scavenge收集器:與 ParNew 一樣是多線程收集器。其它收集器目標(biāo)是盡可能縮短垃圾收集時用戶線程的停頓時間,而它的目標(biāo)是達(dá)到一個可控制的吞吐量,因此它被稱為“吞吐量優(yōu)先”收集器。這里的吞吐量指 CPU 用于運行用戶程序的時間占總時間的比值。在注重吞吐量以及 CPU 資源敏感的場合,都可以優(yōu)先考慮 Parallel Scavenge 加 Parallel Old 收集器
- CMS 收集器(用于老年代,標(biāo)記-清除算法):
是一種以獲取最短停頓回收時間為目標(biāo)的收集器。
初始標(biāo)記:僅僅只是標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)到的對象,速度很快,需要停頓。
并發(fā)標(biāo)記:進(jìn)行 GC Roots Tracing 的過程,它在整個回收過程中耗時最長,不需要停頓。
重新標(biāo)記:為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄,需要停頓。
并發(fā)清除:不需要停頓。 - G1收集器(整體采用標(biāo)記整理,局部采用復(fù)制):G1(Garbage-First),它是一款面向服務(wù)端應(yīng)用的垃圾收集器,在多 CPU 和大內(nèi)存的場景下有很好的性能。堆被分為新生代和老年代,其它收集器進(jìn)行收集的范圍都是整個新生代或者老年代,而 G1 可以直接對新生代和老年代一起回收。解決了CMS內(nèi)存碎片的問題,同時使停頓時間更短,甚至用戶可以自己指定停頓時間。
7.內(nèi)存分配與回收策略
- 對象優(yōu)先在Eden分配
- 大對象(需要大量連續(xù)內(nèi)存空間的對象 )直接進(jìn)入老年代
- 長期存活(默認(rèn)為15歲,每熬過一次minor GC 算一次)的對象將進(jìn)入老年代
- 如果Survivor空間中相同年齡所有對象大小總和超過Survivor空間的一半,則年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代。
三、類加載機制
1.類的生命周期
加載-驗證-準(zhǔn)備-解析-初始化-使用-卸載
- 加載:
- 通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流
- 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
- 在內(nèi)存中生成一個代表這個類的Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口
- 驗證:確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機的要求,并且不會危害虛擬機自身的安全
- 準(zhǔn)備:類變量是被 static 修飾的變量,準(zhǔn)備階段為類變量分配內(nèi)存并設(shè)置初始值,使用的是方法區(qū)的內(nèi)存。實例變量不會在這階段分配內(nèi)存,它會在對象實例化時隨著對象一起被分配在堆中。
- 解析:將常量池的符號引用替換為直接引用的過程,解析某些時候可以在初始化后
- 初始化:
以下5種情況需要立即對類進(jìn)行初始化:
1.遇到new、getstatic、putstatic、invokestatic這4條字節(jié)碼指令時。
2.使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用時
3.當(dāng)初始化一個類時,發(fā)現(xiàn)其父類還沒有初始化
4.虛擬機啟動時,會初始化主類(main)
5.如果一個java.lang.invoke.MethodHandle時力最后的解析結(jié)果為REF_getStatic、REF_putStatic、REF_invokeStaitc方法句柄時。
2.類加載器
- 啟動類加載器:加載存放在<JAVA_HOME>\lib中的
- 擴展類加載器:加載<JAVA_HOME>\lib\ext中的
- 應(yīng)用程序類加載器:加載用戶類路徑上所指定的類庫
3.雙親委派模型
- 工作過程:一個類加載器首先將類加載請求轉(zhuǎn)發(fā)到父類加載器,只有當(dāng)父類加載器無法完成時才嘗試自己加載。
- 好處:保證Java程序的穩(wěn)定運作。使得 Java 類隨著它的類加載器一起具有一種帶有優(yōu)先級的層次關(guān)系,從而使得基礎(chǔ)類得到統(tǒng)一。