自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

揭秘Java 之Jvm內(nèi)幕

云計(jì)算 虛擬化
這篇文章將重點(diǎn)分析jvm,涉及到的內(nèi)容包括jvm內(nèi)存模型,類加載器,GC回收算法,GC回收器,整體偏向于理論。

 這篇文章將重點(diǎn)分析jvm,涉及到的內(nèi)容包括jvm內(nèi)存模型,類加載器,GC回收算法,GC回收器,整體偏向于理論。

本篇文章不適合初學(xué)者,適合具有3年以上開發(fā)經(jīng)驗(yàn)的技術(shù)人員,歡迎大家一起交流分享,文章若有不足之處,歡迎讀者朋友們指出,先感謝。

[[335548]]

一 明確jdk,jre和jvm之間關(guān)系

下圖為官網(wǎng)關(guān)于jdk,jre和jvm的架構(gòu)圖,從該架構(gòu)圖,很容易看出三者之間關(guān)系:

(1)jdk包含jre,而jre又包含jvm

(2)jdk主要用于開發(fā)環(huán)境,jre主要用于發(fā)布環(huán)境,當(dāng)然,發(fā)布環(huán)境用jdk也沒問題,僅僅是性能可能會(huì)有點(diǎn)影響,jdk與jre關(guān)系有點(diǎn)類似程序debug版本和release版本之間關(guān)系

(3)從文件大小來說,jdk比jre大。從圖中可以看出,jdk比jre多了一層工具包,如常用的javac,java命令等

 

揭秘JAVA JVM內(nèi)幕(不合適初學(xué)者)

 

二 類加載器

關(guān)于jvm類加載器,可概括為如下圖:

 

揭秘JAVA JVM內(nèi)幕(不合適初學(xué)者)

 

1.為什么要有類加載器?

(1)將字節(jié)碼文件加載到運(yùn)行時(shí)數(shù)據(jù)區(qū)。.java源碼通過Javac命令編譯后形成的字節(jié)碼文件(.class),通過類加載器加載進(jìn)入jvm中的。

(2)確定字節(jié)碼文件在運(yùn)行時(shí)數(shù)據(jù)區(qū)的唯一性。相同的字節(jié)碼文件,通過不同的類加載器,就形成不同的文件,因此字節(jié)碼文件在運(yùn)行時(shí)數(shù)據(jù)區(qū)的唯一性是由字節(jié)碼文件和加載它的類加載器共同決定的

2.類加載器的種類

從種類上來劃分,類加載器主要?jiǎng)澐譃樗拇箢?/p>

(1)啟動(dòng)類加載器 (根類加載器Bootstrap ClassLoader):該類加載器位于類加載器的最頂層,主要加載jre核心相關(guān)jar包,如 /jre/lib/rt.jar

(2)擴(kuò)展類加載器(Extension ClassLoader):該類加載器位于類加載器層次的第二層,主要加載 jre擴(kuò)展相關(guān)jar包,如/jre/lib/ext/*.jar

(3)應(yīng)用程序類加載器(Application ClassLoader) App:該類加載器位于類加載器的第三層,主要加載類路徑(classpaht)下的相關(guān)jar包

(4)用戶自定義類加載器(User ClassLoader):該類加載器為用戶自定義類加載器,主要加載用戶指定的路徑下的相關(guān)jar包

3.類加載器的機(jī)制(雙親委派)

對(duì)于字節(jié)碼的加載,類加載機(jī)制為雙親委派,什么叫雙親委派呢?

類加載器獲取字節(jié)碼文件后,不是直接加載,而是將該字節(jié)碼文件傳遞給其直接父級(jí)類加載器,其直接父加載器又繼續(xù)傳遞給其直接父加載器的直接父加載器,依次類推到根父加載器,若根父加載器

能加載,則加載,否則交給其直接孩子加載器加載,直接孩子加載器能加載就加載,若不能,依次類推其直接孩子類加載器,若都不能加載,最后才由用戶自定義類加載器加載。

4.jdk 1.8 如何實(shí)現(xiàn)類加載器?

如下為jdk 1.8 類加載器的實(shí)現(xiàn),采用遞歸方式

  1. protected Class<?> loadClass(String name, boolean resolve) 
  2.         throws ClassNotFoundException 
  3.     { 
  4.         synchronized (getClassLoadingLock(name)) { 
  5.             // Firstcheck if the class has already been loaded 
  6.             Class<?> c = findLoadedClass(name); 
  7.             if (c == null) { 
  8.                 long t0 = System.nanoTime(); 
  9.                 try { 
  10.                     if (parent != null) { 
  11.                         c = parent.loadClass(namefalse); 
  12.                     } else { 
  13.                         c = findBootstrapClassOrNull(name); 
  14.                     } 
  15.                 } catch (ClassNotFoundException e) { 
  16.                     // ClassNotFoundException thrown if class not found 
  17.                     // from the non-null parent class loader 
  18.                 } 
  19.  
  20.                 if (c == null) { 
  21.                     // If still not found, then invoke findClass in order 
  22.                     // to find the class. 
  23.                     long t1 = System.nanoTime(); 
  24.                     c = findClass(name); 
  25.  
  26.                     // this is the defining class loader; record the stats 
  27.                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 
  28.                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 
  29.                     sun.misc.PerfCounter.getFindClasses().increment(); 
  30.                 } 
  31.             } 
  32.             if (resolve) { 
  33.                 resolveClass(c); 
  34.             } 
  35.             return c; 
  36.         } 
  37.     } 

5.破壞雙親委派模型

在某些情況下,由于受加載范圍限制,父類加載器無法加載到需要的文件,因此父類加載器需要委托其子類加載器去加載相應(yīng)的字節(jié)碼文件。

如在jdk中定義的數(shù)據(jù)庫驅(qū)動(dòng)接口Driver,但該接口的實(shí)現(xiàn)卻由不同的數(shù)據(jù)庫廠商來實(shí)現(xiàn),這就產(chǎn)生這樣一個(gè)問題:由啟動(dòng)類(Bootstrap ClassLoader)

執(zhí)行的DriverManager要加載實(shí)現(xiàn)了Driver接口的相關(guān)實(shí)現(xiàn)類,從而實(shí)現(xiàn)統(tǒng)一管理,但Bootstrap ClassLoader只能加載jre/lib下的相應(yīng)文件,不能加載

由各個(gè)廠商實(shí)現(xiàn)的Dirver接口相關(guān)實(shí)現(xiàn)類(Dirver實(shí)現(xiàn)類是由Application ClassLoader加載),這時(shí)就需要Bootstrap ClassLoader委托其子類加載器加載Driver

來實(shí)現(xiàn),從而破壞了雙親委派模型。

三 類的生命周期

java中的類,在jvm中的生命周期,大概分為五個(gè)階段:

1.加載階段:獲取字節(jié)碼二進(jìn)制流,并將靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化成方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),且在方法區(qū)生成相應(yīng)的類對(duì)象(java.lang.Class對(duì)象),作為該類的數(shù)據(jù)訪問入口。

2.連接階段:該階段包括三個(gè)小階段,即驗(yàn)證,準(zhǔn)備和解析三階段

(1)驗(yàn)證:確保字節(jié)碼文件符合虛擬機(jī)規(guī)范要求,如元數(shù)據(jù)驗(yàn)證,文件格式驗(yàn)證,字節(jié)碼驗(yàn)證和符號(hào)驗(yàn)證等

(2)準(zhǔn)備:為內(nèi)的靜態(tài)表里分配內(nèi)存,并且設(shè)置jvm默認(rèn)值,對(duì)于非靜態(tài)變量,此階段,不需分配內(nèi)存。

(3)解析:將常量池內(nèi)的符號(hào)引用轉(zhuǎn)化為直接引用

3.初始化階段:類對(duì)象使用前的一些必要初始化工作

如下引用自一位博友的觀點(diǎn),個(gè)人認(rèn)為解釋得很好。

在 Java 代碼中,如果要初始化一個(gè)靜態(tài)字段,我們可以在聲明時(shí)直接賦值,也可以在靜態(tài)代碼塊中對(duì)其賦值。

除了 final static 修飾的常量,直接賦值操作以及所有靜態(tài)代碼塊中的代碼,則會(huì)被 Java 編譯器置于同一方法中,并把它命名為 < clinit > 。初始化的目的是是為標(biāo)記為

常量值的字段賦值,以及執(zhí)行< clinit > 方法的過程。Java 虛擬機(jī)會(huì)通過加鎖來確保類的 < clinit > 方法僅被執(zhí)行一次。

哪些條件會(huì)發(fā)生類初始化呢?

(1)當(dāng)虛擬機(jī)啟動(dòng)時(shí),初始化用戶指定的主類(main函數(shù));

(2)當(dāng)遇到用于新建目標(biāo)類實(shí)例的 new 指令時(shí),初始化 new 指令的目標(biāo)類;

(3)當(dāng)遇到調(diào)用靜態(tài)方法的指令時(shí),初始化該靜態(tài)方法所在的類;

(4)子類的初始化會(huì)觸發(fā)父類的初始化;

(5)如果一個(gè)接口定義了 default 方法,那么直接實(shí)現(xiàn)或者間接實(shí)現(xiàn)該接口的類的初始化,會(huì)觸發(fā)該接口的初始化;

(6)使用反射 API 對(duì)某個(gè)類進(jìn)行反射調(diào)用時(shí),初始化這個(gè)類;

(7)當(dāng)初次調(diào)用 MethodHandle 實(shí)例時(shí),初始化該 MethodHandle 指向的方法所在的類。

4.使用階段:jvm中使用對(duì)象

5.卸載階段:將對(duì)象從jvm中卸載(unload),哪些條件會(huì)使jvm發(fā)生類卸載呢?

(1)加載該類的類加載器被回收

(2)該類的所有實(shí)例已經(jīng)被回收

(3)該類對(duì)應(yīng)的java.lang.Class對(duì)象沒有任何地方被引用

 

揭秘JAVA JVM內(nèi)幕(不合適初學(xué)者)

 

四 jvm內(nèi)存模型

1.JVM內(nèi)存模型是怎樣的?

如下為JVM內(nèi)存模型架構(gòu)圖,由于在之前的文章中論述過,這里就不再一 一論述,主要講解堆區(qū)。

 

揭秘JAVA JVM內(nèi)幕(不合適初學(xué)者)

 

在jdk 1.8前,堆區(qū)主要分為新生代、老年代和永久代。jdk 1.8后,去掉了永久代,增加了MetaSpace區(qū)。這里,主要分享jdk 1.8。

根據(jù)jdk1.8,堆區(qū)邏輯抽象為三個(gè)部分:

(1)新生代:包括Eden區(qū),S0區(qū)(也叫from區(qū)),S21(也叫TO區(qū))

(2)老年代

(3)Metaspace區(qū)

2.新生代和老年代的內(nèi)存大小是怎樣的?

根據(jù)官方建議,新生代占三分之一(Eden:S0:S1=8:1:1),老年代占三分之二,因此內(nèi)存分配圖如下:

 

揭秘JAVA JVM內(nèi)幕(不合適初學(xué)者)

 

3.GC回收是怎樣進(jìn)行的?

對(duì)象先在Eden區(qū)運(yùn)行,當(dāng)Eden內(nèi)存用占用滿時(shí),Eden會(huì)進(jìn)行兩個(gè)操作:回收不用的對(duì)象和將未回收對(duì)象放入s0區(qū),此時(shí)s0區(qū)和s1區(qū)互換名稱,即s0->s1,s1->s0,Eden區(qū)經(jīng)過一次對(duì)象回收后,釋放了空間,當(dāng)Eden下次再滿時(shí),執(zhí)行相同步驟,依次循環(huán)執(zhí)行,當(dāng)Eden區(qū)回收后,剩下的對(duì)象超過s0容量,則將觸發(fā)一次Minor GC,此時(shí)將未回收的對(duì)象放入老年區(qū),依次循環(huán)執(zhí)行,當(dāng)Eden區(qū)觸發(fā)Minor GC時(shí),剩余的對(duì)象容量大于old區(qū)剩余容量時(shí),則old區(qū)將觸發(fā)一次Major GC,此時(shí)便會(huì)觸發(fā)一次Full GC。需要注意的是,一般發(fā)生Major GC,基本都都會(huì)伴隨一次Full GC回收,F(xiàn)ull GC非常損耗性能,在JVM調(diào)優(yōu)時(shí),要注意。

下圖我在生產(chǎn)環(huán)境截的一張GC圖,監(jiān)控工具VisualVM

 

揭秘JAVA JVM內(nèi)幕(不合適初學(xué)者)

 

4.垃圾回收算法有哪些?

(1)標(biāo)記-清除算法

該算法分為2個(gè)階段,即標(biāo)記階段和清除階段,首先標(biāo)記所有要回收的對(duì)象,然后回收被標(biāo)記的對(duì)象。該算法效率低,且容易產(chǎn)生內(nèi)存碎片。

a.效率低:需要遍歷兩次內(nèi)存,第一次標(biāo)記,第二次回收被標(biāo)記對(duì)象

b.由于是非連續(xù)內(nèi)存片段,容易產(chǎn)生碎片,當(dāng)對(duì)象過大時(shí),容易發(fā)生Full GC

下圖為標(biāo)記-清除算法 回收前和回收后對(duì)比示意圖

 

揭秘JAVA JVM內(nèi)幕(不合適初學(xué)者)

 

(2)標(biāo)記-復(fù)制算法

該算法解決了“標(biāo)記-清除”算法效率低和大部分內(nèi)存碎片問題,它將內(nèi)存分為大小相等的兩塊,每次只使用其中一塊,當(dāng)其中一塊需要回收時(shí),只需將該塊區(qū)域還存活的對(duì)象復(fù)制到另一塊,然后再把該塊內(nèi)存一次性清理掉,循環(huán)往復(fù)。

下圖為標(biāo)記-復(fù)制算法回收前和回收收簡(jiǎn)要示意圖

 

揭秘JAVA JVM內(nèi)幕(不合適初學(xué)者)

 

然而,由于年輕代大部分對(duì)象駐留時(shí)間都非常短,98%的對(duì)象都很快被回收,存活的對(duì)象非常少,不需要按照內(nèi)存1:1來劃分,而是按照8:1:1來劃分,

將2%存活的對(duì)象放在s0(from區(qū))即可。

如下為按照Eden:s0:s1 =8:1:1 劃分示意圖

 

揭秘JAVA JVM內(nèi)幕(不合適初學(xué)者)

 

(3)標(biāo)記-整理算法

該算法分為兩階段,即標(biāo)記和整理,首先標(biāo)記所有存活對(duì)象,將這些對(duì)象向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。由于老年代的對(duì)象存活時(shí)間比較長(zhǎng),因此適合用該算法。

標(biāo)記過程仍與“標(biāo)記-清除”過程一致,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活對(duì)象向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。

如下為"標(biāo)記-整理算法"回收期和回收后示意圖

 

揭秘JAVA JVM內(nèi)幕(不合適初學(xué)者)

 

(4)分代收集算法

該算法為目前jvm算法,采用分代思想,模型如下:

 

揭秘JAVA JVM內(nèi)幕(不合適初學(xué)者)

 

5.常見GC回收器有哪些?

(1)SerialGC

SerialGC又叫串行回收器,也是最基礎(chǔ)的GC回收器,主要適用于單核cpu,新生代采用復(fù)制算法, 老年代采用標(biāo)記-壓縮算法,在運(yùn)行的過程中需要暫停應(yīng)用程序,

因此會(huì)造成STW問題,在JVM標(biāo)注參數(shù)為:-XX:+UseSerialGC 。

(2)ParallelGC

ParallelGC基于SerialGC,主要解決SerialGC串行問題,改為并行問題,解決多線程問題,但同樣會(huì)產(chǎn)生STW問題,jvm關(guān)鍵參數(shù):

a.-XX:+UseParNewGC,表示新生代并行(復(fù)制算法) 老年代串行(標(biāo)記-壓縮)

b.XX:+UseParallelOldGC,老年代也是并行

(3)CMS GC

CMSGC屬于老年代回收器,采用“標(biāo)記-清除算法”,不會(huì)發(fā)生STW問題,在jvm中參數(shù)設(shè)置:

-XX:+UseConcMarkSweepGC,表示老年代使用CMS收集器

(4)Garbage First

Garbage First面向jvm垃圾收集器 ,它滿足短時(shí)間停頓的同時(shí)達(dá)到一個(gè)高的吞吐量,適用于多核cpu和大內(nèi)存的服務(wù)端,也是jdk9的默認(rèn)垃圾回收器。

五 總結(jié)

深入分析了JVM內(nèi)存模型,其中重點(diǎn)分析了jdk,jre和jvm關(guān)系,jvm類加載器,jvm堆內(nèi)存劃分,GC回收器和GC回收算法等,整體偏向于理論,由于篇幅有限,本篇文章未分析這些技術(shù)在JVM實(shí)際調(diào)優(yōu)中是如何運(yùn)用的,將在接下來的文章中與大家分享。

責(zé)任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2020-05-08 16:55:48

Java虛擬機(jī)JVM

2009-05-26 14:53:50

2025-03-07 00:00:05

黑客AI人工智能

2010-09-17 15:25:03

JAVAJVM

2019-05-24 09:22:45

JavaWebCRUD

2009-07-09 16:23:36

java jvm

2010-09-27 11:12:46

MyEclipseJVM內(nèi)存

2012-03-21 09:44:15

云計(jì)算開源

2010-09-26 16:31:07

JVM參數(shù)配置

2020-04-15 21:43:22

JVMJavaVMware

2009-02-06 16:23:30

UAC漏洞提權(quán)

2009-07-08 15:11:58

JVM GC調(diào)整優(yōu)化

2017-11-02 14:53:30

PC硬件內(nèi)幕

2011-08-12 10:30:36

AMD服務(wù)器處理器

2010-09-26 16:55:31

JVM學(xué)習(xí)筆記

2024-11-15 09:14:23

JDK4NIO函數(shù)

2024-12-02 09:37:09

大模型AI產(chǎn)品

2010-09-25 15:19:01

2020-07-21 14:19:18

JVM編程語言

2023-08-22 20:23:15

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)