JVM 內(nèi)存模型
運(yùn)行時(shí)數(shù)據(jù)區(qū)域
程序計(jì)數(shù)器(Program Conunter Regisiter)
程序計(jì)數(shù)器是一個(gè)比較小的內(nèi)存空間球碉,可以看作是當(dāng)前線程執(zhí)行的字節(jié)碼行號(hào)指示器霎箍。本質(zhì)就是記錄字節(jié)碼執(zhí)行順序寡具。 在《Java 虛擬機(jī)規(guī)范》中沒有任何 OutOfMemoryError 情況的區(qū)域唇聘。
虛擬機(jī)棧(JVM Stack)
虛擬機(jī)棧介却,存放的是線程運(yùn)行時(shí)內(nèi)部的局部變量,也可以理解為線程棧能扒。
每個(gè)方法被執(zhí)行的時(shí)候佣渴, 虛擬機(jī)會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存放局部變量表(local variable),操作數(shù)棧(operand stack)初斑,動(dòng)態(tài)連接辛润,方法出口等信息。
棧幀(Stack Frame)隨著方法的調(diào)用而創(chuàng)建见秤,隨著方法的結(jié)束而銷毀(不論是正常結(jié)束還拋出異常)频蛔。
字節(jié)碼指令分析(描述 JVM Stack 操作過程)
publicintadd(){inta =1;intb =2;intc = b - a;returnc;}0iconst_1//將 a 壓入局部變量表?xiàng)m?istore_1//對(duì) a 進(jìn)行賦值 1? 2iconst_2//將 b 壓入局部變量表?xiàng)m?istore_2//對(duì) b 進(jìn)行賦值 24iload_2//讀取 b 到操作數(shù)棧5iload_1//讀取 a 到操作數(shù)棧6isub//執(zhí)行 b - a7istore_3//將 int 類型的值存入局部變量表 3 8iload_3//讀取 c 到操作數(shù)棧9ireturn//返回復(fù)制代碼
局部變量表(local variable)
局部變量表存放了各種編譯期 Java 虛擬機(jī)基本數(shù)據(jù)庫類型(boolean灵迫、byte、char晦溪、short、int挣跋、float三圆、long、dubble)和對(duì)象引用(reference 類型避咆,即對(duì)象的起始位置指針或者對(duì)象句柄), 對(duì)象的真實(shí)數(shù)據(jù)通常存放在堆空間舟肉。
局部變量表中的存儲(chǔ)空間通過變量槽(slot) 來表示,其中 64 位長度的 long? 和 double 占 2 個(gè)變量槽查库。
操作數(shù)棧(operand stack)
每個(gè)棧幀都包含一個(gè)操作數(shù)棧的先進(jìn)先出(FIFO)棧路媚,棧幀中操作數(shù)棧的深度由編譯期決定,并且通過方法的 code 屬性保存以及提供給棧幀使用樊销。
動(dòng)態(tài)鏈接
每個(gè)棧幀都包含一個(gè)指向當(dāng)前方法所在類型的運(yùn)行時(shí)常量池的引用整慎。以便對(duì)當(dāng)前方法的代碼實(shí)現(xiàn)動(dòng)態(tài)鏈接。
在 class 文件中围苫,一個(gè)方法如果要調(diào)用其它方法裤园, 或者訪問局部成員變量,則需要將符號(hào)引用(synbolic reference)來表示剂府,動(dòng)態(tài)鏈接的作用就是將這些符號(hào)引用轉(zhuǎn)換為對(duì)實(shí)際方法的直接引用拧揽。
方法出口
方法正常完成,當(dāng)前棧幀恢復(fù)調(diào)用者的責(zé)任腺占,包括恢復(fù)調(diào)用者的局部變量表淤袜,操作數(shù)棧,以及正確的程序計(jì)數(shù)器遞增衰伯。跳過剛才執(zhí)行的方法調(diào)用指令等铡羡,低哦啊用著的代碼被調(diào)用的方法正返回值壓入調(diào)用者操作數(shù)棧后,會(huì)繼續(xù)正常執(zhí)行嚎研。
方法一場完成蓖墅,某些指令導(dǎo)致了 JVM 虛擬機(jī)拋出異常,或者用戶顯示的通過thorw關(guān)鍵字跑出一場临扮,同時(shí)在改該方法中沒有捕獲異常论矾。如果方法異常調(diào)用完成,那不一定有方法返回值返回給調(diào)用者杆勇。
本地方法棧(Native Method Stack)
為本地方法所分配的內(nèi)存空間贪壳,就是為native關(guān)鍵字修飾的方法提供服務(wù)的。
本地方法主要是 Java 來調(diào)用 C/C++ 函數(shù)庫的調(diào)用方法蚜退。
方法區(qū)(Method Area)
主要存放數(shù)據(jù)有:常量闰靴,靜態(tài)變量彪笼,類信息。
方法區(qū)存放的是靜態(tài)變量的內(nèi)存地址蚂且, 方法區(qū)里面有一個(gè)元空間配猫, 在JDK1.8 之前叫永久代。
堆(Heap)
JVM 管理的最大的一塊內(nèi)存空間杏死。與堆相關(guān)的一個(gè)重要概念是垃圾收集器泵肄。幾乎所有的垃圾收集器都是采用分代收集算法,所以對(duì)內(nèi)存空間也是基于這一點(diǎn)進(jìn)行相應(yīng)的劃分:新生代和老年代淑翼,新生代分為Eden 空間腐巢、From Survivor 空間、To Survivor 空間玄括。
對(duì)象創(chuàng)建的過程中首先會(huì)存在 Eden 區(qū)冯丙,然后經(jīng)過 minor gc 過后進(jìn)入 survivor ,進(jìn)過 15 次 survivor 轉(zhuǎn)移過后遭京,進(jìn)入老年代胃惜。
如果內(nèi)存都不夠用了就觸發(fā) full gc, 再次觸發(fā) GC 過后無法分配申請(qǐng)內(nèi)存洁墙,JVM 就會(huì)拋出 OOM蛹疯。
分析對(duì)象是否存在引用,是否被回收采用的是 GC ROOT 可達(dá)性分析热监。
直接內(nèi)存(Direact Memory)
直接內(nèi)存捺弦,不是 JVM 來管理,是通過操作系統(tǒng)來管理的孝扛, 與 Java NIO 密切相關(guān)列吼。 Java 通過DirectByteBuffer來操作直接內(nèi)存。
JVM 內(nèi)存參數(shù)設(shè)置
內(nèi)存參數(shù)配置
Spring-Boot 程序的 JVM 內(nèi)存參數(shù)設(shè)置格式(Tomcat 啟動(dòng)直接在 bin 目錄下的 Catalina.sh 文件設(shè)置)
java -Xms2048m -Xmx2048m? -Xmn1024 -Xss512k -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -jar? xxx-xxx.jar復(fù)制代碼
關(guān)于元空間JVM 有兩個(gè):-XX:MetaspaceSize=N 和 -XX:MaxMetaspaceSize=N苦始,對(duì)于 64 位 JVM 來說寞钥, 元空間默認(rèn)是 21MB,默認(rèn)的元空間的最大值是無限陌选。
-XX:MaxMetaspaceSize: 設(shè)置元空間最大值理郑,默認(rèn)是 -1, 即不限制咨油,或者說是受限制于本地內(nèi)存大小您炉。
-XX:MetaspaceSize:指定元空間的初始大小,以字節(jié)為單位役电,默認(rèn)是 21M赚爵,達(dá)到該值過后就會(huì)觸發(fā) full gc 進(jìn)行類型卸載,同時(shí)收集器會(huì)對(duì)該值進(jìn)行調(diào)整;如果釋放了大量的空間就適當(dāng)降低該值冀膝;如果釋放了很少的空間唁奢,那么就在不超過 -XX:MaxMetaspaceSize (如果設(shè)置)的情況下,適當(dāng)提高該值窝剖。
由于調(diào)整元空間大小需要 full gc , 這是一個(gè)非常昂貴的操作麻掸,如果在啟動(dòng)過程中發(fā)生大量 full gc, 通常都是由于永久代或者元空間發(fā)生了大小調(diào)整,基于這種情況枯芬,一般建議在 JVM 參數(shù)將 MaxMetaspaceSize 和 MetaspaceSize 設(shè)置成一樣的值论笔,并設(shè)置得比初始值要大,對(duì)于 8G 的物理內(nèi)存來說我們通常都會(huì)將這兩個(gè)值設(shè)置為 256M千所。
堆空間內(nèi)存溢出
importjava.util.ArrayList;importjava.util.List;publicclassHeapOverFlowTest{byte[] a =newbyte[1024*1024*2];// 2mbpublicstaticvoidmain(String[] args){? ? ? ? List list =newArrayList<>();while(true) {? ? ? ? ? ? list.add(newHeapOverFlowTest());? ? ? ? }? ? }}// 輸出結(jié)果Exception in thread"main"java.lang.OutOfMemoryError: Java heap spaceat cn.edu.cqvie.jvm.HeapOverFlowTest.(HeapOverFlowTest.java:8)at cn.edu.cqvie.jvm.HeapOverFlowTest.main(HeapOverFlowTest.java:13)復(fù)制代碼
虛擬機(jī)棧內(nèi)存溢出
publicclassStackOverFlowTest{// JVM 設(shè)置// -Xss128k, -Xss默認(rèn)1Mstaticintcount =0;staticvoidredo(){? ? ? ? count++;? ? ? ? redo();? ? }publicstaticvoidmain(String[] args){try{? ? ? ? ? ? redo();? ? ? ? ? ? System.out.println(count);? ? ? ? }catch(Throwable t) {? ? ? ? ? ? t.printStackTrace();? ? ? ? }? ? }}// 輸出結(jié)果: 棧溢出java.lang.StackOverflowErrorat cn.edu.cqvie.jvm.StackOverFlowTest.redo(StackOverFlowTest.java:11)at cn.edu.cqvie.jvm.StackOverFlowTest.redo(StackOverFlowTest.java:11)at cn.edu.cqvie.jvm.StackOverFlowTest.redo(StackOverFlowTest.java:11)....復(fù)制代碼
總結(jié):
-Xss 設(shè)置越小 count 值越小,說明一個(gè)線程棧里能夠分配的棧幀就越小蒜埋,但是對(duì)于 JVM 整體來說能夠開啟的線程數(shù)就會(huì)更多淫痰。
方法區(qū)內(nèi)存溢出
需要注意的是 1.8 內(nèi)模型中,將運(yùn)行時(shí)常量池?cái)?shù)據(jù)放入堆中整份,所以我們限制方法區(qū)的大小對(duì)運(yùn)行時(shí)常量池的限制毫無意義待错。最終也只會(huì)拋出java.lang.OutOfMemoryError: Java heap space異常。
下面通過GCLib 模擬方法區(qū)溢出模擬的一個(gè)例子烈评。
/**
* -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
*/publicclassMyTest4{publicstaticvoidmain(String[] args){for(; ; ) {? ? ? ? ? ? Enhancer enhancer =newEnhancer();? ? ? ? ? ? enhancer.setSuperclass(MyTest4.class);? ? ? ? ? ? enhancer.setUseCache(false);? ? ? ? ? ? enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) ->? ? ? ? ? ? ? ? ? ? proxy.invoke(obj, args1));? ? ? ? ? ? System.out.println("hello world");? ? ? ? ? ? enhancer.create();? ? ? ? }? ? }}//輸出結(jié)果Caused by: java.lang.OutOfMemoryError: Metaspaceat java.lang.ClassLoader.defineClass1(Native Method)at java.lang.ClassLoader.defineClass(ClassLoader.java:756)? ......復(fù)制代碼
JVM 監(jiān)控工具
VisualVM
VisualVM 提供在 Java 虛擬機(jī) (Java Virutal Machine, JVM) 上運(yùn)行的 Java 應(yīng)用程序的詳細(xì)信息火俄。在 VisualVM 的圖形用戶界面中,可以方便讲冠、快捷地查看多個(gè) Java 應(yīng)用程序的相關(guān)信息瓜客。
作者:大漠北
鏈接:https://juejin.cn/post/6925664904833662983
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)竿开,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處谱仪。