日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
為了搞清楚類加載,竟然手?jǐn)]JVM!

為了搞清楚類加載,竟然手?jǐn)]JVM!

作者:小傅哥 2020-12-31 07:57:25

云計算

虛擬化 當(dāng)學(xué)習(xí)一個新知識不知道從哪下手的時候,最有效的辦法是梳理這個知識結(jié)構(gòu)的脈絡(luò)信息,匯總出一整張的思維導(dǎo)出。接下來就是按照思維導(dǎo)圖的知識結(jié)構(gòu),一個個學(xué)習(xí)相應(yīng)的知識點,并匯總記錄。

創(chuàng)新互聯(lián)公司專注于橋東企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站建設(shè),商城網(wǎng)站定制開發(fā)。橋東網(wǎng)站建設(shè)公司,為橋東等地區(qū)提供建站服務(wù)。全流程按需定制開發(fā),專業(yè)設(shè)計,全程項目跟蹤,創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務(wù)

本文轉(zhuǎn)載自微信公眾號「bugstack蟲洞?!梗髡咝「蹈?。轉(zhuǎn)載本文請聯(lián)系bugstack蟲洞棧公眾號。

目錄

  • 一、前言
  • 二、面試題
  • 三、類加載過程描述
  • 四、寫個代碼加載下
    • 1. 案例工程
    • 2. 代碼講解
  • 五、解析字節(jié)碼文件
    • 1. 提取部分字節(jié)碼
    • 2. 解析魔數(shù)并校驗
    • 3. 解析版本號信息
    • 4. 解析全部內(nèi)容對照
  • 六、總結(jié)
  • 七、系列推薦

一、前言

學(xué)習(xí),不知道從哪下手?

當(dāng)學(xué)習(xí)一個新知識不知道從哪下手的時候,最有效的辦法是梳理這個知識結(jié)構(gòu)的脈絡(luò)信息,匯總出一整張的思維導(dǎo)出。接下來就是按照思維導(dǎo)圖的知識結(jié)構(gòu),一個個學(xué)習(xí)相應(yīng)的知識點,并匯總記錄。

就像JVM的學(xué)習(xí),可以說它包括了非常多的內(nèi)容,也是一個龐大的知識體系。例如:類加載、加載器、生命周期、性能優(yōu)化、調(diào)優(yōu)參數(shù)、調(diào)優(yōu)工具、優(yōu)化方案、內(nèi)存區(qū)域、虛擬機棧、直接內(nèi)存、內(nèi)存溢出、元空間、垃圾回收、可達(dá)性分析、標(biāo)記清除、回收過程等等。如果沒有梳理的一頭扎進(jìn)去,東一榔頭西一棒子,很容易造成學(xué)習(xí)恐懼感。

如圖 24-1 是 JVM 知識框架梳理,后續(xù)我們會按照這個結(jié)構(gòu)陸續(xù)講解每一塊內(nèi)容。

圖 24-1 JVM 知識框架

二、面試題

謝飛機,小記!,很多知識根本就是背背背,也沒法操作,難學(xué)!

「謝飛機」:大哥,你問我兩個JVM問題,我看看我自己還行不!

「面試官」:啊?嗯!往死了問還是?

「謝飛機」:就就就,都行!你看著來!

「面試官」:啊,那 JVM 加載過程都是什么步驟?

「謝飛機」:巴拉巴拉,加載、驗證、準(zhǔn)備、解析、初始化、使用、卸載!

「面試官」:嗯,背的挺好!我懷疑你沒操作過! 那加載的時候,JVM 規(guī)范規(guī)定從第幾位開始是解析常量池,以及數(shù)據(jù)類型是如何定義的,u1、u2、u4,是怎么個玩意?

「謝飛機」:握草!算了,告訴我看啥吧!

三、類加載過程描述

圖 24-2 JVM 類加載過程

「JVM 類加載過程分為」,加載、鏈接、初始化、使用和卸載這四個階段,在鏈接中又包括:驗證、準(zhǔn)備、解析。

  • 「加載」:Java 虛擬機規(guī)范對 class 文件格式進(jìn)行了嚴(yán)格的規(guī)則,但對于從哪里加載 class 文件,卻非常自由。Java 虛擬機實現(xiàn)可以從文件系統(tǒng)讀取、從JAR(或ZIP)壓縮包中提取 class 文件。除此之外也可以通過網(wǎng)絡(luò)下載、數(shù)據(jù)庫加載,甚至是運行時直接生成的 class 文件。
  • 「鏈接」:包括了三個階段;
    • 驗證,確保被加載類的正確性,驗證字節(jié)流是否符合 class 文件規(guī)范,例魔數(shù) 0xCAFEBABE,以及版本號等。
    • 準(zhǔn)備,為類的靜態(tài)變量分配內(nèi)存并設(shè)置變量初始值等
    • 解析,解析包括解析出常量池數(shù)據(jù)和屬性表信息,這里會包括 ConstantPool 結(jié)構(gòu)體以及 AttributeInfo 接口等。
  • 「初始化」:類加載完成的最后一步就是初始化,目的就是為標(biāo)記常量值的字段賦值,以及執(zhí)行 方法的過程。JVM虛擬機通過鎖的方式確保 clinit 僅被執(zhí)行一次
  • 「使用」:程序代碼執(zhí)行使用階段。
  • 「卸載」:程序代碼退出、異常、結(jié)束等。

四、寫個代碼加載下

JVM 之所以不好掌握,主要是因為不好實操。虛擬機是 C++ 寫的,很多 Java 程序員根本就不會去讀,或者讀不懂。那么,也就沒辦法實實在在的體會到,到底是怎么加載的,加載的時候都干了啥。只有看到代碼,我才覺得自己學(xué)會了!

所以,我們這里要手動寫一下,JVM 虛擬機的部分代碼,也就是類加載的過程。通過 Java 代碼來實現(xiàn) Java 虛擬機的部分功能,讓開發(fā) Java 代碼的程序員更容易理解虛擬機的執(zhí)行過程。

1. 案例工程

  
 
 
 
  1. interview-24
  2. ├── pom.xml
  3. └── src
  4.     └── main
  5.     │    └── java
  6.     │        └── org.itstack.interview.jvm
  7.     │             ├── classpath
  8.     │             │   ├── impl
  9.     │             │   │   ├── CompositeEntry.java
  10.     │             │   │   ├── DirEntry.java 
  11.     │             │   │   ├── WildcardEntry.java 
  12.     │             │   │   └── ZipEntry.java    
  13.     │             │   ├── Classpath.java
  14.     │             │   └── Entry.java    
  15.     │             ├── Cmd.java
  16.     │             └── Main.java
  17.     └── test
  18.          └── java
  19.              └── org.itstack.interview.jvm.test
  20.                  └── HelloWorld.java

「以上」,工程結(jié)構(gòu)就是按照 JVM 虛擬機規(guī)范,使用 Java 代碼實現(xiàn) JVM 中加載 class 文件部分內(nèi)容。當(dāng)然這部分還不包括解析,因為解析部分的代碼非常龐大,我們先從把 .class 文件加載讀取開始了解。

2. 代碼講解

2.1 定義類路徑接口(Entry)

  
 
 
 
  1. public interface Entry {
  2.     byte[] readClass(String className) throws IOException;
  3.     
  4.     static Entry create(String path) {
  5.         //File.pathSeparator;路徑分隔符(win\linux)
  6.         if (path.contains(File.pathSeparator)) {
  7.             return new CompositeEntry(path);
  8.         }
  9.         if (path.endsWith("*")) {
  10.             return new WildcardEntry(path);
  11.         }
  12.         if (path.endsWith(".jar") || path.endsWith(".JAR") ||
  13.                 path.endsWith(".zip") || path.endsWith(".ZIP")) {
  14.             return new ZipEntry(path);
  15.         }
  16.         return new DirEntry(path);
  17.     }
  18. }
  • 接口中提供了接口方法 readClass 和靜態(tài)方法 create(String path)。
  • jdk1.8 是可以在接口中編寫靜態(tài)方法的,在設(shè)計上屬于補全了抽象類的類似功能。這個靜態(tài)方法主要是按照不同的路徑地址類型,提供不同的解析方法。包括:CompositeEntry、WildcardEntry、ZipEntry、DirEntry,這四種。接下來分別看每一種的具體實現(xiàn)

2.2 目錄形式路徑(DirEntry)

  
 
 
 
  1. public class DirEntry implements Entry {
  2.     private Path absolutePath;
  3.     public DirEntry(String path){
  4.         //獲取絕對路徑
  5.         this.absolutePath = Paths.get(path).toAbsolutePath();
  6.     }
  7.     @Override
  8.     public byte[] readClass(String className) throws IOException {
  9.         return Files.readAllBytes(absolutePath.resolve(className));
  10.     }
  11.     @Override
  12.     public String toString() {
  13.         return this.absolutePath.toString();
  14.     }
  15. }

目錄形式的通過讀取絕對路徑下的文件,通過 Files.readAllBytes 方式獲取字節(jié)碼。

2.3 壓縮包形式路徑(ZipEntry)

  
 
 
 
  1. public class ZipEntry implements Entry {
  2.     private Path absolutePath;
  3.     public ZipEntry(String path) {
  4.         //獲取絕對路徑
  5.         this.absolutePath = Paths.get(path).toAbsolutePath();
  6.     }
  7.     @Override
  8.     public byte[] readClass(String className) throws IOException {
  9.         try (FileSystem zipFs = FileSystems.newFileSystem(absolutePath, null)) {
  10.             return Files.readAllBytes(zipFs.getPath(className));
  11.         }
  12.     }
  13.     @Override
  14.     public String toString() {
  15.         return this.absolutePath.toString();
  16.     }
  17. }
  • 其實壓縮包形式與目錄形式,只有在文件讀取上有包裝差別而已。FileSystems.newFileSystem

2.4 混合形式路徑(CompositeEntry)

  
 
 
 
  1. public class CompositeEntry implements Entry {
  2.     private final List entryList = new ArrayList<>();
  3.     public CompositeEntry(String pathList) {
  4.         String[] paths = pathList.split(File.pathSeparator);
  5.         for (String path : paths) {
  6.             entryList.add(Entry.create(path));
  7.         }
  8.     }
  9.     @Override
  10.     public byte[] readClass(String className) throws IOException {
  11.         for (Entry entry : entryList) {
  12.             try {
  13.                 return entry.readClass(className);
  14.             } catch (Exception ignored) {
  15.                 //ignored
  16.             }
  17.         }
  18.         throw new IOException("class not found " + className);
  19.     }
  20.     @Override
  21.     public String toString() {
  22.         String[] strs = new String[entryList.size()];
  23.         for (int i = 0; i < entryList.size(); i++) {
  24.             strs[i] = entryList.get(i).toString();
  25.         }
  26.         return String.join(File.pathSeparator, strs);
  27.     }
  28.     
  29. }
  • File.pathSeparator,是一個分隔符屬性,win/linux 有不同的類型,所以使用這個方法進(jìn)行分割路徑。
  • 分割后的路徑裝到 List 集合中,這個過程屬于拆分路徑。

2.5 通配符類型路徑(WildcardEntry)

  
 
 
 
  1. public class WildcardEntry extends CompositeEntry {
  2.     public WildcardEntry(String path) {
  3.         super(toPathList(path));
  4.     }
  5.     private static String toPathList(String wildcardPath) {
  6.         String baseDir = wildcardPath.replace("*", ""); // remove *
  7.         try {
  8.             return Files.walk(Paths.get(baseDir))
  9.                     .filter(Files::isRegularFile)
  10.                     .map(Path::toString)
  11.                     .filter(p -> p.endsWith(".jar") || p.endsWith(".JAR"))
  12.                     .collect(Collectors.joining(File.pathSeparator));
  13.         } catch (IOException e) {
  14.             return "";
  15.         }
  16.     }
  17. }
  • 這個類屬于混合形式路徑處理類的子類,唯一提供的方法就是把類路徑解析出來。

2.6 類路徑解析(Classpath)

啟動類路徑、擴展類路徑、用戶類路徑,熟悉嗎?是不經(jīng)??吹竭@幾句話,那么時候怎么實現(xiàn)的呢?

有了上面我們做的一些基礎(chǔ)類的工作,接下來就是類解析的實際調(diào)用過程。代碼如下:

  
 
 
 
  1. public class Classpath {
  2.     private Entry bootstrapClasspath;  //啟動類路徑
  3.     private Entry extensionClasspath;  //擴展類路徑
  4.     private Entry userClasspath;       //用戶類路徑
  5.     public Classpath(String jreOption, String cpOption) {
  6.         //啟動類&擴展類 "C:\Program Files\Java\jdk1.8.0_161\jre"
  7.         bootstrapAndExtensionClasspath(jreOption);
  8.         //用戶類 F:\..\org\itstack\demo\test\HelloWorld
  9.         parseUserClasspath(cpOption);
  10.     }
  11.     private void bootstrapAndExtensionClasspath(String jreOption) {
  12.         
  13.         String jreDir = getJreDir(jreOption);
  14.         //..jre/lib/*
  15.         String jreLibPath = Paths.get(jreDir, "lib") + File.separator + "*";
  16.         bootstrapClasspath = new WildcardEntry(jreLibPath);
  17.         //..jre/lib/ext/*
  18.         String jreExtPath = Paths.get(jreDir, "lib", "ext") + File.separator + "*";
  19.         extensionClasspath = new WildcardEntry(jreExtPath);
  20.     }
  21.     private static String getJreDir(String jreOption) {
  22.         if (jreOption != null && Files.exists(Paths.get(jreOption))) {
  23.             return jreOption;
  24.         }
  25.         if (Files.exists(Paths.get("./jre"))) {
  26.             return "./jre";
  27.         }
  28.         String jh = System.getenv("JAVA_HOME");
  29.         if (jh != null) {
  30.             return Paths.get(jh, "jre").toString();
  31.         }
  32.         throw new RuntimeException("Can not find JRE folder!");
  33.     }
  34.     private void parseUserClasspath(String cpOption) {
  35.         if (cpOption == null) {
  36.             cpOption = ".";
  37.         }
  38.         userClasspath = Entry.create(cpOption);
  39.     }
  40.     public byte[] readClass(String className) throws Exception {
  41.         className = className + ".class";
  42.         //[readClass]啟動類路徑
  43.         try {
  44.             return bootstrapClasspath.readClass(className);
  45.         } catch (Exception ignored) {
  46.             //ignored
  47.         }
  48.         //[readClass]擴展類路徑
  49.         try {
  50.             return extensionClasspath.readClass(className);
  51.         } catch (Exception ignored) {
  52.             //ignored
  53.         }
  54.         //[readClass]用戶類路徑
  55.         return userClasspath.readClass(className);
  56.     }
  57. }
  • 啟動類路徑,bootstrapClasspath.readClass(className);
  • 擴展類路徑,extensionClasspath.readClass(className);
  • 用戶類路徑,userClasspath.readClass(className);
  • 這回就看到它們具體在哪使用了吧!有了具體的代碼也就方便理解了

2.7 加載類測試驗證

  
 
 
 
  1. private static void startJVM(Cmd cmd) {
  2.     Classpath cp = new Classpath(cmd.jre, cmd.classpath);
  3.     System.out.printf("classpath:%s class:%s args:%s\n", cp, cmd.getMainClass(), cmd.getAppArgs());
  4.     //獲取className
  5.     String className = cmd.getMainClass().replace(".", "/");
  6.     try {
  7.         byte[] classData = cp.readClass(className);
  8.         System.out.println(Arrays.toString(classData));
  9.     } catch (Exception e) {
  10.         System.out.println("Could not find or load main class " + cmd.getMainClass());
  11.         e.printStackTrace();
  12.     }
  13. }

這段就是使用 Classpath 類進(jìn)行類路徑加載,這里我們測試加載 java.lang.String 類。你可以加載其他的類,或者自己寫的類

  • 配置IDEA,program arguments 參數(shù):-Xjre "C:\Program Files\Java\jdk1.8.0_161\jre" java.lang.String
  • 另外這里讀取出的 class 文件信息,打印的是 byte 類型信息。

「測試結(jié)果」

  
 
 
 
  1. [-54, -2, -70, -66, 0, 0, 0, 52, 2, 28, 3, 0, 0, -40, 0, 3, 0, 0, -37, -1, 3, 0, 0, -33, -1, 3, 0, 1, 0, 0, 8, 0, 15, 8, 0, 61, 8, 0, 85, 8, 0, 88, 8, 0, 89, 8, 0, 112, 8, 0, -81, 8, 0, -75, 8, 0, -47, 8, 0, -45, 1, 0, 0, 1, 0, 3, 40, 41, 73, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 59, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 3, 40, 41, 86, 1, 0, 3, 40, 41, 90, 1, 0, 4, 40, 41, 91, ...]

這塊部分截取的程序運行打印結(jié)果,就是讀取的 class 文件信息,只不過暫時還不能看出什么。接下來我們再把它翻譯過來!

五、解析字節(jié)碼文件

JVM 在把 class 文件加載完成后,接下來就進(jìn)入鏈接的過程,這個過程包括了內(nèi)容的校驗、準(zhǔn)備和解析,其實就是把 byte 類型 class 翻譯過來,做相應(yīng)的操作。

整個這個過程內(nèi)容相對較多,這里只做部分邏輯的實現(xiàn)和講解。如果讀者感興趣可以閱讀小傅哥的《用Java實現(xiàn)JVM》專欄。

1. 提取部分字節(jié)碼

  
 
 
 
  1. //取部分字節(jié)碼:java.lang.String
  2. private static byte[] classData = {
  3.         -54, -2, -70, -66, 0, 0, 0, 52, 2, 26, 3, 0, 0, -40, 0, 3, 0, 0, -37, -1, 3, 0, 0, -33, -1, 3, 0, 1, 0, 0, 8, 0,
  4.         59, 8, 0, 83, 8, 0, 86, 8, 0, 87, 8, 0, 110, 8, 0, -83, 8, 0, -77, 8, 0, -49, 8, 0, -47, 1, 0, 3, 40, 41, 73, 1,
  5.         0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 59, 1, 0, 20, 40, 41,
  6.         76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 3, 40, 41, 86, 1, 0, 3,
  7.         40, 41, 90, 1, 0, 4, 40, 41, 91, 66, 1, 0, 4, 40, 41, 91, 67, 1, 0, 4, 40, 67, 41, 67, 1, 0, 21, 40, 68, 41, 76,
  8.         106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 4, 40, 73, 41, 67, 1, 0, 4};

java.lang.String 解析出來的字節(jié)碼內(nèi)容較多,當(dāng)然包括的內(nèi)容也多,比如魔數(shù)、版本、類、常量、方法等等。所以我們這里只截取部分進(jìn)行進(jìn)行解析。

2. 解析魔數(shù)并校驗

很多文件格式都會規(guī)定滿足該格式的文件必須以某幾個固定字節(jié)開頭,這幾個字節(jié)主要起到標(biāo)識作用,叫作魔數(shù)(magic number)。

例如;

  • PDF文件以4字節(jié)“%PDF”(0x25、0x50、0x44、0x46)開頭,
  • ZIP文件以2字節(jié)“PK”(0x50、0x4B)開頭
  • class文件以4字節(jié)“0xCAFEBABE”開頭
  
 
 
 
  1. private static void readAndCheckMagic() {
  2.     System.out.println("\r\n------------ 校驗?zāi)?shù) ------------");
  3.     //從class字節(jié)碼中讀取前四位
  4.     byte[] magic_byte = new byte[4];
  5.     System.arraycopy(classData, 0, magic_byte, 0, 4);
  6.     
  7.     //將4位byte字節(jié)轉(zhuǎn)成16進(jìn)制字符串
  8.     String magic_hex_str = new BigInteger(1, magic_byte).toString(16);
  9.     System.out.println("magic_hex_str:" + magic_hex_str);
  10.     
  11.     //byte_magic_str 是16進(jìn)制的字符串,cafebabe,因為java中沒有無符號整型,所以如果想要無符號只能放到更高位中
  12.     long magic_unsigned_int32 = Long.parseLong(magic_hex_str, 16);
  13.     System.out.println("magic_unsigned_int32:" + magic_unsigned_int32);
  14.     
  15.     //魔數(shù)比對,一種通過字符串比對,另外一種使用假設(shè)的無符號16進(jìn)制比較。如果使用無符號比較需要將0xCAFEBABE & 0x0FFFFFFFFL與運算
  16.     System.out.println("0xCAFEBABE & 0x0FFFFFFFFL:" + (0xCAFEBABE & 0x0FFFFFFFFL));
  17.     
  18.     if (magic_unsigned_int32 == (0xCAFEBABE & 0x0FFFFFFFFL)) {
  19.         System.out.println("class字節(jié)碼魔數(shù)無符號16進(jìn)制數(shù)值一致校驗通過");
  20.     } else {
  21.         System.out.println("class字節(jié)碼魔數(shù)無符號16進(jìn)制數(shù)值一致校驗拒絕");
  22.     }
  23. }
  • 讀取字節(jié)碼中的前四位,-54, -2, -70, -66,將這四位轉(zhuǎn)換為16進(jìn)制。
  • 因為 java 中是沒有無符號整型的,所以只能用更高位存放。
  • 解析后就是魔數(shù)的對比,看是否與 CAFEBABE 一致。

「測試結(jié)果」

  
 
 
 
  1. ------------ 校驗?zāi)?shù) ------------
  2. magic_hex_str:cafebabe
  3. magic_unsigned_int32:3405691582
  4. 0xCAFEBABE & 0x0FFFFFFFFL:3405691582
  5. class字節(jié)碼魔數(shù)無符號16進(jìn)制數(shù)值一致校驗通過

3. 解析版本號信息

剛才我們已經(jīng)讀取了4位魔數(shù)信息,接下來再讀取2位,是版本信息。

魔數(shù)之后是class文件的次版本號和主版本號,都是u2類型。假設(shè)某class文件的主版本號是M,次版本號是m,那么完整的版本號可以表示成“M.m”的形式。次版本號只在J2SE 1.2之前用過,從1.2開始基本上就沒有什么用了(都是0)。主版本號在J2SE 1.2之前是45,從1.2開始,每次有大版本的Java版本發(fā)布,都會加1{45、46、47、48、49、50、51、52}

  
 
 
 
  1. private static void readAndCheckVersion() {
  2.     System.out.println("\r\n------------ 校驗版本號 ------------");
  3.     //從class字節(jié)碼第4位開始讀取,讀取2位
  4.     byte[] minor_byte = new byte[2];
  5.     System.arraycopy(classData, 4, minor_byte, 0, 2);
  6.     
  7.     //將2位byte字節(jié)轉(zhuǎn)成16進(jìn)制字符串
  8.     String minor_hex_str = new BigInteger(1, minor_byte).toString(16);
  9.     System.out.println("minor_hex_str:" + minor_hex_str);
  10.     
  11.     //minor_unsigned_int32 轉(zhuǎn)成無符號16進(jìn)制
  12.     int minor_unsigned_int32 = Integer.parseInt(minor_hex_str, 16);
  13.     System.out.println("minor_unsigned_int32:" + minor_unsigned_int32);
  14.     
  15.     //從class字節(jié)碼第6位開始讀取,讀取2位
  16.     byte[] major_byte = new byte[2];
  17.     System.arraycopy(classData, 6, major_byte, 0, 2);
  18.     
  19.     //將2位byte字節(jié)轉(zhuǎn)成16進(jìn)制字符串
  20.     String major_hex_str = new BigInteger(1, major_byte).toString(16);
  21.     System.out.println("major_hex_str:" + major_hex_str);
  22.     
  23.     //major_unsigned_int32 轉(zhuǎn)成無符號16進(jìn)制
  24.     int major_unsigned_int32 = Integer.parseInt(major_hex_str, 16);
  25.     System.out.println("major_unsigned_int32:" + major_unsigned_int32);
  26.     System.out.println("版本號:" + major_unsigned_int32 + "." + minor_unsigned_int32);
  27. }
  • 這里有一個小技巧,class 文件解析出來是一整片的內(nèi)容,JVM 需要按照虛擬機規(guī)范,一段一段的解析出所有的信息。
  • 同樣這里我們需要把2位byte轉(zhuǎn)換為16進(jìn)制信息,并繼續(xù)從第6位繼續(xù)讀取2位信息。組合出來的才是版本信息。

「測試結(jié)果」

  
 
 
 
  1. ------------ 校驗版本號 ------------
  2. minor_hex_str:0
  3. minor_unsigned_int32:0
  4. major_hex_str:34
  5. major_unsigned_int32:52
  6. 版本號:52.0

4. 解析全部內(nèi)容對照

按照 JVM 的加載過程,其實遠(yuǎn)不止魔數(shù)和版本號信息,還有很多其他內(nèi)容,這里我們可以把測試結(jié)果展示出來,方便大家有一個學(xué)習(xí)結(jié)果的比對印象。

  
 
 
 
  1. classpath:org.itstack.demo.jvm.classpath.Classpath@4bf558aa class:java.lang.String args:null
  2. version: 52.0
  3. constants count:540
  4. access flags:0x31
  5. this class:java/lang/String
  6. super class:java/lang/Object
  7. interfaces:[java/io/Serializable, java/lang/Comparable, java/lang/CharSequence]
  8. fields count:5
  9. value    [C
  10. hash    I
  11. serialVersionUID    J
  12. serialPersistentFields    [Ljava/io/ObjectStreamField;
  13. CASE_INSENSITIVE_ORDER    Ljava/util/Comparator;
  14. methods count: 94
  15.     ()V
  16.     (Ljava/lang/String;)V
  17.     ([C)V
  18.     ([CII)V
  19.     ([III)V
  20.     ([BIII)V
  21.     ([BI)V
  22. checkBounds    ([BII)V
  23.     ([BIILjava/lang/String;)V
  24.     ([BIILjava/nio/charset/Charset;)V
  25.     ([BLjava/lang/String;)V
  26.     ([BLjava/nio/charset/Charset;)V
  27.     ([BII)V
  28.     ([B)V
  29.     (Ljava/lang/StringBuffer;)V
  30.     (Ljava/lang/StringBuilder;)V
  31.     ([CZ)V
  32. length    ()I
  33. isEmpty    ()Z
  34. charAt    (I)C
  35. codePointAt    (I)I
  36. codePointBefore    (I)I
  37. codePointCount    (II)I
  38. offsetByCodePoints    (II)I
  39. getChars    ([CI)V
  40. getChars    (II[CI)V
  41. getBytes    (II[BI)V
  42. getBytes    (Ljava/lang/String;)[B
  43. getBytes    (Ljava/nio/charset/Charset;)[B
  44. getBytes    ()[B
  45. equals    (Ljava/lang/Object;)Z
  46. contentEquals    (Ljava/lang/StringBuffer;)Z
  47. nonSyncContentEquals    (Ljava/lang/AbstractStringBuilder;)Z
  48. contentEquals    (Ljava/lang/CharSequence;)Z
  49. equalsIgnoreCase    (Ljava/lang/String;)Z
  50. compareTo    (Ljava/lang/String;)I
  51. compareToIgnoreCase    (Ljava/lang/String;)I
  52. regionMatches    (ILjava/lang/String;II)Z
  53. regionMatches    (ZILjava/lang/String;II)Z
  54. startsWith    (Ljava/lang/String;I)Z
  55. startsWith    (Ljava/lang/String;)Z
  56. endsWith    (Ljava/lang/String;)Z
  57. hashCode    ()I
  58. indexOf    (I)I
  59. indexOf    (II)I
  60. indexOfSupplementary    (II)I
  61. lastIndexOf    (I)I
  62. lastIndexOf    (II)I
  63. lastIndexOfSupplementary    (II)I
  64. indexOf    (Ljava/lang/String;)I
  65. indexOf    (Ljava/lang/String;I)I
  66. indexOf    ([CIILjava/lang/String;I)I
  67. indexOf    ([CII[CIII)I
  68. lastIndexOf    (Ljava/lang/String;)I
  69. lastIndexOf    (Ljava/lang/String;I)I
  70. lastIndexOf    ([CIILjava/lang/String;I)I
  71. lastIndexOf    ([CII[CIII)I
  72. substring    (I)Ljava/lang/String;
  73. substring    (II)Ljava/lang/String;
  74. subSequence    (II)Ljava/lang/CharSequence;
  75. concat    (Ljava/lang/String;)Ljava/lang/String;
  76. replace    (CC)Ljava/lang/String;
  77. matches    (Ljava/lang/String;)Z
  78. contains    (Ljava/lang/CharSequence;)Z
  79. replaceFirst    (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  80. replaceAll    (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  81. replace    (Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;
  82. split    (Ljava/lang/String;I)[Ljava/lang/String;
  83. split    (Ljava/lang/String;)[Ljava/lang/String;
  84. join    (Ljava/lang/CharSequence;[Ljava/lang/CharSequence;)Ljava/lang/String;
  85. join    (Ljava/lang/CharSequence;Ljava/lang/Iterable;)Ljava/lang/String;
  86. toLowerCase    (Ljava/util/Locale;)Ljava/lang/String;
  87. toLowerCase    ()Ljava/lang/String;
  88. toUpperCase    (Ljava/util/Locale;)Ljava/lang/String;
  89. toUpperCase    ()Ljava/lang/String;
  90. trim    ()Ljava/lang/String;
  91. toString    ()Ljava/lang/String;
  92. toCharArray    ()[C
  93. format    (Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
  94. format    (Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
  95. valueOf    (Ljava/lang/Object;)Ljava/lang/String;
  96. valueOf    ([C)Ljava/lang/String;
  97. valueOf    ([CII)Ljava/lang/String;
  98. copyValueOf    ([CII)Ljava/lang/String;
  99. copyValueOf    ([C)Ljava/lang/String;
  100. valueOf    (Z)Ljava/lang/String;
  101. valueOf    (C)Ljava/lang/String;
  102. valueOf    (I)Ljava/lang/String;
  103. valueOf    (J)Ljava/lang/String;
  104. valueOf    (F)Ljava/lang/String;
  105. valueOf    (D)Ljava/lang/String;
  106. intern    ()Ljava/lang/String;
  107. compareTo    (Ljava/lang/Object;)I
  108.     ()V
  109. Process finished with exit code 0

如果大家對這部分驗證、準(zhǔn)備、解析,的實現(xiàn)過程感興趣,可以參照這部分用Java實現(xiàn)的JVM源碼:https://github.com/fuzhengwei/itstack-demo-jvm

六、總結(jié)

學(xué)習(xí) JVM 最大的問題是不好實踐,所以本文以案例實操的方式,學(xué)習(xí) JVM 的加載解析過程。也讓更多的對 JVM 感興趣的研發(fā),能更好的接觸到 JVM 并深入的學(xué)習(xí)。

有了以上這段代碼,大家可以參照 JVM 虛擬機規(guī)范,在調(diào)試Java版本的JVM,這樣就可以非常容易理解整個JVM的加載過程,都做了什么。

如果大家需要文章中一些原圖 xmind 或者源碼,可以添加作者小傅哥(fustack),或者關(guān)注公眾號:bugstack蟲洞棧進(jìn)行獲取。好了,本章節(jié)就扯到這,后續(xù)還有很多努力,持續(xù)原創(chuàng),感謝大家的支持!


新聞名稱:為了搞清楚類加載,竟然手?jǐn)]JVM!
文章URL:http://www.5511xx.com/article/cdehsji.html