為了搞清楚類加載,竟然手?jǐn)]JVM!
本文轉(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. 案例工程
- interview-24
- ├── pom.xml
- └── src
- └── main
- │ └── java
- │ └── org.itstack.interview.jvm
- │ ├── classpath
- │ │ ├── impl
- │ │ │ ├── CompositeEntry.java
- │ │ │ ├── DirEntry.java
- │ │ │ ├── WildcardEntry.java
- │ │ │ └── ZipEntry.java
- │ │ ├── Classpath.java
- │ │ └── Entry.java
- │ ├── Cmd.java
- │ └── Main.java
- └── test
- └── java
- └── org.itstack.interview.jvm.test
- └── HelloWorld.java
「以上」,工程結(jié)構(gòu)就是按照 JVM 虛擬機(jī)規(guī)范,使用 Java 代碼實現(xiàn) JVM 中加載 class 文件部分內(nèi)容。當(dāng)然這部分還不包括解析,因為解析部分的代碼非常龐大,我們先從把 .class 文件加載讀取開始了解。
2. 代碼講解
2.1 定義類路徑接口(Entry)
- public interface Entry {
- byte[] readClass(String className) throws IOException;
- static Entry create(String path) {
- //File.pathSeparator;路徑分隔符(win\linux)
- if (path.contains(File.pathSeparator)) {
- return new CompositeEntry(path);
- }
- if (path.endsWith("*")) {
- return new WildcardEntry(path);
- }
- if (path.endsWith(".jar") || path.endsWith(".JAR") ||
- path.endsWith(".zip") || path.endsWith(".ZIP")) {
- return new ZipEntry(path);
- }
- return new DirEntry(path);
- }
- }
- 接口中提供了接口方法 readClass 和靜態(tài)方法 create(String path)。
- jdk1.8 是可以在接口中編寫靜態(tài)方法的,在設(shè)計上屬于補(bǔ)全了抽象類的類似功能。這個靜態(tài)方法主要是按照不同的路徑地址類型,提供不同的解析方法。包括:CompositeEntry、WildcardEntry、ZipEntry、DirEntry,這四種。接下來分別看每一種的具體實現(xiàn)
2.2 目錄形式路徑(DirEntry)
- public class DirEntry implements Entry {
- private Path absolutePath;
- public DirEntry(String path){
- //獲取絕對路徑
- this.absolutePath = Paths.get(path).toAbsolutePath();
- }
- @Override
- public byte[] readClass(String className) throws IOException {
- return Files.readAllBytes(absolutePath.resolve(className));
- }
- @Override
- public String toString() {
- return this.absolutePath.toString();
- }
- }
目錄形式的通過讀取絕對路徑下的文件,通過 Files.readAllBytes 方式獲取字節(jié)碼。
2.3 壓縮包形式路徑(ZipEntry)
- public class ZipEntry implements Entry {
- private Path absolutePath;
- public ZipEntry(String path) {
- //獲取絕對路徑
- this.absolutePath = Paths.get(path).toAbsolutePath();
- }
- @Override
- public byte[] readClass(String className) throws IOException {
- try (FileSystem zipFs = FileSystems.newFileSystem(absolutePath, null)) {
- return Files.readAllBytes(zipFs.getPath(className));
- }
- }
- @Override
- public String toString() {
- return this.absolutePath.toString();
- }
- }
- 其實壓縮包形式與目錄形式,只有在文件讀取上有包裝差別而已。FileSystems.newFileSystem
2.4 混合形式路徑(CompositeEntry)
- public class CompositeEntry implements Entry {
- private final List<Entry> entryList = new ArrayList<>();
- public CompositeEntry(String pathList) {
- String[] paths = pathList.split(File.pathSeparator);
- for (String path : paths) {
- entryList.add(Entry.create(path));
- }
- }
- @Override
- public byte[] readClass(String className) throws IOException {
- for (Entry entry : entryList) {
- try {
- return entry.readClass(className);
- } catch (Exception ignored) {
- //ignored
- }
- }
- throw new IOException("class not found " + className);
- }
- @Override
- public String toString() {
- String[] strs = new String[entryList.size()];
- for (int i = 0; i < entryList.size(); i++) {
- strs[i] = entryList.get(i).toString();
- }
- return String.join(File.pathSeparator, strs);
- }
- }
- File.pathSeparator,是一個分隔符屬性,win/linux 有不同的類型,所以使用這個方法進(jìn)行分割路徑。
- 分割后的路徑裝到 List 集合中,這個過程屬于拆分路徑。
2.5 通配符類型路徑(WildcardEntry)
- public class WildcardEntry extends CompositeEntry {
- public WildcardEntry(String path) {
- super(toPathList(path));
- }
- private static String toPathList(String wildcardPath) {
- String baseDir = wildcardPath.replace("*", ""); // remove *
- try {
- return Files.walk(Paths.get(baseDir))
- .filter(Files::isRegularFile)
- .map(Path::toString)
- .filter(p -> p.endsWith(".jar") || p.endsWith(".JAR"))
- .collect(Collectors.joining(File.pathSeparator));
- } catch (IOException e) {
- return "";
- }
- }
- }
- 這個類屬于混合形式路徑處理類的子類,唯一提供的方法就是把類路徑解析出來。
2.6 類路徑解析(Classpath)
啟動類路徑、擴(kuò)展類路徑、用戶類路徑,熟悉嗎?是不經(jīng)??吹竭@幾句話,那么時候怎么實現(xiàn)的呢?
有了上面我們做的一些基礎(chǔ)類的工作,接下來就是類解析的實際調(diào)用過程。代碼如下:
- public class Classpath {
- private Entry bootstrapClasspath; //啟動類路徑
- private Entry extensionClasspath; //擴(kuò)展類路徑
- private Entry userClasspath; //用戶類路徑
- public Classpath(String jreOption, String cpOption) {
- //啟動類&擴(kuò)展類 "C:\Program Files\Java\jdk1.8.0_161\jre"
- bootstrapAndExtensionClasspath(jreOption);
- //用戶類 F:\..\org\itstack\demo\test\HelloWorld
- parseUserClasspath(cpOption);
- }
- private void bootstrapAndExtensionClasspath(String jreOption) {
- String jreDir = getJreDir(jreOption);
- //..jre/lib/*
- String jreLibPath = Paths.get(jreDir, "lib") + File.separator + "*";
- bootstrapClasspath = new WildcardEntry(jreLibPath);
- //..jre/lib/ext/*
- String jreExtPath = Paths.get(jreDir, "lib", "ext") + File.separator + "*";
- extensionClasspath = new WildcardEntry(jreExtPath);
- }
- private static String getJreDir(String jreOption) {
- if (jreOption != null && Files.exists(Paths.get(jreOption))) {
- return jreOption;
- }
- if (Files.exists(Paths.get("./jre"))) {
- return "./jre";
- }
- String jh = System.getenv("JAVA_HOME");
- if (jh != null) {
- return Paths.get(jh, "jre").toString();
- }
- throw new RuntimeException("Can not find JRE folder!");
- }
- private void parseUserClasspath(String cpOption) {
- if (cpOption == null) {
- cpOption = ".";
- }
- userClasspath = Entry.create(cpOption);
- }
- public byte[] readClass(String className) throws Exception {
- className = className + ".class";
- //[readClass]啟動類路徑
- try {
- return bootstrapClasspath.readClass(className);
- } catch (Exception ignored) {
- //ignored
- }
- //[readClass]擴(kuò)展類路徑
- try {
- return extensionClasspath.readClass(className);
- } catch (Exception ignored) {
- //ignored
- }
- //[readClass]用戶類路徑
- return userClasspath.readClass(className);
- }
- }
- 啟動類路徑,bootstrapClasspath.readClass(className);
- 擴(kuò)展類路徑,extensionClasspath.readClass(className);
- 用戶類路徑,userClasspath.readClass(className);
- 這回就看到它們具體在哪使用了吧!有了具體的代碼也就方便理解了
2.7 加載類測試驗證
- private static void startJVM(Cmd cmd) {
- Classpath cp = new Classpath(cmd.jre, cmd.classpath);
- System.out.printf("classpath:%s class:%s args:%s\n", cp, cmd.getMainClass(), cmd.getAppArgs());
- //獲取className
- String className = cmd.getMainClass().replace(".", "/");
- try {
- byte[] classData = cp.readClass(className);
- System.out.println(Arrays.toString(classData));
- } catch (Exception e) {
- System.out.println("Could not find or load main class " + cmd.getMainClass());
- e.printStackTrace();
- }
- }
這段就是使用 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é)果」
- [-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é)碼
- //取部分字節(jié)碼:java.lang.String
- private static byte[] classData = {
- -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,
- 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,
- 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, 66, 1, 0, 4, 40, 41, 91, 67, 1, 0, 4, 40, 67, 41, 67, 1, 0, 21, 40, 68, 41, 76,
- 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”開頭
- private static void readAndCheckMagic() {
- System.out.println("\r\n------------ 校驗?zāi)?shù) ------------");
- //從class字節(jié)碼中讀取前四位
- byte[] magic_byte = new byte[4];
- System.arraycopy(classData, 0, magic_byte, 0, 4);
- //將4位byte字節(jié)轉(zhuǎn)成16進(jìn)制字符串
- String magic_hex_str = new BigInteger(1, magic_byte).toString(16);
- System.out.println("magic_hex_str:" + magic_hex_str);
- //byte_magic_str 是16進(jìn)制的字符串,cafebabe,因為java中沒有無符號整型,所以如果想要無符號只能放到更高位中
- long magic_unsigned_int32 = Long.parseLong(magic_hex_str, 16);
- System.out.println("magic_unsigned_int32:" + magic_unsigned_int32);
- //魔數(shù)比對,一種通過字符串比對,另外一種使用假設(shè)的無符號16進(jìn)制比較。如果使用無符號比較需要將0xCAFEBABE & 0x0FFFFFFFFL與運(yùn)算
- System.out.println("0xCAFEBABE & 0x0FFFFFFFFL:" + (0xCAFEBABE & 0x0FFFFFFFFL));
- if (magic_unsigned_int32 == (0xCAFEBABE & 0x0FFFFFFFFL)) {
- System.out.println("class字節(jié)碼魔數(shù)無符號16進(jìn)制數(shù)值一致校驗通過");
- } else {
- System.out.println("class字節(jié)碼魔數(shù)無符號16進(jìn)制數(shù)值一致校驗拒絕");
- }
- }
- 讀取字節(jié)碼中的前四位,-54, -2, -70, -66,將這四位轉(zhuǎn)換為16進(jìn)制。
- 因為 java 中是沒有無符號整型的,所以只能用更高位存放。
- 解析后就是魔數(shù)的對比,看是否與 CAFEBABE 一致。
「測試結(jié)果」
- ------------ 校驗?zāi)?shù) ------------
- magic_hex_str:cafebabe
- magic_unsigned_int32:3405691582
- 0xCAFEBABE & 0x0FFFFFFFFL:3405691582
- 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}
- private static void readAndCheckVersion() {
- System.out.println("\r\n------------ 校驗版本號 ------------");
- //從class字節(jié)碼第4位開始讀取,讀取2位
- byte[] minor_byte = new byte[2];
- System.arraycopy(classData, 4, minor_byte, 0, 2);
- //將2位byte字節(jié)轉(zhuǎn)成16進(jìn)制字符串
- String minor_hex_str = new BigInteger(1, minor_byte).toString(16);
- System.out.println("minor_hex_str:" + minor_hex_str);
- //minor_unsigned_int32 轉(zhuǎn)成無符號16進(jìn)制
- int minor_unsigned_int32 = Integer.parseInt(minor_hex_str, 16);
- System.out.println("minor_unsigned_int32:" + minor_unsigned_int32);
- //從class字節(jié)碼第6位開始讀取,讀取2位
- byte[] major_byte = new byte[2];
- System.arraycopy(classData, 6, major_byte, 0, 2);
- //將2位byte字節(jié)轉(zhuǎn)成16進(jìn)制字符串
- String major_hex_str = new BigInteger(1, major_byte).toString(16);
- System.out.println("major_hex_str:" + major_hex_str);
- //major_unsigned_int32 轉(zhuǎn)成無符號16進(jìn)制
- int major_unsigned_int32 = Integer.parseInt(major_hex_str, 16);
- System.out.println("major_unsigned_int32:" + major_unsigned_int32);
- 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é)果」
- ------------ 校驗版本號 ------------
- minor_hex_str:0
- minor_unsigned_int32:0
- major_hex_str:34
- major_unsigned_int32:52
- 版本號:52.0
4. 解析全部內(nèi)容對照
按照 JVM 的加載過程,其實遠(yuǎn)不止魔數(shù)和版本號信息,還有很多其他內(nèi)容,這里我們可以把測試結(jié)果展示出來,方便大家有一個學(xué)習(xí)結(jié)果的比對印象。
- classpath:org.itstack.demo.jvm.classpath.Classpath@4bf558aa class:java.lang.String args:null
- version: 52.0
- constants count:540
- access flags:0x31
- this class:java/lang/String
- super class:java/lang/Object
- interfaces:[java/io/Serializable, java/lang/Comparable, java/lang/CharSequence]
- fields count:5
- value [C
- hash I
- serialVersionUID J
- serialPersistentFields [Ljava/io/ObjectStreamField;
- CASE_INSENSITIVE_ORDER Ljava/util/Comparator;
- methods count: 94
- <init> ()V
- <init> (Ljava/lang/String;)V
- <init> ([C)V
- <init> ([CII)V
- <init> ([III)V
- <init> ([BIII)V
- <init> ([BI)V
- checkBounds ([BII)V
- <init> ([BIILjava/lang/String;)V
- <init> ([BIILjava/nio/charset/Charset;)V
- <init> ([BLjava/lang/String;)V
- <init> ([BLjava/nio/charset/Charset;)V
- <init> ([BII)V
- <init> ([B)V
- <init> (Ljava/lang/StringBuffer;)V
- <init> (Ljava/lang/StringBuilder;)V
- <init> ([CZ)V
- length ()I
- isEmpty ()Z
- charAt (I)C
- codePointAt (I)I
- codePointBefore (I)I
- codePointCount (II)I
- offsetByCodePoints (II)I
- getChars ([CI)V
- getChars (II[CI)V
- getBytes (II[BI)V
- getBytes (Ljava/lang/String;)[B
- getBytes (Ljava/nio/charset/Charset;)[B
- getBytes ()[B
- equals (Ljava/lang/Object;)Z
- contentEquals (Ljava/lang/StringBuffer;)Z
- nonSyncContentEquals (Ljava/lang/AbstractStringBuilder;)Z
- contentEquals (Ljava/lang/CharSequence;)Z
- equalsIgnoreCase (Ljava/lang/String;)Z
- compareTo (Ljava/lang/String;)I
- compareToIgnoreCase (Ljava/lang/String;)I
- regionMatches (ILjava/lang/String;II)Z
- regionMatches (ZILjava/lang/String;II)Z
- startsWith (Ljava/lang/String;I)Z
- startsWith (Ljava/lang/String;)Z
- endsWith (Ljava/lang/String;)Z
- hashCode ()I
- indexOf (I)I
- indexOf (II)I
- indexOfSupplementary (II)I
- lastIndexOf (I)I
- lastIndexOf (II)I
- lastIndexOfSupplementary (II)I
- indexOf (Ljava/lang/String;)I
- indexOf (Ljava/lang/String;I)I
- indexOf ([CIILjava/lang/String;I)I
- indexOf ([CII[CIII)I
- lastIndexOf (Ljava/lang/String;)I
- lastIndexOf (Ljava/lang/String;I)I
- lastIndexOf ([CIILjava/lang/String;I)I
- lastIndexOf ([CII[CIII)I
- substring (I)Ljava/lang/String;
- substring (II)Ljava/lang/String;
- subSequence (II)Ljava/lang/CharSequence;
- concat (Ljava/lang/String;)Ljava/lang/String;
- replace (CC)Ljava/lang/String;
- matches (Ljava/lang/String;)Z
- contains (Ljava/lang/CharSequence;)Z
- replaceFirst (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
- replaceAll (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
- replace (Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;
- split (Ljava/lang/String;I)[Ljava/lang/String;
- split (Ljava/lang/String;)[Ljava/lang/String;
- join (Ljava/lang/CharSequence;[Ljava/lang/CharSequence;)Ljava/lang/String;
- join (Ljava/lang/CharSequence;Ljava/lang/Iterable;)Ljava/lang/String;
- toLowerCase (Ljava/util/Locale;)Ljava/lang/String;
- toLowerCase ()Ljava/lang/String;
- toUpperCase (Ljava/util/Locale;)Ljava/lang/String;
- toUpperCase ()Ljava/lang/String;
- trim ()Ljava/lang/String;
- toString ()Ljava/lang/String;
- toCharArray ()[C
- format (Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
- format (Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
- valueOf (Ljava/lang/Object;)Ljava/lang/String;
- valueOf ([C)Ljava/lang/String;
- valueOf ([CII)Ljava/lang/String;
- copyValueOf ([CII)Ljava/lang/String;
- copyValueOf ([C)Ljava/lang/String;
- valueOf (Z)Ljava/lang/String;
- valueOf (C)Ljava/lang/String;
- valueOf (I)Ljava/lang/String;
- valueOf (J)Ljava/lang/String;
- valueOf (F)Ljava/lang/String;
- valueOf (D)Ljava/lang/String;
- intern ()Ljava/lang/String;
- compareTo (Ljava/lang/Object;)I
- <clinit> ()V
- 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),感謝大家的支持!