您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關(guān)怎樣解析JVM虛擬機(jī),小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說(shuō),跟著小編一起來(lái)看看吧。
首先我們需要了解什么是虛擬機(jī),為什么虛擬機(jī)可以實(shí)現(xiàn)夸平臺(tái),虛擬機(jī)在計(jì)算機(jī)中扮演一個(gè)什么樣的角色。
(從下向上看)
看上圖的操作系統(tǒng)與虛擬機(jī)層,可以看到,JVM是在操作系統(tǒng)之上的。他幫我們解決了操作系統(tǒng)差異性操作問(wèn)題,所以可以幫我們實(shí)現(xiàn)夸操作系統(tǒng)。
接著向上看,來(lái)到虛擬機(jī)可解析執(zhí)行文件這里,虛擬機(jī)就是根據(jù)這個(gè).class的規(guī)范來(lái)實(shí)現(xiàn)夸平臺(tái)的。
在向上到語(yǔ)言層,不同的語(yǔ)言可以有自己的語(yǔ)法、實(shí)現(xiàn)方式,但最終都要編譯為一個(gè)滿足.class規(guī)范的文件,來(lái)讓虛擬機(jī)執(zhí)行。
所以理論上,任何語(yǔ)言想使用JVM虛擬機(jī)實(shí)現(xiàn)夸平臺(tái)的操作,都可以根據(jù)規(guī)范生成.class文件,就可以使用JVM,并實(shí)現(xiàn)“一次編譯,多次運(yùn)行”。
字節(jié)碼規(guī)范(.class)
內(nèi)存管理
第一點(diǎn)已經(jīng)在上邊說(shuō)過(guò),不在重復(fù)。
第二點(diǎn)內(nèi)存管理也是我們接下來(lái)主要講的內(nèi)容。在沒(méi)有JVM的時(shí)代,在C/C++時(shí)期,寫代碼中除了寫正常的業(yè)務(wù)代碼之外,有很大一部分代碼是內(nèi)存分配與銷毀相關(guān)的代碼。稍有不慎就會(huì)造成內(nèi)存泄露。而使用虛擬機(jī)之后關(guān)于內(nèi)存的分配、銷毀操作就都由虛擬機(jī)來(lái)管理了。
相對(duì)的肯定會(huì)造成虛擬機(jī)占用更多內(nèi)存,在性能上與C/C++對(duì)比會(huì)較差,但隨著虛擬機(jī)的慢慢成熟性能差距正在縮小。
Jvm虛擬機(jī)主要分為五大模塊:類裝載子系統(tǒng)、運(yùn)行時(shí)數(shù)據(jù)區(qū)、執(zhí)行引擎、本地方法接口和垃圾收集模塊。
類的加載過(guò)程包含以下7步:
加載 -->校驗(yàn)-->準(zhǔn)備-->解析-->初始化-->使用-->卸載
其中連接校驗(yàn)、準(zhǔn)備-解析可以統(tǒng)稱為連接。
1. 通過(guò)Class的全限定名獲取Class的二進(jìn)制字節(jié)流 2. 將Class的二進(jìn)制內(nèi)容加載到虛擬機(jī)的方法區(qū) 3. 在內(nèi)存中生成一個(gè)java.lang.Class對(duì)象表示這個(gè)Class
獲取Class的二進(jìn)制字節(jié)流這個(gè)步驟有多種方式:
1. 從zip中讀取,如:從jar、war、ear等格式的文件中讀取Class文件內(nèi)容 2. 從網(wǎng)絡(luò)中獲取,如:Applet 3. 動(dòng)態(tài)生成,如:動(dòng)態(tài)代理、ASM框架等都是基于此方式 4. 由其他文件生成,典型的是從jsp文件生成相應(yīng)的Class
有兩種類型的類加載器
虛擬機(jī)自帶的類加載器
該類加載器沒(méi)有父加載器,他負(fù)責(zé)加載虛擬機(jī)的核心類庫(kù)。 如:java.lang.*等。 根類加載器從系統(tǒng)屬性sun.boot.class.path所指定的目錄中加載類庫(kù)。 根類加載器的實(shí)現(xiàn)依賴于底層操作系統(tǒng),屬于虛擬機(jī)的實(shí)現(xiàn)的一部分,他并沒(méi)有繼承java.lang.ClassLoader類。 如:java.lang.Object就是由根類加載器加載的。
它的父類加載器為根類加載器。 他從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類庫(kù),或者從JDK的安裝目錄的jre\lib\ext子目錄(擴(kuò)展目錄)下加載類庫(kù) 如果把用戶創(chuàng)建的JAR文件放在這個(gè)目錄下,也會(huì)自動(dòng)有擴(kuò)展類加載器加載。 擴(kuò)展類加載器是純java類,是java.lang.ClassLoader類的子類。
也稱為應(yīng)用加載器,他的父類加載器為擴(kuò)展類加載器。 他從環(huán)境變量classpath或者系統(tǒng)屬性java.class.path所指定的目錄中加載類。 他是用戶自定義的類加載器的默認(rèn)父加載器。 系統(tǒng)類加載器是純java類,是java.lang.ClassLoader子類。
App ClassLoader(系統(tǒng)<應(yīng)用>類加載器)
Extension ClassLoader(擴(kuò)展類加載器)
BootStrap ClassLoader(根加載器)
用戶自定義的類加載器
其一定是java.lang.ClassLoader抽象類(這個(gè)類本身就是提供給自定義加載器繼承的)的子類
用戶可以定制的加載方式
注意: 《類加載器的子父關(guān)系》非《子父類繼承關(guān)系》,而是一種數(shù)據(jù)結(jié)構(gòu),可以比做一個(gè)鏈表形式或樹型結(jié)構(gòu)。
代碼:
public class SystemClassLoader { public static void main(String[] args) { ClassLoader classLoader = ClassLoader.getSystemClassLoader(); System.out.println(classLoader); while (classLoader != null){ classLoader = classLoader.getParent(); System.out.println(classLoader); } } } 輸出: sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@7a7b0070 null
獲得類加載器的方法
方式 | 說(shuō)明 |
---|---|
clazz.getClassLoader(); | 獲得當(dāng)前類的ClassLoader,clazz為類的類對(duì)象,而不是普通對(duì)象 |
Thread.currentThread().getContextClassLoader(); | 獲得當(dāng)先線程上下文的ClassLoader |
ClassLoader.getSystemClassLoader(); | 獲得系統(tǒng)的ClassLoader |
DriverManager.getCallerClssLoader(); | 獲得調(diào)用者的ClassLoader |
/** * 獲取字符串的類加載器 * 返回為null表示使用的BootStrap ClassLoader */ public static void getStringClassLoader(){ Class clazz; try { clazz = Class.forName("java.lang.String"); System.out.println("java.lang.String: " + clazz.getClassLoader()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } 輸出: java.lang.String: null 表示使用BootStrap ClassLoader加載
除了根加載器,每個(gè)加載器被委托加載任務(wù)時(shí),都是第一時(shí)間選擇讓其父加載器來(lái)執(zhí)行加載操作,最終總是讓根類加載器來(lái)嘗試加載,如果加載失敗,則再依次返回加載,只要這個(gè)過(guò)程有一個(gè)加載器加載成功,那么就會(huì)執(zhí)行完成(這是Oracle公司Hotpot虛擬機(jī)默認(rèn)執(zhí)行的類加載機(jī)制,并且大部分虛擬機(jī)都是如此執(zhí)行的),整個(gè)過(guò)程如下圖所示:
自定義類加載器:
public class FreeClassLoader extends ClassLoader { private File classPathFile; public FreeClassLoader(){ String classPath = FreeClassLoader.class.getResource("").getPath(); this.classPathFile = new File(classPath); } @Override protected Class<?> findClass(String name){ if(classPathFile == null) { return null; } File classFile = new File(classPathFile,name.replaceAll("\\.","/") + ".class"); if(!classFile.exists()){ return null; } String className = FreeClassLoader.class.getPackage().getName() + "." + name; Class<?> clazz = null; try(FileInputStream in = new FileInputStream(classFile); ByteArrayOutputStream out = new ByteArrayOutputStream()){ byte [] buff = new byte[1024]; int len; while ((len = in.read(buff)) != -1){ out.write(buff,0,len); } clazz = defineClass(className,out.toByteArray(),0,out.size()); }catch (Exception e){ e.printStackTrace(); } return clazz; } /** * 測(cè)試加載 * @param args */ public static void main(String[] args) { FreeClassLoader classLoader = new FreeClassLoader(); Class<?> clazz = classLoader.findClass("SystemClassLoader"); try { Constructor constructor = clazz.getConstructor(); Object obj = constructor.newInstance(); System.out.println("當(dāng)前:" + obj.getClass().getClassLoader()); ClassLoader classLoader1 = obj.getClass().getClassLoader(); while (classLoader1 != null){ classLoader1 = classLoader1.getParent(); System.out.println("父:" + classLoader1); } SystemClassLoader.getClassLoader("com.freecloud.javabasics.classload.SystemClassLoader"); } catch (Exception e) { e.printStackTrace(); } } } 輸出: 當(dāng)前:com.freecloud.javabasics.classload.FreeClassLoader@e6ea0c6 父:sun.misc.Launcher$AppClassLoader@18b4aac2 父:sun.misc.Launcher$ExtClassLoader@1c6b6478 父:null com.freecloud.javabasics.classload.SystemClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
驗(yàn)證一個(gè)Class的二進(jìn)制內(nèi)容是否合法
1. 文件格式驗(yàn)證,確保文件格式符合Class文件格式的規(guī)范。 如:驗(yàn)證魔數(shù)、版本號(hào)等。 2. 元數(shù)據(jù)驗(yàn)證,確保Class的語(yǔ)義描述符合Java的Class規(guī)范。 如:該Class是否有父類、是否錯(cuò)誤繼承了final類、是否一個(gè)合法的抽象類等。 3. 字節(jié)碼驗(yàn)證,通過(guò)分析數(shù)據(jù)流和控制流,確保程序語(yǔ)義符合邏輯。 如:驗(yàn)證類型轉(zhuǎn)換是合法的。 4. 符號(hào)引用驗(yàn)證,發(fā)生于符號(hào)引用轉(zhuǎn)換為直接引用的時(shí)候(轉(zhuǎn)換發(fā)生在解析階段)。 如:驗(yàn)證引用的類、成員變量、方法的是否可以被訪問(wèn)(IllegalAccessError),當(dāng)前類是否存在相應(yīng)的方法、成員等(NoSuchMethodError、NoSuchFieldError)。
使用記事本或文本工具打開任意.class文件就會(huì)看到如下字節(jié)碼內(nèi)容:
左邊方框內(nèi)容表示魔數(shù): cafe babe(作用是確定這個(gè)文件是否為一個(gè)能被虛擬機(jī)接收的Class文件) 右邊方框表示版本號(hào) :0000 0034 (16進(jìn)制轉(zhuǎn)為10進(jìn)制為52表示JDK1.8)
在準(zhǔn)備階段,虛擬機(jī)會(huì)在方法區(qū)中為Class分配內(nèi)存,并設(shè)置static成員變量的初始值為默認(rèn)值。
注意這里僅僅會(huì)為static變量分配內(nèi)存(static變量在方法區(qū)中),并且初始化static變量的值為其所屬類型的默認(rèn)值。 如:int類型初始化為0,引用類型初始化為null。 即使聲明了這樣一個(gè)static變量: public static int a = 123; 在準(zhǔn)備階段后,a在內(nèi)存中的值仍然是0, 賦值123這個(gè)操作會(huì)在中初始化階段執(zhí)行,因此在初始化階段產(chǎn)生了對(duì)應(yīng)的Class對(duì)象之后a的值才是123 。
public class Test{ private static int a =1; public static long b; public static String str; static{ b = 2; str = "hello world" } } 為int類型的靜態(tài)變量 a 分配4個(gè)字節(jié)(32位)的內(nèi)存空間,并賦值為默認(rèn)值0; 為long類的靜態(tài)變量 b 分配8個(gè)字節(jié)(64位)的內(nèi)存空間,并默認(rèn)賦值為0; 為String類型的靜態(tài)變量 str 默認(rèn)賦值為null。
解析階段,虛擬機(jī)會(huì)將常量池中的符號(hào)引用替換為直接引用,解析主要針對(duì)的是類、接口、方法、成員變量等符號(hào)引用。在轉(zhuǎn)換成直接引用后,會(huì)觸發(fā)校驗(yàn)階段的符號(hào)引用驗(yàn)證,驗(yàn)證轉(zhuǎn)換之后的直接引用是否能找到對(duì)應(yīng)的類、方法、成員變量等。這里也可見類加載的各個(gè)階段在實(shí)際過(guò)程中,可能是交錯(cuò)執(zhí)行。
public class DynamicLink { static class Super{ public void test(){ System.out.println("super"); } } static class Sub1 extends Super{ @Override public void test(){ System.out.println("Sub1"); } } static class Sub2 extends Super { @Override public void test() { System.out.println("Sub2"); } } public static void main(String[] args) { Super super1 = new Sub1(); Super super2 = new Sub2(); super1.test(); super2.test(); } }
在解析階段,虛擬機(jī)會(huì)把類的二進(jìn)制數(shù)據(jù)中的符號(hào)引用替換為直接引用。
初始化階段即開始在內(nèi)存中構(gòu)造一個(gè)Class對(duì)象來(lái)表示該類,即執(zhí)行類構(gòu)造器<clinit>()的過(guò)程。需要注意下,<clinit>()不等同于創(chuàng)建類實(shí)例的構(gòu)造方法<init>()
1. <clinit>()方法中執(zhí)行的是對(duì)static變量進(jìn)行賦值的操作,以及static語(yǔ)句塊中的操作。 2. 虛擬機(jī)會(huì)確保先執(zhí)行父類的<clinit>()方法。 3. 如果一個(gè)類中沒(méi)有static的語(yǔ)句塊,也沒(méi)有對(duì)static變量的賦值操作,那么虛擬機(jī)不會(huì)為這個(gè)類生成<clinit>()方法。 4. 虛擬機(jī)會(huì)保證<clinit>()方法的執(zhí)行過(guò)程是線程安全的。
Java程序?qū)︻惖氖褂梅绞娇梢苑譃閮煞N
主動(dòng)使用
被動(dòng)使用
主動(dòng)使用類的七中方式,即類的初始化時(shí)機(jī):
1. 創(chuàng)建類的實(shí)例; 2. 訪問(wèn)某個(gè)類或接口的靜態(tài)變量(無(wú)重寫的變量繼承,變量其屬于父類,而不屬于子類),或者對(duì)該靜態(tài)變量賦值(靜態(tài)的read/write操作); 3. 調(diào)用類的靜態(tài)方法; 4. 反射(如:Class.forName("com.test.Test")); 5. 初始化一個(gè)類的子類(Chlidren 繼承了Parent類,如果僅僅初始化一個(gè)Children類,那么Parent類也是被主動(dòng)使用了); 6. Java虛擬機(jī)啟動(dòng)時(shí)被標(biāo)明為啟動(dòng)類的類(換句話說(shuō)就是包含main方法的那個(gè)類,而且本身main方法就是static的); 7. JDK1.7開始提供的動(dòng)態(tài)語(yǔ)言的支持:java.lang.invoke.MethodHandle實(shí)例的解析結(jié)果REF_getStatic,REF_public,REF_invokeStatic句柄對(duì)應(yīng)的類沒(méi)有初始化,則初始化;
除了上述所講七種情況,其他使用Java類的方式都被看作是對(duì)類的被動(dòng)使用,都不會(huì)導(dǎo)致類的初始化,比如:調(diào)用ClassLoader類的loadClass()方法加載一個(gè)類,并不是對(duì)類的主動(dòng)使用,不會(huì)導(dǎo)致類的初始化。
注意: 初始化單單是上述類加載、連接、初始化過(guò)程中的第三步,被動(dòng)使用并不會(huì)規(guī)定前面兩個(gè)步驟被使用與否 也就是說(shuō)即使被動(dòng)使用只是不會(huì)引起類的初始化,但是完全可以進(jìn)行類的加載以及連接。 例如:調(diào)用ClassLoader類的loadClass方法加載一個(gè)類,這并不是對(duì)類的主動(dòng)使用,不會(huì)導(dǎo)致類的初始化。 需要銘記于心的一點(diǎn): 只有當(dāng)程序訪問(wèn)的靜態(tài)變量或靜態(tài)變量確實(shí)在當(dāng)前類或當(dāng)前接口中定義時(shí),才可以認(rèn)為是對(duì)類或接口的主動(dòng)使用,通過(guò)子類調(diào)用繼承過(guò)來(lái)的靜態(tài)變量算作父類的主動(dòng)使用。
JVM中的Class只有滿足以下三個(gè)條件,才能被被卸載(unload)
1. 該類所有的實(shí)例都已經(jīng)被GC,也就是JVM中不存在該Class的任何實(shí)例。 2. 加載該類的ClassLoader已經(jīng)被GC。 3. 該類的java.lang.Class 對(duì)象沒(méi)有在任何地方被引用。 如:不能在任何地方通過(guò)反射訪問(wèn)該類的方法。
運(yùn)行時(shí)數(shù)據(jù)區(qū)主要分兩大塊: 線程共享:方法區(qū)(常量池、類信息、靜態(tài)常量等)、堆(存儲(chǔ)實(shí)例對(duì)象) 線程獨(dú)占:程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,它的作用可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。在虛擬機(jī)的概念模型里字節(jié)碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來(lái)完成。
特點(diǎn): 1. 如果線程正在執(zhí)行的是Java 方法,則這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址 2. 如果正在執(zhí)行的是Native 方法,則這個(gè)技術(shù)器值為空(Undefined) 3. 此內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況的區(qū)域
public class ProgramCounterJavap { public static void main(String[] args) { int a = 1; int b = 10; int c = 100; System.out.println( a + b * c); } }
使用javap反匯編工具可看到如下圖:
圖中紅框位置就是字節(jié)碼指令的偏移地址,當(dāng)執(zhí)行到main(java.lang.String[])時(shí)在當(dāng)前線程中會(huì)創(chuàng)建相應(yīng)的程序計(jì)數(shù)器,在計(jì)數(shù)器中存放執(zhí)行地址(紅框中內(nèi)容)。
這也說(shuō)明程序在運(yùn)行過(guò)程中計(jì)數(shù)器改變的只是值,而不是隨著程序的運(yùn)行需要更大的空間,也就不會(huì)發(fā)生溢出情況。
一個(gè)方法表示一個(gè)棧,遵循先進(jìn)后出的方式。每個(gè)棧中又分局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈表、返回地址等等。
虛擬機(jī)棧是線程隔離的,即每個(gè)線程都有自己獨(dú)立的虛擬機(jī)棧。
局部變量:存儲(chǔ)方法參數(shù)和方法內(nèi)部定義的局部變量名 操作數(shù)棧:棧針指令集(表達(dá)式棧) 動(dòng)態(tài)鏈接:保存指向運(yùn)行時(shí)常量池中該指針?biāo)鶎俜椒ǖ囊?nbsp;。作用是運(yùn)行期將符號(hào)引用轉(zhuǎn)化為直接引用 返回地址:保留退出方法時(shí),上層方法執(zhí)行狀態(tài)信息
虛擬機(jī)棧的StackOverflowError
單個(gè)線程請(qǐng)求的棧深度大于虛擬機(jī)允許的深度,則會(huì)拋出StackOverflowError(棧溢出錯(cuò)誤)
JVM會(huì)為每個(gè)線程的虛擬機(jī)棧分配一定的內(nèi)存大小(-Xss參數(shù)),因此虛擬機(jī)棧能夠容納的棧幀數(shù)量是有限的,若棧幀不斷進(jìn)棧而不出棧,最終會(huì)導(dǎo)致當(dāng)前線程虛擬機(jī)棧的內(nèi)存空間耗盡,典型如一個(gè)無(wú)結(jié)束條件的遞歸函數(shù)調(diào)用,代碼見下:
/** * 虛擬機(jī)棧的StackOverflowError * JVM參數(shù):-Xss160k * @Author: maomao * @Date: 2019-11-12 09:48 */ public class JVMStackSOF { private int count = 0; /** * 通過(guò)遞歸調(diào)用造成StackOverFlowError */ public void stackLeak() { count++; stackLeak(); } public static void main(String[] args) { JVMStackSOF oom = new JVMStackSOF(); try { oom.stackLeak(); }catch (Throwable e){ System.out.println("stack count : " + oom.count); e.printStackTrace(); } } }
設(shè)置單個(gè)線程的虛擬機(jī)棧內(nèi)存大小為160K,執(zhí)行main方法后,拋出了StackOverflow異常
stack count : 771 java.lang.StackOverflowError at com.freecloud.javabasics.jvm.JVMStackSOF.stackLeak(JVMStackSOF.java:18) at com.freecloud.javabasics.jvm.JVMStackSOF.stackLeak(JVMStackSOF.java:19) at com.freecloud.javabasics.jvm.JVMStackSOF.stackLeak(JVMStackSOF.java:19) at com.freecloud.javabasics.jvm.JVMStackSOF.stackLeak(JVMStackSOF.java:19) at com.freecloud.javabasics.jvm.JVMStackSOF.stackLeak(JVMStackSOF.java:19)
虛擬機(jī)棧的OutOfMemoryError
不同于StackOverflowError,OutOfMemoryError指的是當(dāng)整個(gè)虛擬機(jī)棧內(nèi)存耗盡,并且無(wú)法再申請(qǐng)到新的內(nèi)存時(shí)拋出的異常。
JVM未提供設(shè)置整個(gè)虛擬機(jī)棧占用內(nèi)存的配置參數(shù)。虛擬機(jī)棧的最大內(nèi)存大致上等于“JVM進(jìn)程能占用的最大內(nèi)存(依賴于具體操作系統(tǒng)) - 最大堆內(nèi)存 - 最大方法區(qū)內(nèi)存 - 程序計(jì)數(shù)器內(nèi)存(可以忽略不計(jì)) - JVM進(jìn)程本身消耗內(nèi)存”。當(dāng)虛擬機(jī)棧能夠使用的最大內(nèi)存被耗盡后,便會(huì)拋出OutOfMemoryError,可以通過(guò)不斷開啟新的線程來(lái)模擬這種異常,代碼如下:
/** * java棧溢出OutOfMemoryError * JVM參數(shù):-Xms20M -Xmx20M -Xmn10M -Xss2m -verbose:gc -XX:+PrintGCDetails * @Author: maomao * @Date: 2019-11-12 10:10 */ public class JVMStackOOM { private void dontStop() { try { Thread.sleep(24 * 60 * 60 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 通過(guò)不斷的創(chuàng)建新的線程使Stack內(nèi)存耗盡 */ public void stackLeakByThread(){ while (true){ Thread thread = new Thread(() -> dontStop()); thread.start(); } } public static void main(String[] args) { JVMStackOOM oom = new JVMStackOOM(); oom.stackLeakByThread(); } }
方法區(qū),主要存放已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
常亮池中的值是在類加載階段時(shí),通過(guò)靜態(tài)方法塊加載到內(nèi)存中
對(duì)于絕大多數(shù)應(yīng)用來(lái)說(shuō),這塊區(qū)域是 JVM 所管理的內(nèi)存中最大的一塊。線程共享,主要是存放對(duì)象實(shí)例和數(shù)組。內(nèi)部會(huì)劃分出多個(gè)線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer, TLAB)??梢晕挥谖锢砩喜贿B續(xù)的空間,但是邏輯上要連續(xù)。也是我們?cè)陂_發(fā)過(guò)程中主要使用的地方。
Heap的數(shù)據(jù)是二叉樹實(shí)現(xiàn),每個(gè)分配的地址會(huì)存儲(chǔ)內(nèi)存地址、與對(duì)象長(zhǎng)度。
在jdk 1.8之前的版本heap分新生代、老年帶、永久代,但在1.8之后永久代修改為元空間,本質(zhì)與永久代類似,都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。元空間不在虛擬機(jī)中,而是在本地內(nèi)存中。
我們使用下面一個(gè)生活中的例子來(lái)說(shuō)明:
首先我們把整個(gè)內(nèi)存處理過(guò)程比作一個(gè)倉(cāng)庫(kù)管理,用戶會(huì)有不同的東西要在我們倉(cāng)庫(kù)做存取。
倉(cāng)庫(kù)中的貨物比作我們內(nèi)存中的實(shí)例,用戶會(huì)不確定時(shí)間來(lái)我們這做存取操作,現(xiàn)在讓我們來(lái)管理這個(gè)倉(cāng)庫(kù),我們?nèi)绾巫龅叫首畲蠡?/p>
用戶會(huì)有不同大小的貨物要寄存,我們不做特殊處理,就是誰(shuí)先來(lái)了按照固定的順序存放。如下圖
但過(guò)了一段時(shí)間之后,用戶會(huì)不定期拿走自己的貨物
這時(shí)在我們倉(cāng)庫(kù)中就會(huì)產(chǎn)生大小不同的空位,如果這時(shí)還有用戶來(lái)存入貨物時(shí),就會(huì)發(fā)現(xiàn)我們需要拿著貨物在倉(cāng)庫(kù)中找到合適的空位放進(jìn)去(像俄羅斯方塊),但用戶的貨物不一定會(huì)正好放到對(duì)應(yīng)的空位中,就會(huì)產(chǎn)生不同大小的空位,而且不好找。
如果在有貨物取走之后我們就整理一次的話,又會(huì)非常累也耗時(shí)。
這時(shí)我們就會(huì)發(fā)現(xiàn),如果我們不對(duì)倉(cāng)庫(kù)做有效的劃分管理的話,我們的使用效率非常低。
我們將倉(cāng)庫(kù)邏輯的劃分為:
最常用: 用戶所有的貨物都先進(jìn)入到這里,如果用戶只是臨時(shí)存放,可以快速?gòu)倪@里取走。除非貨物大小超過(guò)倉(cāng)庫(kù)剩余空間(或我們認(rèn)定的大貨物)。
臨時(shí)緩沖1、2: 臨時(shí)緩沖存放,存放小于一定天數(shù)的貨物暫時(shí)放到這里,當(dāng)超出天數(shù)還未取走再放到后臺(tái)倉(cāng)庫(kù)中。
后臺(tái)倉(cāng)庫(kù): 存放大貨物與長(zhǎng)期無(wú)人取的貨物
上圖劃分了倆大區(qū)域,左邊比較小的是常用區(qū)域,用戶在存入貨物時(shí)最先放到這里,對(duì)于臨時(shí)存取的貨物可以非??斓奶幚怼?右邊比較大的區(qū)域做為后臺(tái)倉(cāng)庫(kù),存放長(zhǎng)時(shí)間無(wú)人取的或者常用區(qū)無(wú)法放下的大貨物。
通過(guò)這樣的劃分我們就可以把存取快的小貨物在一個(gè)較小的區(qū)域中處理,而不需要到大倉(cāng)庫(kù)中去找,可以極大的提升倉(cāng)庫(kù)效率。
JVM的垃圾回收算法是對(duì)內(nèi)存空間管理的一種實(shí)現(xiàn)算法,是在逐漸演進(jìn)中的內(nèi)存管理算法。
標(biāo)記-清除算法,就像他的名字一樣,分為“標(biāo)記”和“清除”兩個(gè)階段。首先遍歷所有內(nèi)存,將存活對(duì)象進(jìn)行標(biāo)記。清除階段遍歷堆中所有沒(méi)被標(biāo)記的對(duì)象進(jìn)行全部清除。在整個(gè)過(guò)程中會(huì)造成整個(gè)程序的stop the world。
缺點(diǎn):
造成stop the world(暫停整個(gè)程序)
產(chǎn)生內(nèi)存碎片
效率低
為什么要stop the world?
舉個(gè)簡(jiǎn)單的例子,假設(shè)我們的程序與GC線程是一起運(yùn)行的,試想這樣一個(gè)場(chǎng)景。
假設(shè)我們剛標(biāo)記完的A對(duì)象(非存活對(duì)象),此時(shí)在程序當(dāng)中又new了一個(gè)新的對(duì)象B,且A對(duì)象可以到達(dá)B對(duì)象。 但由于此時(shí)A對(duì)象在標(biāo)記階段已被標(biāo)記為非存活對(duì)象,B對(duì)象錯(cuò)過(guò)了標(biāo)記階段。因此到清除階段時(shí),新對(duì)象會(huì)將B對(duì)象清除掉。如此一來(lái)GC線程會(huì)導(dǎo)致程序無(wú)法正常工作。 我們剛new了一個(gè)對(duì)象,經(jīng)過(guò)一次GC,變?yōu)榱薾ull,會(huì)嚴(yán)重影響程序運(yùn)行。
產(chǎn)生內(nèi)存碎片
內(nèi)存被清理完之后就會(huì)產(chǎn)生像下圖3中(像俄羅斯方框游戲一樣),空閑的位置不連續(xù),如果需要為新的對(duì)象分配內(nèi)存空間時(shí),無(wú)法創(chuàng)建連續(xù)較大的空間,甚至在創(chuàng)建時(shí)還需要搜索整個(gè)內(nèi)存空間哪有空余空間可以分配。
效率低
也就是上邊兩個(gè)缺點(diǎn)的集合,會(huì)造成程序stop the world影響程序執(zhí)行,產(chǎn)生內(nèi)存碎片勢(shì)必在分配時(shí)會(huì)需要更多的時(shí)間去找合適的位置來(lái)分配。
為解決標(biāo)記清除算法的缺點(diǎn),提升效率,“復(fù)制”收集算法出現(xiàn)了。它將可用的內(nèi)存空間按容量劃分為大小相等的兩塊,每次只使用其中一塊。當(dāng)這一塊內(nèi)存用完了,就將還存活的對(duì)象復(fù)制到另外一快上,然后把已使用過(guò)的內(nèi)存空間一次清理掉。
這樣使每次都是對(duì)其中一塊進(jìn)行內(nèi)存回收,內(nèi)存分配也不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動(dòng)指針按順序分配內(nèi)存就可以了,實(shí)現(xiàn)簡(jiǎn)單運(yùn)行高效。
缺點(diǎn):
在存活對(duì)象較多時(shí),復(fù)制操作次數(shù)多,效率低。
內(nèi)存縮小了一半
針對(duì)以上兩種算法的問(wèn)題,又出現(xiàn)了“標(biāo)記-整理”算法,看名字與“標(biāo)記-清除”算法相似,不同的地方就是在“整理”階段。
在《深入理解Java虛擬機(jī)》中對(duì)“整理”階段的說(shuō)明是:"讓所有存活對(duì)象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存"
沒(méi)有找到具體某一個(gè)使用的方案,我分別畫了3張圖來(lái)表示我的理解:
標(biāo)記-移動(dòng)-清除
類似冒泡排序,把存活對(duì)象像最左側(cè)移動(dòng)
疑問(wèn):
如果確定邊界?記錄最后一個(gè)存活對(duì)象移動(dòng)的位置,后邊的全部清除?
為什么不是遇到可回收對(duì)象先回收再移動(dòng),這樣可以減少移動(dòng)可回收對(duì)象的操作(除非回收需要的性能比移動(dòng)還高)
標(biāo)記-移動(dòng)-清除 2
劃分移動(dòng)區(qū)域,將存活對(duì)象暫時(shí)放到該區(qū)域,然后一次清理使用過(guò)的內(nèi)存,最后再將存活對(duì)象一次移動(dòng)
疑問(wèn):
如何分配邏輯足夠存活對(duì)象的連續(xù)內(nèi)存空間?
如果空間不足怎么辦?
標(biāo)記-清除-整理
以上我對(duì)標(biāo)記-整理算法理解,如有不對(duì)的地方還請(qǐng)指正。
參考資料:
https://liujiacai.net/blog/2018/07/08/mark-sweep/
https://www.azul.com/files/Understanding_Java_Garbage_Collection_v41.pdf
分代收集不是一種新的算法,是針對(duì)對(duì)象的存活周期的不同將內(nèi)存劃分為幾塊。當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用“分代收集”。
GC分代的基本假設(shè):絕大部分對(duì)象的生命周期都非常短暫,存活時(shí)間短。
把Java堆分為新生代和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?/p>
新生代 每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對(duì)象死去,只有少量存活,那就選用復(fù)制算法,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集。
老年代 因?yàn)閷?duì)象存活率高、沒(méi)有額外空間對(duì)它進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記-清理”或“標(biāo)記-整理”算法來(lái)進(jìn)行回收。
垃圾收集器,就是針對(duì)垃圾回收算法的具體實(shí)現(xiàn)。
下圖是對(duì)收集器的推薦組合關(guān)系圖,有連線的說(shuō)明可以搭配使用。沒(méi)有最好的收集器,也沒(méi)有萬(wàn)能的收集器,只有最合適的收集器。
Serial
特點(diǎn):
- 單線程、簡(jiǎn)單高效(與其他收集器的單線程相比),對(duì)于限定單個(gè)CPU的環(huán)境來(lái)說(shuō),Serial收集器由于沒(méi)有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。 - 收集器進(jìn)行垃圾回收時(shí),必須暫停其他所有的工作線程,直到它結(jié)束(Stop The World)。
應(yīng)用場(chǎng)景:
適用于Client模式下的虛擬機(jī)
ParNew
ParNew收集器其實(shí)就是Serial收集器的多線程版本。
除了使用多線程外其余行為均和Serial收集器一模一樣(參數(shù)控制、收集算法、Stop The World、對(duì)象分配規(guī)則、回收策略等)
特點(diǎn):
- 多線程、ParNew收集器默認(rèn)開啟的收集線程數(shù)與CPU的數(shù)量相同,在CPU非常多的環(huán)境中,可以使用-XX:ParallelGCThreads參數(shù)來(lái)限制垃圾收集的線程數(shù)。 - 與Serial收集器一樣存在Stop The World問(wèn)題
應(yīng)用場(chǎng)景:
ParNew收集器是許多運(yùn)行在Server模式下的虛擬機(jī)中首選的新生代收集器,因?yàn)樗浅薙erial收集器外,唯一一個(gè)能與CMS收集器配合工作的。
Parallel Scavenge
與吞吐量關(guān)系密切,故也稱為吞吐量?jī)?yōu)先收集器。 除了使用多線程外其余行為均和Serial收集器一模一樣(參數(shù)控制、收集算法、Stop The World、對(duì)象分配規(guī)則、回收策略等)
特點(diǎn):
屬于新生代收集器也是采用復(fù)制算法的收集器,又是并行的多線程收集器(與ParNew收集器類似)。
該收集器的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量。還有一個(gè)值得關(guān)注的點(diǎn)是:GC自適應(yīng)調(diào)節(jié)策略(與ParNew收集器最重要的一個(gè)區(qū)別)
GC自適應(yīng)調(diào)節(jié)策略:
Parallel Scavenge收集器可設(shè)置-XX:+UseAdptiveSizePolicy參數(shù)。 當(dāng)開關(guān)打開時(shí)不需要手動(dòng)指定新生代的大小(-Xmn)、Eden與Survivor區(qū)的比例(-XX:SurvivorRation)、晉升老年代的對(duì)象年齡(-XX:PretenureSizeThreshold)等。 虛擬機(jī)會(huì)根據(jù)系統(tǒng)的運(yùn)行狀況收集性能監(jiān)控信息,動(dòng)態(tài)設(shè)置這些參數(shù)以提供最優(yōu)的停頓時(shí)間和最高的吞吐量,這種調(diào)節(jié)方式稱為GC的自適應(yīng)調(diào)節(jié)策略。 Parallel Scavenge收集器使用兩個(gè)參數(shù)控制吞吐量: XX:MaxGCPauseMillis 控制最大的垃圾收集停頓時(shí)間 XX:GCRatio 直接設(shè)置吞吐量的大小。
Serial Old
Serial Old是Serial收集器的老年代版本。
特點(diǎn):同樣是單線程收集器,采用標(biāo)記-整理算法。
應(yīng)用場(chǎng)景:主要也是使用在Client模式下的虛擬機(jī)中。也可在Server模式下使用。
Server模式下主要的兩大用途
1.在JDK1.5以及以前的版本中與Parallel Scavenge收集器搭配使用。 2.作為CMS收集器的后備方案,在并發(fā)收集Concurent Mode Failure時(shí)使用。
CMS
一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。
特點(diǎn):基于標(biāo)記-清除算法實(shí)現(xiàn)。并發(fā)收集、低停頓。
應(yīng)用場(chǎng)景:
適用于注重服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時(shí)間最短,給用戶帶來(lái)更好的體驗(yàn)等場(chǎng)景下。如web程序、b/s服務(wù)。
CMS收集器的運(yùn)行過(guò)程分為下列4步:
初始標(biāo)記:標(biāo)記GC Roots能直接到的對(duì)象。速度很快但是仍存在Stop The World問(wèn)題。 并發(fā)標(biāo)記:進(jìn)行GC Roots Tracing 的過(guò)程,找出存活對(duì)象且用戶線程可并發(fā)執(zhí)行。 重新標(biāo)記:為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄。仍然存在Stop The World問(wèn)題。 并發(fā)清除:對(duì)標(biāo)記的對(duì)象進(jìn)行清除回收。
CMS收集器的內(nèi)存回收過(guò)程是與用戶線程一起并發(fā)執(zhí)行的。
CMS收集器的缺點(diǎn):
對(duì)CPU資源非常敏感。
無(wú)法處理浮動(dòng)垃圾,可能出現(xiàn)Concurrent Model Failure失敗而導(dǎo)致另一次Full GC的產(chǎn)生。
因?yàn)椴捎脴?biāo)記-清除算法所以會(huì)存在空間碎片的問(wèn)題,導(dǎo)致大對(duì)象無(wú)法分配空間,不得不提前觸發(fā)一次Full GC。
G1
一款面向服務(wù)端應(yīng)用的垃圾收集器。不再是將整個(gè)內(nèi)存區(qū)域按代整體劃分,他根據(jù),將每一個(gè)內(nèi)存單元獨(dú)立為Region區(qū),每個(gè)Region還是按代劃分。 如下圖:
特點(diǎn):
- 并行與并發(fā):G1能充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢(shì),使用多個(gè)CPU來(lái)縮短Stop-The-World停頓時(shí)間。 部分收集器原本需要停頓Java線程來(lái)執(zhí)行GC動(dòng)作,G1收集器仍然可以通過(guò)并發(fā)的方式讓Java程序繼續(xù)運(yùn)行。 - 分代收集:G1能夠獨(dú)自管理整個(gè)Java堆,并且采用不同的方式去處理新創(chuàng)建的對(duì)象和已經(jīng)存活了一段時(shí)間、熬過(guò)多次GC的舊對(duì)象以獲取更好的收集效果。 - 空間整合:G1運(yùn)作期間不會(huì)產(chǎn)生空間碎片,收集后能提供規(guī)整的可用內(nèi)存。 - 可預(yù)測(cè)的停頓:G1除了追求低停頓外,還能建立可預(yù)測(cè)的停頓時(shí)間模型。能讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間段內(nèi),消耗在垃圾收集上的時(shí)間不得超過(guò)N毫秒。
G1為什么能建立可預(yù)測(cè)的停頓時(shí)間模型?
因?yàn)樗杏?jì)劃的避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集。G1跟蹤各個(gè)Region里面的垃圾堆積的大小,在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間,優(yōu)先回收價(jià)值最大的Region。這樣就保證了在有限的時(shí)間內(nèi)可以獲取盡可能高的收集效率。
G1與其他收集器的區(qū)別:
其他收集器的工作范圍是整個(gè)新生代或者老年代、G1收集器的工作范圍是整個(gè)Java堆。在使用G1收集器時(shí),它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region)。雖然也保留了新生代、老年代的概念,但新生代和老年代不再是相互隔離的,他們都是一部分Region(不需要連續(xù))的集合。
G1收集器存在的問(wèn)題:
Region不可能是孤立的,分配在Region中的對(duì)象可以與Java堆中的任意對(duì)象發(fā)生引用關(guān)系。在采用可達(dá)性分析算法來(lái)判斷對(duì)象是否存活時(shí),得掃描整個(gè)Java堆才能保證準(zhǔn)確性。其他收集器也存在這種問(wèn)題(G1更加突出而已)。會(huì)導(dǎo)致Minor GC效率下降。
G1收集器是如何解決上述問(wèn)題的?
采用Remembered Set來(lái)避免整堆掃描。G1中每個(gè)Region都有一個(gè)與之對(duì)應(yīng)的Remembered Set,虛擬機(jī)發(fā)現(xiàn)程序在對(duì)Reference類型進(jìn)行寫操作時(shí),會(huì)產(chǎn)生一個(gè)Write Barrier暫時(shí)中斷寫操作,檢查Reference引用對(duì)象是否處于多個(gè)Region中(即檢查老年代中是否引用了新生代中的對(duì)象),如果是,便通過(guò)CardTable把相關(guān)引用信息記錄到被引用對(duì)象所屬的Region的Remembered Set中。當(dāng)進(jìn)行內(nèi)存回收時(shí),在GC根節(jié)點(diǎn)的枚舉范圍中加入Remembered Set即可保證不對(duì)全堆進(jìn)行掃描也不會(huì)有遺漏。
如果不計(jì)算維護(hù) Remembered Set 的操作,G1收集器大致可分為如下步驟:
- 初始標(biāo)記:僅標(biāo)記GC Roots能直接到的對(duì)象,并且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序并發(fā)運(yùn)行時(shí),能在正確可用的Region中創(chuàng)建新對(duì)象。(需要線程停頓,但耗時(shí)很短。) - 并發(fā)標(biāo)記:從GC Roots開始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,找出存活對(duì)象。(耗時(shí)較長(zhǎng),但可與用戶程序并發(fā)執(zhí)行) - 最終標(biāo)記:為了修正在并發(fā)標(biāo)記期間因用戶程序執(zhí)行而導(dǎo)致標(biāo)記產(chǎn)生變化的那一部分標(biāo)記記錄。且對(duì)象的變化記錄在線程Remembered Set Logs里面,把Remembered Set Logs里面的數(shù)據(jù)合并到Remembered Set中。(需要線程停頓,但可并行執(zhí)行。) - 篩選回收:對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的GC停頓時(shí)間來(lái)制定回收計(jì)劃。(可并發(fā)執(zhí)行)
上邊詳細(xì)說(shuō)了垃圾收集相關(guān)的內(nèi)容,那有很重要的一點(diǎn)沒(méi)有說(shuō),就是如何確定某個(gè)對(duì)象是垃圾對(duì)象,可被回收呢? 有下邊兩種方式,虛擬機(jī)中使用的是可達(dá)性分析算法。
引用計(jì)數(shù)法
給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用他的時(shí)候,計(jì)數(shù)器的數(shù)值就+1,當(dāng)引用失效時(shí),計(jì)數(shù)器就-1。
任何時(shí)候計(jì)數(shù)器的數(shù)值都為0的對(duì)象時(shí)不可能再被使用的。
可達(dá)性分析算法 (java使用)
以GC Roots的對(duì)象作為起始點(diǎn),從這些起始點(diǎn)開始向下搜索,搜索所搜過(guò)的路徑稱為引用鏈Reference Chain,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連接時(shí),則證明此對(duì)象時(shí)不可用的。
什么是GC Roots?
在虛擬機(jī)中可作為GC Roots的對(duì)象有以下幾種:
虛擬機(jī)棧中引用的對(duì)象
方法區(qū)中類靜態(tài)屬性引用的對(duì)象
方法區(qū)常量引用的對(duì)象
本地方法棧引用的對(duì)象
匯編指令是指可被虛擬機(jī)識(shí)別指令,我們平時(shí)看到的.class字節(jié)碼文件中就存放著我們某個(gè)類的匯編指令,通過(guò)了解匯編指令,可以幫助我們更深入了解虛擬機(jī)的工作機(jī)制與內(nèi)存分配方式。
javap是jdk自帶的反解析工具。它的作用就是根據(jù)class字節(jié)碼文件,反解析出當(dāng)前類對(duì)應(yīng)的code區(qū)(匯編指令)、本地變量表、異常表和代碼行偏移量映射表、常量池等等信息。
當(dāng)然這些信息中,有些信息(如本地變量表、指令和代碼行偏移量映射表、常量池中方法的參數(shù)名稱等等)需要在使用javac編譯成class文件時(shí),指定參數(shù)才能輸出,比如,你直接javac xx.java,就不會(huì)在生成對(duì)應(yīng)的局部變量表等信息,如果你使用javac -g xx.java就可以生成所有相關(guān)信息了。
javap的用法格式: javap <options> <classes>
用法與參數(shù): -help --help -? 輸出此用法消息 -version 版本信息,其實(shí)是當(dāng)前javap所在jdk的版本信息,不是class在哪個(gè)jdk下生成的。 -v -verbose 輸出附加信息(包括行號(hào)、本地變量表,反匯編等詳細(xì)信息) -l 輸出行號(hào)和本地變量表 -public 僅顯示公共類和成員 -protected 顯示受保護(hù)的/公共類和成員 -package 顯示程序包/受保護(hù)的/公共類 和成員 (默認(rèn)) -p -private 顯示所有類和成員 -c 對(duì)代碼進(jìn)行反匯編 -s 輸出內(nèi)部類型簽名 -sysinfo 顯示正在處理的類的系統(tǒng)信息 (路徑, 大小, 日期, MD5 散列) -constants 顯示靜態(tài)最終常量 -classpath <path> 指定查找用戶類文件的位置 -bootclasspath <path> 覆蓋引導(dǎo)類文件的位置
一般常用的是-v -l -c三個(gè)選項(xiàng)。
下面通過(guò)一個(gè)簡(jiǎn)單例子說(shuō)明一下匯編指令,具體說(shuō)明會(huì)以注釋形式說(shuō)明。
具體指令作用與意思可參考該地址:
https://my.oschina.net/u/1019754/blog/3116798
package com.freecloud.javabasics.javap; /** * @Author: maomao * @Date: 2019-11-01 09:57 */ public class StringJavap { /** * String與StringBuilder */ public void StringAndStringBuilder(){ String s1 = "111" + "222"; StringBuilder s2 = new StringBuilder("111").append("222"); System.out.println(s1); System.out.println(s2); } public void StringStatic(){ String s1 = "333"; String s2 = "444"; String s3 = s1 + s2; String s4 = s1 + "555"; } private static final String STATIC_STRING = "staticString"; public void StringStatic2(){ String s1 = "111"; String s2 = STATIC_STRING + 111; } }
匯編指令
//文件地址 Classfile /Users/workspace/free-cloud-test/free-javaBasics/javap/target/classes/com/freecloud/javabasics/javap/StringJavap.class //最后修改日期與文件大小 Last modified 2019-11-5; size 1432 bytes MD5 checksum 1c6892dd51b214a205eae9612124535d Compiled from "StringJavap.java" //類信息 public class com.freecloud.javabasics.javap.StringJavap minor version: 0 //編譯版本號(hào)(jdk1.8) major version: 52 flags: ACC_PUBLIC, ACC_SUPER //常量池 Constant pool: #1 = Methodref #18.#45 // java/lang/Object."<init>":()V #2 = String #46 // 111222 #3 = Class #47 // java/lang/StringBuilder #4 = String #48 // 111 #5 = Methodref #3.#49 // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V #6 = String #50 // 222 #7 = Methodref #3.#51 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #8 = Fieldref #52.#53 // java/lang/System.out:Ljava/io/PrintStream; #9 = Methodref #54.#55 // java/io/PrintStream.println:(Ljava/lang/String;)V #10 = Methodref #54.#56 // java/io/PrintStream.println:(Ljava/lang/Object;)V #11 = String #57 // 333 #12 = String #58 // 444 #13 = Methodref #3.#45 // java/lang/StringBuilder."<init>":()V #14 = Methodref #3.#59 // java/lang/StringBuilder.toString:()Ljava/lang/String; #15 = String #60 // 555 #16 = Class #61 // com/freecloud/javabasics/javap/StringJavap #17 = String #62 // staticString111 #18 = Class #63 // java/lang/Object #19 = Utf8 STATIC_STRING #20 = Utf8 Ljava/lang/String; #21 = Utf8 ConstantValue #22 = String #64 // staticString #23 = Utf8 <init> #24 = Utf8 ()V #25 = Utf8 Code #26 = Utf8 LineNumberTable #27 = Utf8 LocalVariableTable #28 = Utf8 this #29 = Utf8 Lcom/freecloud/javabasics/javap/StringJavap; #30 = Utf8 main #31 = Utf8 ([Ljava/lang/String;)V #32 = Utf8 args #33 = Utf8 [Ljava/lang/String; #34 = Utf8 MethodParameters #35 = Utf8 StringAndStringBuilder #36 = Utf8 s1 #37 = Utf8 s2 #38 = Utf8 Ljava/lang/StringBuilder; #39 = Utf8 StringStatic #40 = Utf8 s3 #41 = Utf8 s4 #42 = Utf8 StringStatic2 #43 = Utf8 SourceFile #44 = Utf8 StringJavap.java #45 = NameAndType #23:#24 // "<init>":()V #46 = Utf8 111222 #47 = Utf8 java/lang/StringBuilder #48 = Utf8 111 #49 = NameAndType #23:#65 // "<init>":(Ljava/lang/String;)V #50 = Utf8 222 #51 = NameAndType #66:#67 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #52 = Class #68 // java/lang/System #53 = NameAndType #69:#70 // out:Ljava/io/PrintStream; #54 = Class #71 // java/io/PrintStream #55 = NameAndType #72:#65 // println:(Ljava/lang/String;)V #56 = NameAndType #72:#73 // println:(Ljava/lang/Object;)V #57 = Utf8 333 #58 = Utf8 444 #59 = NameAndType #74:#75 // toString:()Ljava/lang/String; #60 = Utf8 555 #61 = Utf8 com/freecloud/javabasics/javap/StringJavap #62 = Utf8 staticString111 #63 = Utf8 java/lang/Object #64 = Utf8 staticString #65 = Utf8 (Ljava/lang/String;)V #66 = Utf8 append #67 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #68 = Utf8 java/lang/System #69 = Utf8 out #70 = Utf8 Ljava/io/PrintStream; #71 = Utf8 java/io/PrintStream #72 = Utf8 println #73 = Utf8 (Ljava/lang/Object;)V #74 = Utf8 toString #75 = Utf8 ()Ljava/lang/String; { //默認(rèn)構(gòu)造方法 public com.freecloud.javabasics.javap.StringJavap(); //輸入?yún)?shù)(該處表示無(wú)參) descriptor: ()V flags: ACC_PUBLIC //指令代碼《也是執(zhí)行代碼,重點(diǎn)關(guān)注》 Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return //指令與代碼中的行號(hào)關(guān)系 LineNumberTable: line 7: 0 //本地變量表 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/freecloud/javabasics/javap/StringJavap; // 對(duì)應(yīng)StringAndStringBuilder方法 public void StringAndStringBuilder(); descriptor: ()V //描述方法關(guān)鍵字 flags: ACC_PUBLIC Code: //stack() locals(本地變量數(shù)/方法內(nèi)使用的變量數(shù)) args_size(入?yún)?shù),所有方法都有一個(gè)this所以參數(shù)至少為1) stack=3, locals=3, args_size=1 //通過(guò)#2可在常量池中找到111222字符串,表示在編譯時(shí)就把原本的"111" + "222"合并為一個(gè)常量 0: ldc #2 // String 111222 2: astore_1 3: new #3 // class java/lang/StringBuilder 6: dup 7: ldc #4 // String 111 9: invokespecial #5 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 12: ldc #6 // String 222 14: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 17: astore_2 18: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 21: aload_1 22: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 25: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 28: aload_2 29: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V //返回指針,無(wú)論方法是否有返回值,都會(huì)有該指令,作用是出棧 32: return LineNumberTable: line 19: 0 line 20: 3 line 22: 18 line 23: 25 line 24: 32 LocalVariableTable: Start Length Slot Name Signature 0 33 0 this Lcom/freecloud/javabasics/javap/StringJavap; 3 30 1 s1 Ljava/lang/String; 18 15 2 s2 Ljava/lang/StringBuilder; public void StringStatic(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=5, args_size=1 0: ldc #11 // String 333 2: astore_1 3: ldc #12 // String 444 5: astore_2 6: new #3 // class java/lang/StringBuilder 9: dup 10: invokespecial #13 // Method java/lang/StringBuilder."<init>":()V 13: aload_1 14: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 17: aload_2 18: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #14 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: astore_3 25: new #3 // class java/lang/StringBuilder 28: dup 29: invokespecial #13 // Method java/lang/StringBuilder."<init>":()V 32: aload_1 33: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 36: ldc #15 // String 555 38: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 41: invokevirtual #14 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 44: astore 4 46: return LineNumberTable: line 27: 0 line 28: 3 line 29: 6 line 30: 25 line 31: 46 LocalVariableTable: Start Length Slot Name Signature 0 47 0 this Lcom/freecloud/javabasics/javap/StringJavap; 3 44 1 s1 Ljava/lang/String; 6 41 2 s2 Ljava/lang/String; 25 22 3 s3 Ljava/lang/String; 46 1 4 s4 Ljava/lang/String; public void StringStatic2(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=3, args_size=1 0: ldc #4 // String 111 2: astore_1 3: ldc #17 // String staticString111 5: astore_2 6: return LineNumberTable: line 35: 0 line 36: 3 line 37: 6 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this Lcom/freecloud/javabasics/javap/StringJavap; 3 4 1 s1 Ljava/lang/String; 6 1 2 s2 Ljava/lang/String; } SourceFile: "StringJavap.java"
可以在指令集中明確看到我們上邊講解的內(nèi)存運(yùn)行時(shí)數(shù)據(jù)區(qū)的一些影子。
比如常量池、本地變量表、虛擬機(jī)棧(每個(gè)方法可以理解為一個(gè)棧,具體方法內(nèi)就是Code區(qū))、返回地址(return)
以上就是怎樣解析JVM虛擬機(jī),小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。