柚子快報激活碼778899分享:JVM基礎知識整理
柚子快報激活碼778899分享:JVM基礎知識整理
JVM相關基礎知識
一、JVM基礎1、java從編譯到執(zhí)行
二、類加載器1、類的加載1.1 類加載過程1.2 什么情況下會對類進行初始化?1.3 new 一個對象的過程是怎樣的?
2、雙親委派模型2.1 模型組成2.2 工作原理2.3 優(yōu)點2.4.類加載器的加載過程源碼2.5 自定義類加載器的實現(xiàn)2.6 懶加載
三、JVM運行數(shù)據(jù)區(qū)1、JVM運行數(shù)據(jù)區(qū)可分為:方法區(qū)、堆、本地方法棧、虛擬機棧和程序計數(shù)器。2、一些補充2.1 介紹一下方法區(qū)2.2 對象一定分配在堆中嗎?
四、垃圾回收機制1、什么是新生代、老年代和永久代1.1 新生代1.2 老年代1.3 永久代
2、如何對象是否存活3、有哪些GC4、垃圾回收算法4.1 標記清除算法4.2 復制算法:4.3 標記整理算法4.4 分代算法
五、垃圾回收器1、Serial + Serial Old2、Parallel Scavenge + Parallel Old3、ParNew + CMS4、G1垃圾收集器
一、JVM基礎
1、java從編譯到執(zhí)行
一個x.java文件通過執(zhí)行javac變?yōu)閤.class文件,然后被ClassLoader加載到內存中,通過java類庫中的一部分類(如:Object,String等)也會被加載到內存中。Java即是編譯型的,也是解釋型語言,即混合型語言。常用代碼會被JIT即時編譯,提高效率??偟膩碚fJava更接近解釋型語言。Java是跨平臺語言,JVM是跨語言平臺(只要是.class文件就能執(zhí)行)。JDK是Java開發(fā)工具包;JRE是Java運行環(huán)境;JVM是Java虛擬機。JDK包含JRE,JRE包含JVM。
二、類加載器
1、類的加載
1.1 類加載過程
loading (加載) —> linking(驗證 —> 準備 —> 解析) —> initializing(初始化)
加載:將java文件編譯成class文件。驗證:校驗class文件是否符合class文件的規(guī)范。準備:為靜態(tài)變量分配空間,則在此階段分配默認值。解析:將常量池中的符號引用替換為直接引用的過程。(有一部分符號引用會在使用時轉換為直接引用)初始化:執(zhí)行初始化方法的過程。加載靜態(tài)代碼塊,為靜態(tài)成員變量分配初始值等。
1.2 什么情況下會對類進行初始化?
jvm執(zhí)行new指令時。初始化一個類,但其父類未被初始化時,會優(yōu)先初始化父類。虛擬機啟動時,用戶需要定義一個執(zhí)行的主類即main()方法,會優(yōu)先初始化此類。首次訪問此類的靜態(tài)變量或者靜態(tài)方法時使用java.lang.reflect包下的方法進行反射調用如Class.forName()方法等
1.3 new 一個對象的過程是怎樣的?
如:T t = new T();
類的加載申請內存(賦默認值)調用構造方法(賦初始值)指針指向內存(賦值給t)
這里需要注意,在沒有volatile的情況下,3和4可能會發(fā)生指令重排序,即t先指向內存,后賦初始值。 可能會導致另一個線程拿到的值為默認值而非正確的初始化的值。(單例模式為什么要用volatile修飾)
2、雙親委派模型
2.1 模型組成
啟動類加載器:c++實現(xiàn)擴展類加載器應用程序類加載器自定義加載器
2.2 工作原理
當一個類需要被加載的時候,它不會直接執(zhí)行類加載,而是先把這個請求委托給父類加載器,直到把這個請求傳遞到啟動類加載器。如果父類加載器無法完成此次類加載,則會讓其子類嘗試執(zhí)行類加載。
2.3 優(yōu)點
避免類的重復加載。避免Java的核心API被篡改。
2.4.類加載器的加載過程源碼
在loadClass中,會通過類似遞歸的方式自底向上地檢查父加載器是否已經加載此類,如果全部沒有加載,則開始自頂向下地嘗試加載。如果最終仍然加載失敗,則拋出ClassNotFoundException。
// java.lang.ClassLoader
// 類加載過程源碼
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 校驗類是否已經被加載
Class> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 雙親委派,找父加載器去檢查加載。即自底向上地檢查。
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
// 上面沒有從緩存中找到加載信息,只能自己加載。即自頂向下地嘗試加載。
// 如果未被重寫,該方法直接拋出ClassNotFoundException。
// 所以實現(xiàn)自定義類加載器的核心是重寫此方法。
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
}
return c;
}
2.5 自定義類加載器的實現(xiàn)
繼承ClassLoader,重寫其findClass方法。通過name構造文件絕對路徑讀取文件。創(chuàng)建文件字節(jié)輸入流。創(chuàng)建字節(jié)數(shù)組輸出流。讀取輸入流,將數(shù)據(jù)寫入到輸出流。將字節(jié)數(shù)組輸出流裝換成二進制字節(jié)數(shù)組。調用ClassLoader的defineClass()方法用字節(jié)數(shù)組構造clazz對象。
public class MyClassLoader extends ClassLoader {
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
// 這里通過入?yún)?name 構造出文件的絕對路徑來獲取file。
File file = new File("");
try {
// 創(chuàng)建文件字節(jié)輸入流
FileInputStream fileInputStream = new FileInputStream(file);
// 創(chuàng)建字節(jié)數(shù)組輸出流
ByteArrayOutputStream byteArrayInputStream = new ByteArrayOutputStream();
int b;
while ((b = fileInputStream.read()) != 0) {
// 讀取輸入流,將數(shù)據(jù)寫入到輸出流
byteArrayInputStream.write(b);
}
// 將字節(jié)數(shù)組輸出流裝換成二進制字節(jié)數(shù)組
byte[] bytes = byteArrayInputStream.toByteArray();
byteArrayInputStream.close();
fileInputStream.close();
// 調用ClassLoader的defineClass()方法用字節(jié)數(shù)組構造clazz對象
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
自定義類加載器的使用也非常簡單,即反射。
public static void main(String[] args) throws Exception {
ClassLoader classLoader = new MyClassLoader();
Class clazz = classLoader.loadClass("");
Object o = clazz.newInstance();
}
2.6 懶加載
懶加載其實就是按需加載,即當對象需要用到的時候再去加載相關的對象。
三、JVM運行數(shù)據(jù)區(qū)
1、JVM運行數(shù)據(jù)區(qū)可分為:方法區(qū)、堆、本地方法棧、虛擬機棧和程序計數(shù)器。
方法區(qū)(Method Area):線程共有,主要用于存放類文件信息,靜態(tài)變量,常量以及即時編譯(JIT)后的代碼等數(shù)據(jù)。堆(Heap):線程共有,JVM中內存最大的一塊, 主要用于存放對象實例,可分為新生代和老年代,是GC的主要區(qū)域。虛擬機棧(JVM Stacks):線程私有,主要存放棧幀。每個方法執(zhí)行時都會在棧中創(chuàng)建一個棧幀,棧幀是由局部變量表、操作數(shù)棧、動態(tài)鏈接和返回地址組成。動態(tài)鏈接指動態(tài)將常量池中符號引用轉化為直接引用。本地方法棧(Native Method Stacks):線程私有,專門為JVM調用C和C++編寫的native方法服務。程序計數(shù)器(Program Counter Register):線程私有,用于保存當前正在執(zhí)行的線程的字節(jié)碼地址。因為線程不具備記憶功能,所以需要程序計數(shù)器告訴線程該執(zhí)行哪一條字節(jié)碼。
2、一些補充
2.1 介紹一下方法區(qū)
方法區(qū)并不真實存在,屬于 Java 虛擬機規(guī)范中的一個邏輯概念,用于存儲已被 JVM 加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼緩存等。
在 HotSpot 虛擬機中,方法區(qū)的實現(xiàn)稱為永久代(PermGen),但在 Java 8 及之后的版本中,已經被元空間(Metaspace)所替代,方法區(qū)的內存大小取決于系統(tǒng)內存的大小。
2.2 對象一定分配在堆中嗎?
隨著 JIT 編譯期的發(fā)展與逃逸分析技術逐漸成熟,所有的對象都分配到堆上也漸漸變得不那么“絕對”了。其實,在編譯期間,JIT 會對代碼做很多優(yōu)化。其中有一部分優(yōu)化的目的就是減少內存堆分配壓力,其中一種重要的技術叫做逃逸分析。
逃逸分析(Escape Analysis)技術
編譯器可能會將一些原本應該在堆上分配的對象優(yōu)化為在棧上分配。這通常發(fā)生在對象的作用域明確并且不會逃逸出它的創(chuàng)建方法時。
逃逸分析的好處
棧上分配:如果確定一個對象不會逃逸到線程之外,那么久可以考慮將這個對象在棧上分配,對象占用的內存隨著棧幀出棧而銷毀,這樣一來,垃圾收集的壓力就降低很多。 同步消除:線程同步本身是一個相對耗時的過程,如果逃逸分析能夠確定一個變量不會逃逸出線程,無法被其他線程訪問,那么這個變量的讀寫肯定就不會有競爭, 對這個變量實施的同步措施也就可以安全地消除掉
四、垃圾回收機制
1、什么是新生代、老年代和永久代
1.1 新生代
主要用于存放新生的對象,占據(jù)堆內存的1/3。由于頻繁創(chuàng)建對象,新生代會進行頻繁的Minor GC。新生代分為三塊區(qū)域:Eden、ServiceFrom和ServiceTo。
Eden區(qū):新創(chuàng)建的對象會被放入Eden區(qū),如果對象過大則直接分配老年代。當Eden區(qū)內存不夠時會觸發(fā)Minor GC,而大多數(shù)對象都是朝生夕死,對于存活對象則是進入ServiceTo區(qū)或ServiceFrom區(qū)。ServiceTo:一次Minor GC后Eden區(qū)或ServiceFrom存活的對象會被復制到此區(qū)域。ServiceFrom:一次Minor GC后Eden區(qū)或ServiceTo存活的對象會被復制到此區(qū)域。
ServiceTo和ServerFrom區(qū)本質上沒什么區(qū)別,循環(huán)使用于存放GC后存活的對象。每次存活后對象的年齡加1,年齡到達15后依然存活的對象會被分配到老年代。 Minor GC一般采用復制算法。
1.2 老年代
用于存放大對象或者存活時間很長的對象,占據(jù)堆內存的2/3。老年代的對象比較穩(wěn)定,所以Major GC不會頻繁執(zhí)行。 進入老年代的方式有三種:
對象存活時間長,默認年齡為15。對象很大。動態(tài)年齡判斷:按年齡從小到大將對象累加,如果加入某個年齡的對象后內存超過一半,則從該年齡開始,往上的年齡都進入老年代。
在對象進入老年代后,如果老年代空間不足,則會觸發(fā)Major GC。如果老年滿了裝不下了,則會OOM。 Major GC一般采用標記清除或標記整理算法。
1.3 永久代
Java8后永久代被移除,被元空間(Meta Space)取代。
元空間不是方法區(qū),而是方法區(qū)的一種實現(xiàn)。元空間并不使用JVM的內存,而是使用操作系統(tǒng)的本地內存。元空間主要用于存放Class信息以及元數(shù)據(jù)信息。元空間不會觸發(fā)GC。
2、如何對象是否存活
對象不存活就是垃圾。
引用計數(shù)法 一個對象,如果被引用則計數(shù)器加1,引用失效則計數(shù)器減1。 如果計數(shù)器值為0,則表示該對象不在被引用,可以進行回收。 但有一個致命缺點:無法處理循環(huán)依賴。導致沒有任何地方使用此方式。 可達性分析法 以根對象(GC root)為起點,搜索被根對象集合所連接的目標對象是否可達。 可作為GC Roots 的對象有: 1、虛擬機棧里的變量。 2、方法區(qū)中的靜態(tài)屬性引用的對象。 3、方法區(qū)中的常量引用的對象。 4、native方法引用的對象。
3、有哪些GC
MInor GC:新生代Eden區(qū)滿時觸發(fā)。Major GC:老年代內存滿時觸發(fā)。Full GC:清除整個堆空間,通常和Major GC等價。
4、垃圾回收算法
4.1 標記清除算法
概述:標記無用對象,然后進行清除回收。一共分為兩個階段:標記階段和清除階段,所以該算法需要掃描兩次。優(yōu)點:實現(xiàn)簡單,不需要對對象進行移動。缺點:需要兩次掃描;會產生內存碎片,提高了GC的頻率。使用:多用于老年代。
4.2 復制算法:
概述:將內存劃為兩個相等的區(qū)域,每次只使用一個區(qū)域存放數(shù)據(jù)。垃圾回收時,將存活對象復制到另一個區(qū)域,然后將當前區(qū)域回收。優(yōu)點:無內存碎片;只掃描一次,效率高。缺點:將可用內存縮小為原來的一半,并且在對象存活率高的時候會頻繁進行GC。使用:多用于新生代。
4.3 標記整理算法
概述:在標記可回收的對象后將所有存活的對象壓縮到內存的一端,然后對端邊界以外的內存進行回收。需要兩次掃描。優(yōu)點:不會存在內存碎片。缺點:需要兩次掃描,并且需要對對象進行移動,效率偏低。使用:多用于老年代。
4.4 分代算法
對新生代中的對象采用復制算法,對老年代的對象采用標記清除或標記整理算法。
五、垃圾回收器
1、Serial + Serial Old
這是一組單線程垃圾回收器,并且GC線程在運行時會STW。
2、Parallel Scavenge + Parallel Old
相比于Serial + Serial Old垃圾收集器,PS+PO的組合優(yōu)點在于可以通過多線程(默認為CPU數(shù)量)進行垃圾回收,但是PS+PO依然會STW。
3、ParNew + CMS
ParNew收集器就是PS收集器為了兼容CMS做了部分改進產生的。 CMS 收集器是一種以最短回收停頓時間為目標的收集器,以 “ 最短用戶線程停頓時間 ” 著稱。整個垃圾收集過程分為 4 個步驟:
初始標記:標記一下 GC Roots 能直接關聯(lián)到的對象,速度較快。會STW。并發(fā)標記:標記出全部的垃圾對象,并且此時用戶線程依然在運行,所以會有新的垃圾產生。垃圾回收的絕大部分耗時都在此階段。重新標記:修正并發(fā)標記階段因用戶程序繼續(xù)運行而導致垃圾對象的標記記錄(可能不再是垃圾)。但并發(fā)標記產生的新垃圾無法處理,即產生浮動垃圾。會STW。并發(fā)清除:用標記-清除算法清除垃圾對象(所以CMS會產生內存碎片),可以和用戶線程并發(fā)執(zhí)行。
CMS收集器缺點:
對 CPU 資源敏感:默認分配的垃圾收集線程數(shù)為(CPU 數(shù)+3)/4,隨著 CPU 數(shù)量下降,占用 CPU 資源越多,吞吐量越小。無法處理浮動垃圾(Floating Garbage):由于存在浮動垃圾,并且在并發(fā)標記階段用戶線程也在并發(fā)執(zhí)行,CMS 收集器不能像其他收集器那樣等老年代被填滿時再進行GC,需要預留一部分空間提供用戶線程運行使用。當 CMS 運行時,預留的內存空間無法滿足用戶線程的需要,就會出現(xiàn) “ Concurrent Mode Failure ”的錯誤,這時將會啟動后備預案,臨時用 Serial Old 來重新進行老年代的垃圾收集。
4、G1垃圾收集器
在JDK1.9后,G1成為了默認的垃圾回收器。并且G1垃圾回收期在邏輯上分代,物理上不分代。
G1的堆區(qū)在分代的基礎上,引入分區(qū)的概念:
G1將堆分成了若干Region,這些分區(qū)不要求是連續(xù)的內存空間。Region的大小可以通過G1HeapRegionSize參數(shù)進行設置,其必須是2的冪,范圍允許為1Mb到32Mb。JVM的會基于堆內存的初始值和最大值的平均數(shù)計算分區(qū)的尺寸,平均的堆尺寸會分出約2000個Region。分區(qū)大小一旦設置,則啟動之后不會再變化。分區(qū)可以有效利用內存空間,因為收集整體是使用“標記-整理”,Region之間基于“復制”算法,GC后會將存活對象復制到可用分區(qū)(未分配的分區(qū)),所以不會產生空間碎片。
G1收集器的運作大致可以分為以下步驟:
初始標記:標記出 GC Roots 直接關聯(lián)的對象,并且修改TAMS(Next Top at Mark Set)的值,讓下一個階段用戶程序并發(fā)運行時,能在正確可用的Region中創(chuàng)建新對象,會STW。并發(fā)標記:從GC Root 開始對堆中的對象進行可達性分析,找出存活對象。最終標記:修正并發(fā)標記階段因用戶程序繼續(xù)運行而導致垃圾對象的標記記錄(可能不再是垃圾),并且虛擬機將這段時間對象變化記錄在線程Remembered Set Logs里面,最終標記需要把Remembered Set Logs的數(shù)據(jù)合并到Remembered Sets中,這個階段耗時較長,但可以和用戶線程并發(fā)執(zhí)行。會STW。篩選回收:篩選回收階段會對各個 Region 的回收價值和成本進行排序,根據(jù)用戶所期望的 GC 停頓時間來指定回收計劃(用最少的時間來回收包含垃圾最多的區(qū)域,這就是 Garbage First 的由來——第一時間清理垃圾最多的區(qū)塊),這里為了提高回收效率,并沒有采用和用戶線程并發(fā)執(zhí)行的方式,而是STW。
柚子快報激活碼778899分享:JVM基礎知識整理
文章鏈接
本文內容根據(jù)網絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉載請注明,如有侵權,聯(lián)系刪除。