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

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

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

 [[361294]]

本文轉(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)的知識點(diǎn),并匯總記錄。

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

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

圖 24-1 JVM 知識框架

二、面試題

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

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

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

「謝飛機(jī)」:就就就,都行!你看著來!

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

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

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

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

三、類加載過程描述

圖 24-2 JVM 類加載過程

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

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

四、寫個代碼加載下

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

所以,我們這里要手動寫一下,JVM 虛擬機(jī)的部分代碼,也就是類加載的過程。通過 Java 代碼來實現(xiàn) Java 虛擬機(jī)的部分功能,讓開發(fā) Java 代碼的程序員更容易理解虛擬機(jī)的執(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 虛擬機(jī)規(guī)范,使用 Java 代碼實現(xiàn) JVM 中加載 class 文件部分內(nèi)容。當(dāng)然這部分還不包括解析,因為解析部分的代碼非常龐大,我們先從把 .class 文件加載讀取開始了解。

2. 代碼講解

2.1 定義類路徑接口(Entry)

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

2.2 目錄形式路徑(DirEntry)

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

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

2.3 壓縮包形式路徑(ZipEntry)

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

2.4 混合形式路徑(CompositeEntry)

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

2.5 通配符類型路徑(WildcardEntry)

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

2.6 類路徑解析(Classpath)

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

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

  1. public class Classpath { 
  2.  
  3.     private Entry bootstrapClasspath;  //啟動類路徑 
  4.     private Entry extensionClasspath;  //擴(kuò)展類路徑 
  5.     private Entry userClasspath;       //用戶類路徑 
  6.  
  7.     public Classpath(String jreOption, String cpOption) { 
  8.         //啟動類&擴(kuò)展類 "C:\Program Files\Java\jdk1.8.0_161\jre" 
  9.         bootstrapAndExtensionClasspath(jreOption); 
  10.         //用戶類 F:\..\org\itstack\demo\test\HelloWorld 
  11.         parseUserClasspath(cpOption); 
  12.     } 
  13.  
  14.     private void bootstrapAndExtensionClasspath(String jreOption) { 
  15.          
  16.         String jreDir = getJreDir(jreOption); 
  17.  
  18.         //..jre/lib/* 
  19.         String jreLibPath = Paths.get(jreDir, "lib") + File.separator + "*"
  20.         bootstrapClasspath = new WildcardEntry(jreLibPath); 
  21.  
  22.         //..jre/lib/ext/* 
  23.         String jreExtPath = Paths.get(jreDir, "lib""ext") + File.separator + "*"
  24.         extensionClasspath = new WildcardEntry(jreExtPath); 
  25.  
  26.     } 
  27.  
  28.     private static String getJreDir(String jreOption) { 
  29.         if (jreOption != null && Files.exists(Paths.get(jreOption))) { 
  30.             return jreOption; 
  31.         } 
  32.         if (Files.exists(Paths.get("./jre"))) { 
  33.             return "./jre"
  34.         } 
  35.         String jh = System.getenv("JAVA_HOME"); 
  36.         if (jh != null) { 
  37.             return Paths.get(jh, "jre").toString(); 
  38.         } 
  39.         throw new RuntimeException("Can not find JRE folder!"); 
  40.     } 
  41.  
  42.     private void parseUserClasspath(String cpOption) { 
  43.         if (cpOption == null) { 
  44.             cpOption = "."
  45.         } 
  46.         userClasspath = Entry.create(cpOption); 
  47.     } 
  48.  
  49.     public byte[] readClass(String className) throws Exception { 
  50.         className = className + ".class"
  51.  
  52.         //[readClass]啟動類路徑 
  53.         try { 
  54.             return bootstrapClasspath.readClass(className); 
  55.         } catch (Exception ignored) { 
  56.             //ignored 
  57.         } 
  58.  
  59.         //[readClass]擴(kuò)展類路徑 
  60.         try { 
  61.             return extensionClasspath.readClass(className); 
  62.         } catch (Exception ignored) { 
  63.             //ignored 
  64.         } 
  65.  
  66.         //[readClass]用戶類路徑 
  67.         return userClasspath.readClass(className); 
  68.     } 
  69.  
  • 啟動類路徑,bootstrapClasspath.readClass(className);
  • 擴(kuò)展類路徑,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.     } 

這段就是使用 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, ...] 

這塊部分截取的程序運(yùn)行打印結(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與運(yùn)算 
  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.     } 
  • 讀取字節(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); 
  • 這里有一個小技巧,class 文件解析出來是一整片的內(nèi)容,JVM 需要按照虛擬機(jī)規(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. <init>    ()V 
  16. <init>    (Ljava/lang/String;)V 
  17. <init>    ([C)V 
  18. <init>    ([CII)V 
  19. <init>    ([III)V 
  20. <init>    ([BIII)V 
  21. <init>    ([BI)V 
  22. checkBounds    ([BII)V 
  23. <init>    ([BIILjava/lang/String;)V 
  24. <init>    ([BIILjava/nio/charset/Charset;)V 
  25. <init>    ([BLjava/lang/String;)V 
  26. <init>    ([BLjava/nio/charset/Charset;)V 
  27. <init>    ([BII)V 
  28. <init>    ([B)V 
  29. <init>    (Ljava/lang/StringBuffer;)V 
  30. <init>    (Ljava/lang/StringBuilder;)V 
  31. <init>    ([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. <clinit>    ()V 
  109.  
  110. 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 虛擬機(jī)規(guī)范,在調(diào)試Java版本的JVM,這樣就可以非常容易理解整個JVM的加載過程,都做了什么。

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

 

責(zé)任編輯:武曉燕 來源: bugstack蟲洞棧
相關(guān)推薦

2021-09-01 09:32:40

工具

2011-06-22 09:37:03

桌面虛擬化存儲

2020-11-16 08:37:16

MariaDB性能優(yōu)化

2021-05-06 10:25:37

蘋果手機(jī)二手

2020-12-16 11:09:27

JavaScript語言開發(fā)

2017-08-15 08:27:48

云備份問題恢復(fù)

2015-10-12 10:01:26

AndroidWindows應(yīng)用Windows 10

2018-06-26 14:42:10

StringJava數(shù)據(jù)

2018-06-20 10:43:58

云端霧端霧計算

2021-01-19 06:43:10

Netty框架網(wǎng)絡(luò)技術(shù)

2022-11-16 14:02:44

2020-04-28 17:26:04

監(jiān)督學(xué)習(xí)無監(jiān)督學(xué)習(xí)機(jī)器學(xué)習(xí)

2022-10-24 00:33:59

MySQL全局鎖行級鎖

2011-03-07 17:44:59

中小企業(yè)實施虛擬化

2020-04-11 11:21:22

留存分析模型分析

2022-08-08 08:48:15

Go版本偽版本

2023-02-17 14:40:08

MySQLSQL優(yōu)化

2023-06-26 11:59:52

標(biāo)簽質(zhì)量梳理

2021-05-31 07:22:46

ORM框架程序

2018-07-19 10:16:25

華光昱能
點(diǎn)贊
收藏

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