java解析dxf文件_浅析JVM方法解析、创建和链接
一:前言
上周末寫了一篇文章《你知道Java類是如何被加載的嗎?》,分析了HotSpot是如何加載Java類的,干脆趁熱打鐵,本周末再來分析下Hotspot又是如何解析、創建和鏈接類方法的。
二:Class文件中的Java方法
Java類在編譯后會被編譯成 Class 文件,在幾年前寫的《Jvm之用java解析class文件》中,我對 Class 文件的結構進行了分析,里面已經講過了Java 方法在 Class 文件中的結構,今天就再溫故而知新下。
先來看下 Class 文件的結構:
ClassFile- methods_count 記錄了 Class 文件中一共有多少方法。
- methods 是個數組,包含 Class 文件的所有方法。
methods 的數組類型為 method_info,每個 method_info 對應一個 Java 方法。
method_info {u2 access_flags;u2 name_index;u2 descriptor_index;u2 attributes_count;attribute_info attributes[attributes_count]; }- access_flags 是方法的訪問權限。
- name_index 是方法名在常量池中的索引。
- descriptor_index 是方法描述符在常量池中的索引。
- attributes_count 記錄了方法一共有多少屬性。
- attributes是個數組,包含了方法的所有屬性。
attributes 中的每一項都是方法的一個屬性,其中代表字節碼的屬性為 Code_attribute:
Code_attribute {u2 attribute_name_index; u4 attribute_length;u2 max_stack;u2 max_locals;u4 code_length;u1 code[code_length];u2 exception_table_length; {u2 start_pc;u2 end_pc;u2 handler_pc;u2 catch_type;} exception_table[exception_table_length];u2 attributes_count;attribute_info attributes[attributes_count]; }- max_stack 表示當前方法操作數棧的最大深度。
- max_locals 表示當前方法局部變量的最大個數。
- code[code_length] 記錄了方法中的字節碼指令
總的來說,Class 文件中對方法的描述還是很簡潔清晰的。
三:HotSpot 如何解析 Java 方法
Class 文件相當于 Java 類的模板,JVM 在讀取 Class 文件后,會根據這個模板,建立 Java 類在虛擬機中的模型。
在上篇文章《你知道Java類是如何被加載的嗎?》中,我提到了 ClassFileParser,它是HotSpot 加載類所需要的一員大將,通過名字我們就能猜出它的作用:類文件解析器。
還記得Class在JVM中對應的 InstanceKlass 是如何創建的嗎?不記得話看一下下面這段代碼來回憶下。
ClassFileParser上面這段代碼主要是創建了一個ClassFileParser,并調用了其create_instance_klass()來創建 InstanceKlass。但是對于Class文件的解析,是在 create_instance_klass()之前就完成了的。當經過一系列初始化操作后,ClassFileParser 便在其構造函數的末尾,調用 parse_stream(stream, CHECK),開始了 Class 文件的解析之旅。
在 parse_stream()中,ClassFileParser 會對整個Class文件解析解析,包括常量池、字段、父類、接口等信息,當然也包括類方法。用來解析所有類方法的函數為:parse_methods()。
voidClassFileParser 對于 Class文件的解析是流式的,parse_methods()先通過 cfs->get_u2_fast() 拿到方法數量,接著便開始進行遍歷,調用 parse_method()依次解析每個類方法。
從Class文件中method_info的定義可知,method 基本上所有信息都存儲在method_info中的attributes[] 數組中,所以對于method的解析,基本上也就是在遍歷attributes[] 數組。
method_info 中的attributes[] 數組是用來存放方法的各個屬性的,其中包括Code屬性、Exception屬性、MethodParameters屬性、Synthetic屬性。parse_method()要做的主要工作,就是遍歷attributes[] 數組,解析每個屬性。
下面我們來便來各個擊破,看看上面這些屬性是如何被解析的。
3.1 解析 Code 屬性
(1)獲取maxStacks、maxLocals 和 code length
if(2)獲取字節碼指令首地址
code_start(3)解析方法中的異常處理表
exception_table_length(4)解析Code屬性中的屬性表,如 LineNumberTables、LocalVariableTables、LocalVariableTypeTables。主要是用于記錄一些調試信息。
3.2 解析 Exception 屬性
Exception 屬性記錄了方法可能拋出的異常。
checked_exceptions_start3.3 解析 MethodParameters 屬性
MethodParameters 屬性記錄了方法的參數信息。
method_parameters_seen3.4 解析 Synthetic 屬性
Synthetic 屬性表示成員是在編譯期自動為Class生成,如內部類提供給外部類用來訪問內部成員的 access()方法。
access_flags如果在解析到該屬性,直接調用 set_is_synthetic()標志下即可。
由上面的解析過程可知,ClassFileParser 主要就是按照Java虛擬機規范對Class文件結構的定義進行流式解析。
四:HotSpot 如何創建 Java 方法
經過第三節的解析,ClassFileParser 已經從Class文件中獲取到了方法的所有信息。接下來要做的,便是通過讀取的信息,創建 Java 方法在 JVM 中的數據模型。
在HotSpot中,Java方法對應的數據結構為 Method,定義在 method.hpp 中
class創建 Method 主要分為下面幾步。
4.1 分配方法對應的 Method
Method4.2 將解析方法時讀取到信息填充到 Method 中
m->set_constants(_cp); m->set_name_index(name_index); m->set_signature_index(signature_index); ...... // Fill in code attribute information m->set_max_stack(max_stack); m->set_max_locals(max_locals); ...... // Copy byte codes m->set_code((u1*)code_start); ...... // Copy exception table if (exception_table_length > 0) {Copy::conjoint_swap_if_needed<Endian::JAVA>(exception_table_start,m->exception_table_start(),exception_table_length * sizeof(ExceptionTableElement),sizeof(u2)); } ......Method 中將一些只讀數據都存放到了它的 _constMethod 中,_constMethod 類型為ConstMethod,定義在 constMethod.hpp 中。
舉個例子,方法的字節碼指令就存放在 ConstMethod 中,不過這么說不太嚴謹,字節碼指令并不是直接存放在 ConstMethod 內部,而是緊跟著 ConstMethod 存放在內存中。
我們再看看上面的填充邏輯,調用了 m->set_code((u1*)code_start) 來存放字節碼指令首地址,Method 其實是直接調用了 ConstMethod 的 set_code():
voidConstMethod的set_code()也很簡單:
void首先調用code_base()獲取存放字節碼的地址,接著便調用memcpy(),將字節碼指令從 code 處拷貝到code_base()處。
code_base()代碼如下:
address因為 this 本身是指針,所以 this + 1 獲取的地址為:
constMethod 首地址 + sizeOf(ConstMethod)所以字節碼指令存放在ConstMethod之后:
存放好字節碼指令后,以后當調用該方法時,就可以從 ConstMethod 中獲取到字節碼指令首地址,從而進行取指執行了。
五:HotSpot 如何鏈接 Java 方法
上面只是在加載Class文件時對Java方法進行了解析和創建,而Java 方法的鏈接是發生在所屬InstanceKlass 的初始化時期。
一般來說,Class文件在被加載成 InstanceKlass 后不會立即初始化,而是等到實例化 Obejct、反射獲取字段、方法信息,或者調用static方法等時機才會初始化。
InstanceKlass在初始化時會調用 link_class()對類進行鏈接,在類的鏈接過程中,便會調用 InstanceKlass的 link_methods()方法,對類的所有方法進行鏈接。
對單個 Method 進行鏈接的方法為:Method::link_method(const methodHandle& h_method, TRAPS),方法鏈接主要就是做的事就是設置 Method 的 interpreter_entry:
address上面首先通過 entry_for_method(h_method)獲取方法的入口例程,關于這個例程是干什么的,可以看看之前寫的《JVM方法執行的來龍去脈》,簡單來說,HotSpot對于Java方法的執行不是簡單的從方法字節碼首地址處進行取指執行即可,在進行字節碼指令執行之前,需要為Java方法創建棧幀、局部變量表等事情,而這些事情是通用的,所以HotSpot將這些事情統一到一起,對Java方法的執行做了一層封裝,而例程便是這個封裝的入口。
HotSpot 提前為各種類型的方法創建好了一系列例程,所以 entry_for_method(h_method)便是根據方法類型,從例程表中查詢到對應類型的例程。
查詢到例程后,便調用set_interpreter_entry(entry),將例程的入口地址保存到Method中:
void保存例程的入口地址后,以后調用Java方法時,便可以從Method中獲取例程的入口地址,跳到此處執行。
六:總結
通過上面的分析,我們了解了Java方法在Class文件中的結構,以及方法的解析、創建及鏈接。
解析過程主要是流式讀取Class文件,獲取方法在Class文件中的信息。
創建過程主要是創建Java方法對應的Method,并將解析過程讀取的信息填充到Method中。
鏈接過程主要是根據方法類型,獲取并保存方法對應的入口例程的地址。
我的文章只是個引子,畢竟短短篇幅無法囊括JVM浩瀚如煙的源碼。如果想對 HotSpot 如何處理 Java 方法的細節深入了解的話,想必最好的方式還是自己去閱讀和調試 OpenJDK,
罷了,寫完,關燈,睡覺!
聽說喜歡點關注的同學都長得帥
總結
以上是生活随笔為你收集整理的java解析dxf文件_浅析JVM方法解析、创建和链接的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三面玻璃视野绝了!鑫谷斜杠青年mini机
- 下一篇: 由于在客户端检测到一个协议错误_HTTP