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