JVM體系結構概覽
暗色區域表示線程獨佔的,亮色區域表示線程共享,存在垃圾回收。
類裝載器ClassLoader
1.作用: 負責加載class文件,class文件開頭有特定的文件標示,將class文件字節碼內容加載到內存中,並將這些內容轉換成方法區中的運行時的數據結構並且ClassLoader只負責class文件的加載,至於它是否可以運行,由Execution Engine決定。
注意:方法區裏放的不是方法,而是類的模板(類的描述信息)。
Car的多個實例屬性方法等一樣,是因爲它們都是由同一個模板(Class)生成的。
2.類加載器的種類
以下是類加載器的繼承關係圖:
- 虛擬機自帶的加載器
- 啓動類加載器(Bootstrap)C++ 加載Java中的原始對象(Object,String,List等)
- 擴展類記載器(Extension)Java 加載Java中的擴展類。
- 應用程序類加載器(AppClassLoader)
- 用戶自定義加載器 用戶定製類的加載方式
查看加載器:對象.getClass().getClassLoader()
public class Test01 {
public static void main(String[] args) {
Object object = new Object();
// 報錯
System.out.println(object.getClass().getClassLoader().getParent().getParent());
// 報錯
System.out.println(object.getClass().getClassLoader().getParent());
// Bootstrap Loader:輸出null
System.out.println(object.getClass().getClassLoader());
Test01 test01 = new Test01();
// Bootstrap Loader: 輸出null
System.out.println(test01.getClass().getClassLoader().getParent().getParent());
// Extension Loader: 輸出 sun.misc.Launcher$ExtClassLoader@1b6d3586
System.out.println(test01.getClass().getClassLoader().getParent());
// AppClassLoader: 輸出 sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(test01.getClass().getClassLoader());
}
}
3.雙親委派機制
4.沙箱安全機制
雙親委派機制防止惡意代碼污染java源代碼(雙親委派機制保證了同包名的類總是先找到系統自帶的)。以下代碼編譯通過,但運行不了。
package java.lang
public Class String {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
本地方法接口
1.native: 表示java調用的是與java無關的底層的第三方的操作系統庫或C。
Thread類的start方法如下,從代碼中可以看出start()方法底層調用的是C/C++。
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
本地接口的作用是融合不同的編程語言爲 Java 所用,它的初衷是融合 C/C++程序,Java 誕生的時候是 C/C++橫行的時候,要想立足,必須有調用 C/C++程序,於是就在內存中專門開闢了一塊區域處理標記爲native的代碼,它的具體做法是 Native Method Stack中登記 native方法,在Execution Engine 執行時加載native libraies。
程序計數器(PC寄存器)
作用: 一個指針,指向下一條將要執行的指令。
方法區
作用: 存儲每一個類的結構信息,如 常量池、字段和方法數據、構造函數和普通方法的字節碼內容。
在不同虛擬機實現不一樣,如jdk7的永久代(PermGen space)和 jdk8的元空間(Metaspace)。
stack | heap
棧管運行,堆管存儲。
棧也叫棧內存,主管Java程序的運行,是在線程創建時創建,它的生命期是跟隨線程的生命期,線程結束棧內存也就釋放,對於棧來說不存在垃圾回收問題,只要線程一結束該棧就Over,生命週期和線程一致,是線程私有的。8種基本類型的變量+對象的引用變量+實例方法都是在函數的棧內存中分配。
每個方法執行的同時都會創建一個棧幀,用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息,每一個方法從調用直至執行完畢的過程,就對應着一個棧幀在虛擬機中入棧到出棧的過程。棧的大小和具體JVM的實現有關,通常在256K~756K之間,與等於1Mb左右。
棧幀中主要保存3 類數據:
- 本地變量(Local Variables):輸入參數和輸出參數以及方法內的變量;
- 棧操作(Operand Stack):記錄出棧、入棧的操作;
- 棧幀數據(Frame Data):包括類文件、方法等等。
堆體系結構概述
Heap堆(Java7之前)
一個JVM實例只存在一個堆內存,堆內存的大小是可以調節的。類加載器讀取了類文件後,需要把類、方法、常變量放到堆內存中,保存所有引用類型的真實信息,以方便執行器執行。
堆內存邏輯上分爲三部分:新生+養老+永久。但在物理上只有新生+養老。
其中永久存儲區在jdk7及以前叫永久代,jdk8開始叫元空間。永久代和元空間是不用版本jdk對方法區的實現!
GC的大致流程
新生區是類的誕生、成長、消亡的區域,一個類在這裏產生,應用,最後被垃圾回收器收集,結束生命。新生區又分爲兩部分: 伊甸區(Eden space)和倖存者區(Survivor pace) ,所有的類都是在伊甸區被new出來的。倖存區有兩個: 0區(Survivor 0 space)和1區(Survivor 1 space)。當伊甸園的空間用完時,程序又需要創建對象,JVM的垃圾回收器將對伊甸園區進行垃圾回收(Minor GC),將伊甸園區中的不再被其他對象所引用的對象進行銷燬。然後將伊甸園中的剩餘對象移動到倖存 0區。若倖存 0區也滿了,再對該區進行垃圾回收,然後移動到 1 區。那如果1 區也滿了呢?再移動到養老區。若養老區也滿了,那麼這個時候將產生MajorGC(FullGC),進行養老區的內存清理。若養老區執行了Full GC之後發現依然無法進行對象的保存,就會產生OOM異常“OutOfMemoryError”。
如果出現java.lang.OutOfMemoryError: Java heap space異常,說明Java虛擬機的堆內存不夠。原因有二:
(1)Java虛擬機的堆內存設置不夠,可以通過參數-Xms、-Xmx來調整。
(2)代碼中創建了大量大對象,並且長時間不能被垃圾收集器收集(存在被引用)。
Minor GC
1:eden、SurvivorFrom 複製到 SurvivorTo,年齡+1
首先,當Eden區滿的時候會觸發第一次GC,把還活着的對象拷貝到SurvivorFrom區,當Eden區再次觸發GC的時候會掃描Eden區和From區域,對這兩個區域進行垃圾回收,經過這次回收後還存活的對象,則直接複製到To區域(如果有對象的年齡已經達到了老年的標準,則賦值到老年代區),同時把這些對象的年齡+1
2:清空 eden、SurvivorFrom
然後,清空Eden和SurvivorFrom中的對象,也即複製之後有交換,誰空誰是to
3:SurvivorTo和 SurvivorFrom 互換
最後,SurvivorTo和SurvivorFrom互換,原SurvivorTo成爲下一次GC時的SurvivorFrom區。部分對象會在From和To區域中複製來複制去,如此交換15次(由JVM參數MaxTenuringThreshold決定,這個參數默認是15),最終如果還是存活,就存入到老年代
生產環境:Xmx 和 Xms 設置成一樣大。避免GC和應用程序爭搶內存,理論值的峯值和峯谷忽高忽低。
堆參數調優入門
Java7 與 Java8 的區別
jdk7:
jdk8:
-
java8中永久代移除,由元空間替代
-
永久代使用的JVM的堆內存,但是java8以後的元空間並不在虛擬機中而是使用本機物理內存。
因此,默認情況下,元空間的大小僅受本地內存限制。 類的元數據放入 native memory, 字符串池和類的靜態變量放入 java 堆中,這樣可以加載多少類的元數據就不再由MaxPermSize 控制, 而由系統的實際可用空間來控制。
堆內存調優
可以採用以下程序驗證:其中的Runtime是對jvm數據區的抽象,可以查看相關參數。
public static void main(String[] args){
long maxMemory = Runtime.getRuntime().maxMemory() ; // 返回 Java 虛擬機試圖使用的最大內存量。
long totalMemory = Runtime.getRuntime().totalMemory() ; // 返回 Java 虛擬機中的內存總量。
System.out.println("MAX_MEMORY = " + maxMemory + "(字節)、" + (maxMemory / (double)1024 / 1024) + "MB");
System.out.println("TOTAL_MEMORY = " + totalMemory + "(字節)、" + (totalMemory / (double)1024 / 1024) + "MB");
}
指定參數(idea):
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
算法
判斷對象是否已經死亡的算法:引用計數算法,可達性分析算法;
四個垃圾收集算法:標記清除算法,複製算法,標記整理算法,分代收集算法;
七個垃圾收集器:Serial,SerialOld,ParNew,Parallel Scavenge,Parallel。Old,CMS,G1.
以上內容整理自:https://www.bilibili.com/video/BV1vE411D7KE?from=search&seid=14558761714549494791