摘要:執(zhí)行引擎負(fù)責(zé)解釋指令,提交給操作系統(tǒng)執(zhí)行。如圖棧幀是最先被調(diào)用的方法,先入棧,然后方法又調(diào)用了方法,棧幀處于棧頂?shù)奈恢?,棧幀處于棧底,?zhí)行完畢后,依次彈出棧幀和棧幀,線程結(jié)束,棧釋放。了解性參數(shù)永久代初始值永久代最大值新生代大小
JVM即Java Virtual Machine(Java虛擬機(jī))的縮寫(xiě),身為一名java開(kāi)發(fā)者,適當(dāng)了解JVM,拓展一下知識(shí)面并沒(méi)有壞處,本人結(jié)合最近的學(xué)習(xí)對(duì)JVM做了簡(jiǎn)單總結(jié),現(xiàn)給大家分享。
1 JVM結(jié)構(gòu) 1.1 Class Loaderclass loader顧名思義是類(lèi)加載器,我們的類(lèi)文件(.class)是保存在硬盤(pán)上的,如果想要被jvm執(zhí)行,需要有一個(gè)中間層把它加載到j(luò)vm中,這個(gè)工作就是由class loader做的,它通過(guò)IO流的形式把.class文件載入到虛擬機(jī),類(lèi)加載器分四種:
①啟動(dòng)類(lèi)加載器(Bootstrap)這部分是由c/c++編寫(xiě)的,屬于最底層的類(lèi)加載器。他會(huì)加載$JAVA_HOME/jre/lib/rt.jar中的所有類(lèi),這個(gè)jar包中有我們常用的最基本的類(lèi),比如java.lang.Object、java.lang.String等,這也就解釋了為什么我們?cè)谑褂眠@些類(lèi)時(shí)不需要導(dǎo)包的原因,啟動(dòng)類(lèi)加載器已經(jīng)事先加載到j(luò)vm中了。
②擴(kuò)展類(lèi)加載器(Extension)使用java編寫(xiě),它會(huì)加載$JAVA_HOME/jre/lib/ext/*.jar。
③應(yīng)用程序類(lèi)加載器(AppClassLoader)也叫系統(tǒng)類(lèi)加載器,使用java編寫(xiě),加載當(dāng)前應(yīng)用的$CLASSPATH中的所有類(lèi)。
④用戶自定義加載器Java.lang.ClassLoader的子類(lèi),用戶可以定制類(lèi)的加載方式。(一般用不到)
雙親委派機(jī)制和沙箱機(jī)制提到類(lèi)加載器,就不得不提這兩個(gè)機(jī)制,所謂雙親委派是指:當(dāng)應(yīng)用類(lèi)加載器接收到一個(gè)加載類(lèi)的請(qǐng)求時(shí),不會(huì)馬上進(jìn)行加載,而是委托給它的父類(lèi)加載器——擴(kuò)展類(lèi)加載器去加載,而擴(kuò)展類(lèi)加載器又委托給啟動(dòng)類(lèi)加載器,如果啟動(dòng)類(lèi)加載器在它的范圍內(nèi)沒(méi)有找到該類(lèi),則會(huì)拋一個(gè)ClassNotFoundException異常,這時(shí)它的子類(lèi)加載器才會(huì)逐級(jí)向下去嘗試加載,直到找個(gè)這個(gè)類(lèi)。那么這有什么意義呢?設(shè)想,假如你建了一個(gè)java.lang的包,又在該包下建了一個(gè)String類(lèi),如果沒(méi)有這個(gè)雙親委派機(jī)制,那么你自己寫(xiě)的String類(lèi)是不是就把jre標(biāo)準(zhǔn)的String給覆蓋了?java為了保護(hù)自身標(biāo)準(zhǔn)的類(lèi)不會(huì)被覆蓋,于是就采用了雙親委派把這些類(lèi)隔離開(kāi)來(lái),也就是所謂的“沙箱機(jī)制”。
獲取類(lèi)加載器可以通過(guò)java.lang.Class
public class JVMTest01 { public static void main(String[] args) { Object obj = new Object(); System.out.println(obj.getClass().getClassLoader()); JVMTest01 test = new JVMTest01(); System.out.println(test.getClass().getClassLoader()); System.out.println(test.getClass().getClassLoader().getParent()); System.out.println(test.getClass().getClassLoader().getParent().getParent()); } }
輸出結(jié)果:
null sun.misc.Launcher$AppClassLoader@2a139a55 sun.misc.Launcher$ExtClassLoader@7852e922 null
我們來(lái)分析一下這個(gè)結(jié)果,第二行和第三行的輸出應(yīng)該容易理解,JVMTest01是一個(gè)用戶自定義的類(lèi),是由應(yīng)用類(lèi)加載器加載的,而它的父類(lèi)加載器是擴(kuò)展類(lèi)加載器。但奇怪的是第一行和第四行的結(jié)果,為什么是null?我們知道Object類(lèi)是由啟動(dòng)類(lèi)加載器加載的,應(yīng)用類(lèi)加載器的父類(lèi)的父類(lèi)加載器也是啟動(dòng)類(lèi)加載器,那為什么獲取不到呢?因?yàn)閱?dòng)類(lèi)加載器是jvm最底層的直接跟操作系統(tǒng)打交道的接口,是由c++編寫(xiě)的,已經(jīng)很底層了,單靠java已經(jīng)獲取不到了,所以是null。
1.2 Execution Engine執(zhí)行引擎負(fù)責(zé)解釋指令,提交給操作系統(tǒng)執(zhí)行。
1.3 Native Interface本地接口的作用是融合不同的編程語(yǔ)言為 Java 所用,它的初衷是融合 C/C++程序,Java 誕生之初正是 C/C++橫行的時(shí)候,要想立足,必須有調(diào)用 C/C++程序,于是就在內(nèi)存中專(zhuān)門(mén)開(kāi)辟了一塊區(qū)域處理標(biāo)記為native的代碼,它的具體做法是 Native Method Stack中登記 native方法,在Execution Engine 執(zhí)行時(shí)加載native libraies。
目前該方法使用的越來(lái)越少了,除非是與硬件有關(guān)的應(yīng)用,比如通過(guò)Java程序驅(qū)動(dòng)打印機(jī)或者Java系統(tǒng)管理生產(chǎn)設(shè)備,在企業(yè)級(jí)應(yīng)用中已經(jīng)比較少見(jiàn)。因?yàn)楝F(xiàn)在的異構(gòu)領(lǐng)域間的通信很發(fā)達(dá),比如可以使用 Socket通信,也可以使用Web Service等等。
它的具體做法是Native Method Stack中登記native方法,在Execution Engine 執(zhí)行時(shí)加載本地方法庫(kù)。
1.5 PC寄存器每個(gè)線程都有一個(gè)程序計(jì)數(shù)器,是線程私有的,就是一個(gè)指針,指向方法區(qū)中的方法字節(jié)碼(用來(lái)存儲(chǔ)指向下一條指令的地址,也即將要執(zhí)行的指令代碼),由執(zhí)行引擎讀取下一條指令,是一個(gè)非常小的內(nèi)存空間,幾乎可以忽略不記。
1.6 Method Area靜態(tài)變量+常量+類(lèi)信息+運(yùn)行時(shí)常量池存在方法區(qū)中,該區(qū)被所有線程共享。
注:實(shí)例變量存在堆內(nèi)存中,和方法區(qū)無(wú)關(guān)1.7 Stack 1.7.1 棧是什么
棧主管Java程序運(yùn)行,是在線程創(chuàng)建時(shí)創(chuàng)建,它的生命期是跟隨線程的生命期,線程結(jié)束棧內(nèi)存也就釋放,對(duì)于棧來(lái)說(shuō)不存在垃圾回收問(wèn)題,只要線程一結(jié)束該棧就釋放,生命周期和線程一致,是線程私有的。
1.7.2 棧中存放什么棧幀中主要保存3類(lèi)數(shù)據(jù):
本地變量(Local Variables):輸入?yún)?shù)和輸出參數(shù)以及方法內(nèi)的變量。
棧操作(Operand Stack):記錄出棧、入棧的操作。
棧幀數(shù)據(jù)(Frame Data):包括類(lèi)文件、方法等等。
棧中的數(shù)據(jù)都是以棧幀(Stack Frame)的格式存在,棧幀是一個(gè)內(nèi)存區(qū)塊,是一個(gè)數(shù)據(jù)集,是一個(gè)有關(guān)方法(Method)和運(yùn)行期數(shù)據(jù)的數(shù)據(jù)集,當(dāng)一個(gè)方法A被調(diào)用時(shí)就產(chǎn)生了一個(gè)棧幀 F1,并被壓入到棧中,
A方法又調(diào)用了 B方法,于是產(chǎn)生棧幀 F2 也被壓入棧,
B方法又調(diào)用了 C方法,于是產(chǎn)生棧幀 F3 也被壓入棧,
……
執(zhí)行完畢后,先彈出F3棧幀,再?gòu)棾鯢2棧幀,再?gòu)棾鯢1棧幀……
遵循“先進(jìn)后出”/“后進(jìn)先出”原則。
如圖:
棧幀 2是最先被調(diào)用的方法,先入棧,然后方法 2 又調(diào)用了方法1,棧幀 1處于棧頂?shù)奈恢茫瑮?2 處于棧底,執(zhí)行完畢后,依次彈出棧幀 1和棧幀 2,線程結(jié)束,棧釋放。
設(shè)想:如果方法中不斷調(diào)用方法,棧幀一幀一幀的往上堆疊,終于超過(guò)了??臻g的上限,于是就報(bào)了java.lang.StackOverflowError。這就是無(wú)限遞歸調(diào)用:
public void test() { test(); }
調(diào)用這個(gè)方法就會(huì)產(chǎn)生這個(gè)結(jié)果:
圖中表示的關(guān)系是這樣的:在棧中,保存了局部變量(基本類(lèi)型+引用類(lèi)型),而引用類(lèi)型指向了堆內(nèi)存中的一塊對(duì)象實(shí)例,而這個(gè)實(shí)例是依據(jù)什么為藍(lán)圖創(chuàng)建的呢?就是存在于方法區(qū)中的類(lèi)信息,它記錄了該類(lèi)的“DNA”,基于該類(lèi)的所有實(shí)例都以此為模版進(jìn)行創(chuàng)建。
注:本地方法存在于本地方法棧中,和普通Java方法不在同一個(gè)棧2 堆體系結(jié)構(gòu)概述
一個(gè)JVM實(shí)例只存在一個(gè)堆內(nèi)存,堆內(nèi)存的大小是可以調(diào)節(jié)的,堆內(nèi)存分為三部分:
Young Generation Space 新生區(qū) Young/New
Tenure generation space 養(yǎng)老區(qū) Old/Tenure
Permanent Space 永久區(qū) Perm
注:JDK1.8開(kāi)始,永久區(qū)替換為了元空間
新生區(qū)又分為:
伊甸區(qū)(Eden Space)
幸存0區(qū)(Survivor 0 Space)
幸存1區(qū)(Survivor 1 Space)
圖例:
所有的對(duì)象都是在伊甸區(qū)被new出來(lái)的,當(dāng)伊甸園的空間用完時(shí),程序又需要?jiǎng)?chuàng)建對(duì)象,JVM的垃圾回收器將對(duì)伊甸園區(qū)進(jìn)行垃圾回收(Minor GC),將伊甸園區(qū)中的不再被其他對(duì)象所引用的對(duì)象進(jìn)行銷(xiāo)毀。然后將伊甸園中的剩余對(duì)象移動(dòng)到幸存 0區(qū)。若幸存 0區(qū)也滿了,再對(duì)該區(qū)進(jìn)行垃圾回收,然后移動(dòng)到 1 區(qū)。那如果1 區(qū)也滿了呢?再移動(dòng)到養(yǎng)老區(qū)。若養(yǎng)老區(qū)也滿了,那么這個(gè)時(shí)候?qū)a(chǎn)生MajorGC(FullGC),進(jìn)行養(yǎng)老區(qū)的內(nèi)存清理。若養(yǎng)老區(qū)執(zhí)行了Full GC之后發(fā)現(xiàn)依然無(wú)法進(jìn)行對(duì)象的保存,就會(huì)產(chǎn)生OOM異常java.lang.OutOfMemoryError。
永久存儲(chǔ)區(qū)是一個(gè)常駐內(nèi)存區(qū)域,用于存放JDK自身所攜帶的 Class,Interface 的元數(shù)據(jù),也就是說(shuō)它存儲(chǔ)的是運(yùn)行環(huán)境必須的類(lèi)信息,被裝載進(jìn)此區(qū)域的數(shù)據(jù)是不會(huì)被垃圾回收器回收掉的,關(guān)閉 JVM 才會(huì)釋放此區(qū)域所占用的內(nèi)存。
如果出現(xiàn)java.lang.OutOfMemoryError: PermGen space,說(shuō)明是Java虛擬機(jī)對(duì)永久代Perm內(nèi)存設(shè)置不夠。一般出現(xiàn)這種情況,都是程序啟動(dòng)需要加載大量的第三方j(luò)ar包。例如:在一個(gè)Tomcat下部署了太多的應(yīng)用。或者大量動(dòng)態(tài)反射生成的類(lèi)不斷被加載,最終導(dǎo)致Perm區(qū)被占滿。
注:3 堆參數(shù)調(diào)優(yōu)入門(mén)
Jdk1.6及之前:有永久代, 常量池1.6在方法區(qū)
Jdk1.7:有永久代,但已經(jīng)逐步“去永久代”,常量池1.7在堆
Jdk1.8及之后:無(wú)永久代,常量池1.8在元空間
常用參數(shù):
-Xms 設(shè)置初始分配大小,默認(rèn)為物理內(nèi)存的1/64
-Xmx 最大分配內(nèi)存,默認(rèn)為物理內(nèi)存的1/4
-XX:PrintGCDetails 輸出詳細(xì)GC日志
Demo01public static void main(String[] args) { long maxMemory = Runtime.getRuntime().maxMemory(); //返回 Java 虛擬機(jī)試圖使用的最大內(nèi)存量 long totalMemory = Runtime.getRuntime().totalMemory(); //返回 Java 虛擬機(jī)中的內(nèi)存總量 System.out.println("MAX_MEMORY = " + maxMemory + "Byte " + (maxMemory / (double)1024 / 1024) + "MB"); System.out.println("TOTAL_MEMORY = " + totalMemory + "Byte " + (totalMemory / (double)1024 / 1024) + "MB"); }
在eclipse中配置jvm參數(shù):
輸出結(jié)果:
由圖,我們利用-Xms和-Xmx參數(shù)將初始內(nèi)存和最大內(nèi)存都設(shè)置為1024MB(實(shí)際結(jié)果981.5MB屬于誤差)
注:永久代/元空間 只是JVM邏輯上有這么一塊區(qū)域,但實(shí)際物理內(nèi)存中并不存在,如何證明呢?如圖:新生代+養(yǎng)老代 的內(nèi)存總和已經(jīng)等于TOTAL_MEMORY,說(shuō)明實(shí)際內(nèi)存中只有新生區(qū)和養(yǎng)老區(qū),永久代/元空間只是邏輯上存在。Demo02
public static void main(String[] args) { String str = "hello world!"; while (true) { str += str + new Random().nextInt(88888888) + new Random().nextInt(999999999); } }
參數(shù)配置:
-Xms8m -Xmx8m -XX:+PrintGCDetails
運(yùn)行結(jié)果:
分析:我們故意把堆內(nèi)存調(diào)小至8M,然后再不斷地在堆中生成String對(duì)象,直到產(chǎn)生OOM異常,從輸出日志中可以看到,在拋出異常前JVM不斷進(jìn)行GC,直到最后一次Full GC之后,堆內(nèi)存依舊沒(méi)有足夠的空間new出新的對(duì)象,于是就拋出了OOM異常。一般OOM異常都是在Full GC之后產(chǎn)生的。
-XX:+HeapDumpOnOutOfMemoryError這個(gè)長(zhǎng)參數(shù)是比較特別的,所以這里多帶帶提一下,它的作用是當(dāng)JVM產(chǎn)生OOM異常時(shí),生成一個(gè)dump文件到你的工程目錄下,可以配合eclipse的MAT(Eclipse Memory Analyzer)插件分析內(nèi)存泄漏。
了解性參數(shù)-XX:PermSize 永久代初始值
-XX:MaxPermSize 永久代最大值
-Xmn 新生代大小
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/68365.html
摘要:直接對(duì)棧的操作只有兩個(gè),就是對(duì)棧幀的壓棧和出棧。中將永久代移除,同時(shí)增加元數(shù)據(jù)區(qū)。在中,本地方法棧和虛擬機(jī)棧是在同一塊兒區(qū)域,這完全取決于技術(shù)實(shí)現(xiàn)的決定,并未在規(guī)范中強(qiáng)制。 原文:https://github.com/linsheng97... 描述一下 JVM 的內(nèi)存區(qū)域 程序計(jì)數(shù)?(PC,Program Counter Register)。在 JVM 規(guī)范中,每個(gè)線程都有它自己的...
摘要:內(nèi)存分配解析四方法執(zhí)行完畢,立即釋放局部變量所占用的??臻g。內(nèi)存分配解析五調(diào)用對(duì)象的方法,以實(shí)例為參數(shù)。堆和棧的小結(jié)以上就是程序運(yùn)行時(shí)內(nèi)存分配的大致情況。 前言 java中有很多類(lèi)型的變量、靜態(tài)變量、全局變量及對(duì)象等,這些變量在java運(yùn)行的時(shí)候到底是如何分配內(nèi)存的呢?接下來(lái)有必要對(duì)此進(jìn)行一些探究。 基本知識(shí)概念: (1)寄存器:最快的存儲(chǔ)區(qū), 由編譯器根據(jù)需求進(jìn)行分配,我們?cè)诔绦?..
摘要:復(fù)制這一工作所花費(fèi)的時(shí)間,在對(duì)象存活率達(dá)到一定程度時(shí),將會(huì)變的不可忽視。針對(duì)老年代老年代的特點(diǎn)是區(qū)域較大,對(duì)像存活率高。這種情況,存在大量存活率高的對(duì)像,復(fù)制算法明顯變得不合適。 GC(Garbage Collection)即Java垃圾回收機(jī)制,是Java與C++的主要區(qū)別之一,作為Java開(kāi)發(fā)者,一般不需要專(zhuān)門(mén)編寫(xiě)內(nèi)存回收和垃圾清理代碼,對(duì)內(nèi)存泄露和溢出的問(wèn)題,也不需要像C++程序...
摘要:堆區(qū)堆是虛擬機(jī)所管理的內(nèi)存中最大的一塊,它是被所有線程共享的一塊內(nèi)存區(qū)域,該區(qū)域在虛擬機(jī)啟動(dòng)的時(shí)候創(chuàng)建。 運(yùn)行時(shí)數(shù)據(jù)區(qū)域 ? ?想要了解jvm,那對(duì)其內(nèi)存分配管理的學(xué)習(xí)是必不可少的;java虛擬機(jī)在執(zhí)行java程序的時(shí)候會(huì)把它所管理的內(nèi)存劃分成若干數(shù)據(jù)區(qū)域。這些區(qū)域有著不同的功能、用途、創(chuàng)建/銷(xiāo)毀時(shí)間。java虛擬機(jī)所分配管理的內(nèi)存區(qū)域如圖1所示 程序計(jì)數(shù)器 ? ?程序計(jì)數(shù)器是一塊比較...
閱讀 2748·2023-04-26 02:44
閱讀 10025·2021-11-22 14:44
閱讀 2191·2021-09-27 13:36
閱讀 2773·2021-09-08 10:43
閱讀 762·2019-08-30 15:56
閱讀 1454·2019-08-30 15:55
閱讀 2941·2019-08-28 18:12
閱讀 2902·2019-08-26 13:50