JVM架構(gòu)
JVM = 類加載器(classloader) + 執(zhí)行引擎(execution engine) + 運行時數(shù)據(jù)區(qū)(runtime data area)
內(nèi)存分類
按空間劃分為兩類:堆內(nèi)存(Heap) 筒繁、非堆內(nèi)存(NonHeap)
堆內(nèi)存(Heap)
- 堆是Java 虛擬機所管理的內(nèi)存中最大的一塊阳准,默認(rèn)為物理內(nèi)存1/4大小
- 堆是被所有線程共享的區(qū)域叶撒,在虛擬機啟動時創(chuàng)建
- 堆里面存放的都是對象的實例(new 出來的對象都存在堆中)
- 我們平常所說的垃圾回收,主要回收的就是堆區(qū)
- 默認(rèn)的悟民,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2
- 默認(rèn)的丸卷,Eden : From : To = 8 : 1 : 1 ( 可以通過參數(shù) –XX:SurvivorRatio 來設(shè)定 )
Thread Local Allocation Buffer (TLAB挺举,線程本地分配緩沖區(qū))
- 占用 Eden 區(qū)(缺省 Eden 的1%)弱恒,默認(rèn)開啟,線程私有
- 優(yōu)化多線程堆空間分配對象指針碰撞問題
非堆內(nèi)存(NonHeap)
Metaspace
- JAVA8以后使用Metaspace替代了PermGen(永久代)棋恼,將元數(shù)據(jù)移動到了非堆內(nèi)存中返弹。默認(rèn)為物理內(nèi)存的1/64大小
- 當(dāng)一個類被加載時,它的類加載器會負(fù)責(zé)在 Metaspace 中分配空間用于存放這個類的元數(shù)據(jù)
- 只有當(dāng)這個類加載器加載的所有類都沒有存活的對象爪飘,并且沒有到達(dá)這些類和類加載器的引用時义起,相應(yīng)的 Metaspace 空間才會被 GC 釋放
- 釋放 Metaspace 的空間,并不意味著將這部分空間還給系統(tǒng)內(nèi)存师崎,這部分空間通常會被 JVM 保留下來
- Metaspace 可能在兩種情況下觸發(fā) GC:Metaspace需要擴容時默终、達(dá)到MaxMetaspaceSize時,所以通常把-XX:MetaspaceSize和-XX:MaxMetaspaceSize設(shè)置為相同的值(Metaspace默認(rèn)大小只有21MB)
Compressed Class Space
- 壓縮指針犁罩,指的是在 64 位的機器上齐蔽,使用 32 位的指針來訪問數(shù)據(jù)(堆中的對象或 Metaspace 中的元數(shù)據(jù))的一種方式
- 它需要被分配一個連續(xù)的地址空間,因此在GC后釋放的空間會被JVM保留床估,一定不會還給物理內(nèi)存
- 默認(rèn)大小是1G含滴,但如果設(shè)置了-XX:MaxMetaspaceSize,則它的大小不會超過MaxMetaspaceSize
Direct Memory
- NIO(New Input/Output)類丐巫,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的 I/O 方式谈况,它可以使用 native 函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在Java堆中的 DirectByteBuffer 對象作為這塊內(nèi)存的引用進行操作
- 可以幫助JAVA實現(xiàn)零拷貝
- 默認(rèn)大小為堆內(nèi)存大小递胧,可通過-XX:MaxDirectMemorySize設(shè)置
- 不會被GC碑韵,使用時要注意釋放
線程共享與私有
堆外內(nèi)存使用指標(biāo)
used
、capacity
缎脾、committed
祝闻、reserved
committed、reserved
committed
和reserved
并不純粹是JVM的概念赊锚,它和操作系統(tǒng)相關(guān)治筒。
reserved
是指,操作系統(tǒng)已經(jīng)為該進程“保留”的舷蒲。所謂的保留耸袜,更加接近一種記賬的概念,就是操作系統(tǒng)承諾說一大塊連續(xù)的內(nèi)存已經(jīng)是你這個進程的了牲平。那么實際上這一大塊內(nèi)存有沒有真實對應(yīng)的物理內(nèi)存堤框,這時是不知道的,等進程committed
的時候,進程真的要用這個連續(xù)地址空間的時候蜈抓,操作系統(tǒng)才會分配真正的內(nèi)存启绰。所以,這也就是意味著沟使,這個過程會失敗委可。used、capacity
比如創(chuàng)建了一個可以存放20個元素的ArrayList
腊嗡,但是我實際上只放了10個元素着倾,那么capacity
就是20,而size就是10燕少,這里的size和used
就是一個概念卡者。
Netty直接內(nèi)存溢出
- 如果有手工分配了DataBuffer但是沒有釋放的時候,Netty會報警
ByteBuf.release()
沒有調(diào)用客们。 - 如果直接內(nèi)存被占滿無法繼續(xù)分配到內(nèi)存空間崇决,會報出
OutOfDirectMemoryError
的異常,大量的請求無法獲取內(nèi)存會導(dǎo)致應(yīng)用掛掉底挫,無法訪問任何接口恒傻。
常見調(diào)優(yōu)參數(shù)
參數(shù)名稱 | 含義 | 默認(rèn)值 | 說明 |
---|---|---|---|
-Xms | ? 初始堆大小 ?????????? | ? 物理內(nèi)存的1/64 ?????????? | 默認(rèn)空余堆內(nèi)存小于40%時(MinHeapFreeRatio),堆增大到-Xmx的值 |
-Xmx | 最大堆大小 | 物理內(nèi)存的1/4 | 默認(rèn)空余堆內(nèi)存大于70%時(MaxHeapFreeRatio)建邓,堆減小到-Xms的值 |
-Xmn | 年輕代大小 | 堆內(nèi)存的1/3 | 此處的大小是eden+ 2 survivor space碌冶,增大年輕代后,將會減小年老代大小 |
-Xss | 每個線程的堆棧大小 | 1M | |
-XX:NewRatio | 年輕代與年老代的比值 | 2 | -XX:NewRatio=4表示年輕代與年老代所占比值為1:4,年輕代占整個堆棧的1/5,當(dāng)Xms=Xmx并且設(shè)置了Xmn的情況下涝缝,該參數(shù)不需要進行設(shè)置 |
-XX:MetaspaceSize | 元空間初始大小 | 21M | 元空間的大小達(dá)到這個值時扑庞,會觸發(fā)Full GC并會卸載沒有用的類 |
-XX:MaxMetaspaceSize | 元空間最大可分配大小 | 物理內(nèi)存的1/64 | |
-XX:MaxDirectMemorySize | 直接內(nèi)存最大可分配大小 | 與堆內(nèi)存相同 |
- 應(yīng)用可分配的最大內(nèi)存
[-Xmx] + 線程數(shù)量*[-Xss] + [-XX:MaxMetaspaceSize] + [-XX:MaxDirectMemorySize]
垃圾回收(GC)
有不少人把這項技術(shù)當(dāng)作Java語言的伴生產(chǎn)物。事實上,垃圾收集的歷史遠(yuǎn)遠(yuǎn)比Java久遠(yuǎn),在1960年誕生于麻省理工學(xué)院的Lisp是第一門開始使用內(nèi)存動態(tài)分配和垃圾收集技術(shù)的語言躯肌。
GC重點
- 程序計數(shù)器、虛擬機棧栅隐、本地方法棧3個區(qū)域隨線程而生,隨線程而滅玩徊,因此這幾個區(qū)域的內(nèi)存分配和回收都具備確定性租悄,不需要過多考慮如何回收的問題,當(dāng)方法結(jié)束或者線程結(jié)束時恩袱,內(nèi)存自然就跟隨著回收了泣棋。
- 而Java堆和方法區(qū)(元數(shù)據(jù)區(qū))這兩個區(qū)域則有著很顯著的不確定性,只有處于運行期間畔塔,我們才能知道程序究竟會創(chuàng)建哪些對象潭辈,創(chuàng)建多少個對象鸯屿,這部分內(nèi)存的分配和回收是動態(tài)的。垃圾收集器所關(guān)注的正是這部分內(nèi)存該如何管理把敢。
可達(dá)性分析法
當(dāng)前主流的商用程序語言(Java寄摆、C#,上溯至前面提到的古老的Lisp)的內(nèi)存管理子系統(tǒng)修赞,都是通過可達(dá)性分析(Reachability Analysis)算法來判定對象是否存活的婶恼。
在Java技術(shù)體系里面,固定可作為GC Roots的對象包括以下幾種:
- 在虛擬機棧(棧幀中的本地變量表)中引用的對象柏副,譬如各個線程被調(diào)用的方法堆棧中使用到的參數(shù)熙尉、局部變量、臨時變量等
- 在方法區(qū)中類靜態(tài)屬性引用的對象搓扯,譬如Java類的引用類型靜態(tài)變量。
- 在方法區(qū)中常量引用的對象包归,譬如字符串常量池(String Table)里的引用
- 在本地方法棧中JNI(即通常所說的Native方法)引用的對象
- Java虛擬機內(nèi)部的引用锨推,如基本數(shù)據(jù)類型對應(yīng)的Class對象,一些常駐的異常對象(比如NullPointExcepiton公壤、OutOfMemoryError)等换可,還有系統(tǒng)類加載器
- 所有被同步鎖(synchronized關(guān)鍵字)持有的對象
- 反映Java虛擬機內(nèi)部情況的JMXBean、JVMTI中注冊的回調(diào)厦幅、本地代碼緩存等
分代收集理論
當(dāng)前商業(yè)虛擬機的垃圾收集器沾鳄,大多數(shù)都遵循了“分代收集”(Generational Collection)[1]的理論進行設(shè)計,分代收集名為理論确憨,實質(zhì)是一套符合大多數(shù)程序運行實際情況的經(jīng)驗法則译荞,它建立在兩個分代假說之上:
- 弱分代假說(Weak Generational Hypothesis):絕大多數(shù)對象都是朝生夕滅的
- 強分代假說(Strong Generational Hypothesis):熬過越多次垃圾收集過程的對象就越難以消亡
這兩個分代假說共同奠定了多款常用的垃圾收集器的一致的設(shè)計原則:
- 收集器應(yīng)該將Java堆劃分出不同的區(qū)域,然后將回收對象依據(jù)其年齡(年齡即對象熬過垃圾收集過程的次數(shù))分配到不同的區(qū)域之中存儲
在一次次只局限于新生代區(qū)域內(nèi)的收集中休弃,新生代中的對象是完全有可能被老年代所引用的吞歼,因此對分代收集理論添加第三條經(jīng)驗法則:
- 跨代引用假說(Intergenerational Reference Hypothesis):跨代引用相對于同代引用來說僅占極少數(shù)
不應(yīng)再為了少量的跨代引用去掃描整個老年代,也不必浪費空間專門記錄每一個對象是否存在及存在哪些跨代引用塔猾,只需在新生代上建立一個全局的數(shù)據(jù)結(jié)構(gòu)記憶集(Remembered Set)篙骡,把老年代劃分成若干小塊,標(biāo)識出老年代的哪一塊內(nèi)存會存在跨代引用丈甸。
各階段GC名詞:
- 新生代收集(Minor GC/Young GC):指目標(biāo)只是新生代的垃圾收集
- 老年代收集(Major GC/Old GC):指目標(biāo)只是老年代的垃圾收集糯俗。目前只有CMS收集器會有單獨收集老年代的行為。Major GC在不同的資料上常有不同所指睦擂,需要按照上下文區(qū)分到底是指老年代收集還是整堆收集
- 整堆收集(Full GC):收集整個Java堆和方法區(qū)(元數(shù)據(jù)區(qū))的垃圾收集得湘。