久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

JVM內(nèi)幕:Java虛擬機(jī)詳解 – 碼農(nóng)網(wǎng)

 YanYMLu 2016-06-10

這篇文章解釋了Java 虛擬機(jī)(JVM)的內(nèi)部架構(gòu)。下圖顯示了遵守 Java SE 7 規(guī)范的典型的 JVM 核心內(nèi)部組件,。

JVM內(nèi)幕:Java虛擬機(jī)詳解

上圖顯示的組件分兩個(gè)章節(jié)解釋,。第一章討論針對每個(gè)線程創(chuàng)建的組件,第二章節(jié)討論了線程無關(guān)組件,。

  • 線程
    • JVM 系統(tǒng)線程
    • 每個(gè)線程相關(guān)的
    • 程序計(jì)數(shù)器
    • 本地棧
    • 棧限制
    • 棧幀
    • 局部變量數(shù)組
    • 操作數(shù)棧
    • 動(dòng)態(tài)鏈接
  • 線程共享
    • 內(nèi)存管理
    • 非堆內(nèi)存
    • 即時(shí)編譯
    • 方法區(qū)
    • 類文件結(jié)構(gòu)
    • 類加載器
    • 更快的類加載
    • 方法區(qū)在哪里
    • 類加載器參考
    • 運(yùn)行時(shí)常量池
    • 異常表
    • 符號表
    • Interned 字符串

線程

這里所說的線程指程序執(zhí)行過程中的一個(gè)線程實(shí)體,。JVM 允許一個(gè)應(yīng)用并發(fā)執(zhí)行多個(gè)線程,。Hotspot JVM 中的 Java 線程與原生操作系統(tǒng)線程有直接的映射關(guān)系。當(dāng)線程本地存儲(chǔ),、緩沖區(qū)分配,、同步對象,、棧,、程序計(jì)數(shù)器等準(zhǔn)備好以后,,就會(huì)創(chuàng)建一個(gè)操作系統(tǒng)原生線程,。Java 線程結(jié)束,,原生線程隨之被回收,。操作系統(tǒng)負(fù)責(zé)調(diào)度所有線程,并把它們分配到任何可用的 CPU 上,。當(dāng)原生線程初始化完畢,,就會(huì)調(diào)用 Java 線程的 run() 方法。run() 返回時(shí),,被處理未捕獲異常,,原生線程將確認(rèn)由于它的結(jié)束是否要終止 JVM 進(jìn)程(比如這個(gè)線程是最后一個(gè)非守護(hù)線程)。當(dāng)線程結(jié)束時(shí),,會(huì)釋放原生線程和 Java 線程的所有資源,。

JVM 系統(tǒng)線程

如果使用 jconsole 或者其它調(diào)試器,你會(huì)看到很多線程在后臺(tái)運(yùn)行,。這些后臺(tái)線程與觸發(fā) public static void main(String[]) 函數(shù)的主線程以及主線程創(chuàng)建的其他線程一起運(yùn)行,。Hotspot JVM 后臺(tái)運(yùn)行的系統(tǒng)線程主要有下面幾個(gè):

虛擬機(jī)線程(VM thread)這個(gè)線程等待 JVM 到達(dá)安全點(diǎn)操作出現(xiàn)。這些操作必須要在獨(dú)立的線程里執(zhí)行,因?yàn)楫?dāng)堆修改無法進(jìn)行時(shí),,線程都需要 JVM 位于安全點(diǎn),。這些操作的類型有:stop-the-world 垃圾回收、線程棧 dump,、線程暫停,、線程偏向鎖(biased locking)解除。
周期性任務(wù)線程這線程負(fù)責(zé)定時(shí)器事件(也就是中斷),,用來調(diào)度周期性操作的執(zhí)行。
GC 線程這些線程支持 JVM 中不同的垃圾回收活動(dòng),。
編譯器線程這些線程在運(yùn)行時(shí)將字節(jié)碼動(dòng)態(tài)編譯成本地平臺(tái)相關(guān)的機(jī)器碼。
信號分發(fā)線程這個(gè)線程接收發(fā)送到 JVM 的信號并調(diào)用適當(dāng)?shù)?JVM 方法處理,。

線程相關(guān)組件

每個(gè)運(yùn)行的線程都包含下面這些組件:

程序計(jì)數(shù)器(PC)

PC 指當(dāng)前指令(或操作碼)的地址,,本地指令除外,。如果當(dāng)前方法是 native 方法,,那么PC 的值為 undefined。所有的 CPU 都有一個(gè) PC,,典型狀態(tài)下,,每執(zhí)行一條指令 PC 都會(huì)自增,因此 PC 存儲(chǔ)了指向下一條要被執(zhí)行的指令地址,。JVM 用 PC 來跟蹤指令執(zhí)行的位置,,PC 將實(shí)際上是指向方法區(qū)(Method Area)的一個(gè)內(nèi)存地址。

棧(Stack)

每個(gè)線程擁有自己的棧,,棧包含每個(gè)方法執(zhí)行的棧幀,。棧是一個(gè)后進(jìn)先出(LIFO)的數(shù)據(jù)結(jié)構(gòu),因此當(dāng)前執(zhí)行的方法在棧的頂部,。每次方法調(diào)用時(shí),,一個(gè)新的棧幀創(chuàng)建并壓棧到棧頂。當(dāng)方法正常返回或拋出未捕獲的異常時(shí),,棧幀就會(huì)出棧,。除了棧幀的壓棧和出棧,棧不能被直接操作,。所以可以在堆上分配棧幀,,并且不需要連續(xù)內(nèi)存,。

Native棧

并非所有的 JVM 實(shí)現(xiàn)都支持本地(native)方法,,那些提供支持的 JVM 一般都會(huì)為每個(gè)線程創(chuàng)建本地方法棧。如果 JVM 用 C-linkage 模型實(shí)現(xiàn) JNI(Java Native Invocation),那么本地棧就是一個(gè) C 的棧,。在這種情況下,,本地方法棧的參數(shù)順序、返回值和典型的 C 程序相同,。本地方法一般來說可以(依賴 JVM 的實(shí)現(xiàn))反過來調(diào)用 JVM 中的 Java 方法,。這種 native 方法調(diào)用 Java 會(huì)發(fā)生在棧(一般是 Java 棧)上;線程將離開本地方法棧,,并在 Java 棧上開辟一個(gè)新的棧幀。

棧的限制

??梢允莿?dòng)態(tài)分配也可以固定大小,。如果線程請求一個(gè)超過允許范圍的空間,,就會(huì)拋出一個(gè)StackOverflowError,。如果線程需要一個(gè)新的棧幀,,但是沒有足夠的內(nèi)存可以分配,就會(huì)拋出一個(gè) OutOfMemoryError,。

棧幀(Frame)

每次方法調(diào)用都會(huì)新建一個(gè)新的棧幀并把它壓棧到棧頂,。當(dāng)方法正常返回或者調(diào)用過程中拋出未捕獲的異常時(shí),棧幀將出棧,。更多關(guān)于異常處理的細(xì)節(jié),,可以參考下面的異常信息表章節(jié)。

每個(gè)棧幀包含:

  • 局部變量數(shù)組
  • 返回值
  • 操作數(shù)棧
  • 類當(dāng)前方法的運(yùn)行時(shí)常量池引用

局部變量數(shù)組

局部變量數(shù)組包含了方法執(zhí)行過程中的所有變量,,包括 this 引用,、所有方法參數(shù)、其他局部變量,。對于類方法(也就是靜態(tài)方法),,方法參數(shù)從下標(biāo) 0 開始,對于對象方法,,位置0保留為 this,。

有下面這些局部變量:

  • boolean
  • byte
  • char
  • long
  • short
  • int
  • float
  • double
  • reference
  • returnAddress

除了 long 和 double 類型以外,所有的變量類型都占用局部變量數(shù)組的一個(gè)位置,。long 和 double 需要占用局部變量數(shù)組兩個(gè)連續(xù)的位置,,因?yàn)樗鼈兪?64 位雙精度,其它類型都是 32 位單精度,。

操作數(shù)棧

操作數(shù)棧在執(zhí)行字節(jié)碼指令過程中被用到,,這種方式類似于原生 CPU 寄存器,。大部分 JVM 字節(jié)碼把時(shí)間花費(fèi)在操作數(shù)棧的操作上:入棧、出棧,、復(fù)制,、交換、產(chǎn)生消費(fèi)變量的操作,。因此,,局部變量數(shù)組和操作數(shù)棧之間的交換變量指令操作通過字節(jié)碼頻繁執(zhí)行。比如,,一個(gè)簡單的變量初始化語句將產(chǎn)生兩條跟操作數(shù)棧交互的字節(jié)碼,。

int i;

被編譯成下面的字節(jié)碼:

0: iconst_0 // Push 0 to top of the operand stack1: istore_1 // Pop value from top of operand stack and store as local variable 1

更多關(guān)于局部變量數(shù)組、操作數(shù)棧和運(yùn)行時(shí)常量池之間交互的詳細(xì)信息,,可以在類文件結(jié)構(gòu)部分找到,。

動(dòng)態(tài)鏈接

每個(gè)棧幀都有一個(gè)運(yùn)行時(shí)常量池的引用。這個(gè)引用指向棧幀當(dāng)前運(yùn)行方法所在類的常量池,。通過這個(gè)引用支持動(dòng)態(tài)鏈接(dynamic linking),。

C/C++ 代碼一般被編譯成對象文件,然后多個(gè)對象文件被鏈接到一起產(chǎn)生可執(zhí)行文件或者 dll,。在鏈接階段,,每個(gè)對象文件的符號引用被替換成了最終執(zhí)行文件的相對偏移內(nèi)存地址。在 Java中,,鏈接階段是運(yùn)行時(shí)動(dòng)態(tài)完成的,。

當(dāng) Java 類文件編譯時(shí),所有變量和方法的引用都被當(dāng)做符號引用存儲(chǔ)在這個(gè)類的常量池中,。符號引用是一個(gè)邏輯引用,,實(shí)際上并不指向物理內(nèi)存地址。JVM 可以選擇符號引用解析的時(shí)機(jī),,一種是當(dāng)類文件加載并校驗(yàn)通過后,,這種解析方式被稱為饑餓方式。另外一種是符號引用在第一次使用的時(shí)候被解析,,這種解析方式稱為惰性方式,。無論如何 ,JVM 必須要在第一次使用符號引用時(shí)完成解析并拋出可能發(fā)生的解析錯(cuò)誤,。綁定是將對象域,、方法、類的符號引用替換為直接引用的過程,。綁定只會(huì)發(fā)生一次,。一旦綁定,符號引用會(huì)被完全替換,。如果一個(gè)類的符號引用還沒有被解析,,那么就會(huì)載入這個(gè)類,。每個(gè)直接引用都被存儲(chǔ)為相對于存儲(chǔ)結(jié)構(gòu)(與運(yùn)行時(shí)變量或方法的位置相關(guān)聯(lián)的)偏移量。

線程間共享

堆被用來在運(yùn)行時(shí)分配類實(shí)例,、數(shù)組。不能在棧上存儲(chǔ)數(shù)組和對象,。因?yàn)闂辉O(shè)計(jì)為創(chuàng)建以后無法調(diào)整大小,。棧幀只存儲(chǔ)指向堆中對象或數(shù)組的引用。與局部變量數(shù)組(每個(gè)棧幀中的)中的原始類型和引用類型不同,,對象總是存儲(chǔ)在堆上以便在方法結(jié)束時(shí)不會(huì)被移除,。對象只能由垃圾回收器移除。

為了支持垃圾回收機(jī)制,,堆被分為了下面三個(gè)區(qū)域:

  • 新生代
    • 經(jīng)常被分為 Eden 和 Survivor
  • 老年代
  • 永久代

內(nèi)存管理

對象和數(shù)組永遠(yuǎn)不會(huì)顯式回收,,而是由垃圾回收器自動(dòng)回收。通常,,過程是這樣的:

  1. 新的對象和數(shù)組被創(chuàng)建并放入老年代,。
  2. Minor垃圾回收將發(fā)生在新生代。依舊存活的對象將從 eden 區(qū)移到 survivor 區(qū),。
  3. Major垃圾回收一般會(huì)導(dǎo)致應(yīng)用進(jìn)程暫停,,它將在三個(gè)區(qū)內(nèi)移動(dòng)對象。仍然存活的對象將被從新生代移動(dòng)到老年代,。
  4. 每次進(jìn)行老年代回收時(shí)也會(huì)進(jìn)行永久代回收,。它們之中任何一個(gè)變滿時(shí),都會(huì)進(jìn)行回收,。

非堆內(nèi)存

非堆內(nèi)存指的是那些邏輯上屬于 JVM 一部分對象,,但實(shí)際上不在堆上創(chuàng)建。

非堆內(nèi)存包括:

  • 永久代,,包括:
    • 方法區(qū)
    • 駐留字符串(interned strings)
  • 代碼緩存(Code Cache):用于編譯和存儲(chǔ)那些被 JIT 編譯器編譯成原生代碼的方法,。

即時(shí)編譯(JIT)

Java 字節(jié)碼是解釋執(zhí)行的,但是沒有直接在 JVM 宿主執(zhí)行原生代碼快,。為了提高性能,,Oracle Hotspot 虛擬機(jī)會(huì)找到執(zhí)行最頻繁的字節(jié)碼片段并把它們編譯成原生機(jī)器碼。編譯出的原生機(jī)器碼被存儲(chǔ)在非堆內(nèi)存的代碼緩存中,。通過這種方法,,Hotspot 虛擬機(jī)將權(quán)衡下面兩種時(shí)間消耗:將字節(jié)碼編譯成本地代碼需要的額外時(shí)間和解釋執(zhí)行字節(jié)碼消耗更多的時(shí)間。

方法區(qū)

方法區(qū)存儲(chǔ)了每個(gè)類的信息,,比如:

  • Classloader 引用
  • 運(yùn)行時(shí)常量池
    • 數(shù)值型常量
    • 字段引用
    • 方法引用
    • 屬性
  • 字段數(shù)據(jù)
    • 針對每個(gè)字段的信息
      • 字段名
      • 類型
      • 修飾符
      • 屬性(Attribute)
  • 方法數(shù)據(jù)
    • 每個(gè)方法
      • 方法名
      • 返回值類型
      • 參數(shù)類型(按順序)
      • 修飾符
      • 屬性
  • 方法代碼
    • 每個(gè)方法
      • 字節(jié)碼
      • 操作數(shù)棧大小
      • 局部變量大小
      • 局部變量表
      • 異常表
      • 每個(gè)異常處理器
      • 開始點(diǎn)
      • 結(jié)束點(diǎn)
      • 異常處理代碼的程序計(jì)數(shù)器(PC)偏移量
      • 被捕獲的異常類對應(yīng)的常量池下標(biāo)

所有線程共享同一個(gè)方法區(qū),,因此訪問方法區(qū)數(shù)據(jù)的和動(dòng)態(tài)鏈接的進(jìn)程必須線程安全,。如果兩個(gè)線程試圖訪問一個(gè)還未加載的類的字段或方法,,必須只加載一次,,而且兩個(gè)線程必須等它加載完畢才能繼續(xù)執(zhí)行,。

類文件結(jié)構(gòu)

一個(gè)編譯后的類文件包含下面的結(jié)構(gòu):

ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info contant_pool[constant_pool_count – 1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count];}
Magic, minor_version, major_version類文件的版本信息和用于編譯這個(gè)類的 JDK 版本。
constant_pool類似于符號表,,盡管它包含更多數(shù)據(jù),。下面有更多的詳細(xì)描述。
access_flags提供這個(gè)類的描述符列表,。
this_class提供這個(gè)類全名的常量池(constant_pool)索引,,比如org/jamesdbloom/foo/Bar。
super_class提供這個(gè)類的父類符號引用的常量池索引,。
interfaces指向常量池的索引數(shù)組,,提供那些被實(shí)現(xiàn)的接口的符號引用。
fields提供每個(gè)字段完整描述的常量池索引數(shù)組,。
methods指向constant_pool的索引數(shù)組,,用于表示每個(gè)方法簽名的完整描述。如果這個(gè)方法不是抽象方法也不是 native 方法,,那么就會(huì)顯示這個(gè)函數(shù)的字節(jié)碼,。
attributes不同值的數(shù)組,表示這個(gè)類的附加信息,,包括 RetentionPolicy.CLASS 和 RetentionPolicy.RUNTIME 注解,。

可以用 javap 查看編譯后的 Java Class 文件字節(jié)碼。

如果你編譯下面這個(gè)簡單的類:

package org.jvminternals;public class SimpleClass { public void sayHello() { System.out.println('Hello'); }}

運(yùn)行下面的命令,,就可以得到下面的結(jié)果輸出: javap -v -p -s -sysinfo -constants classes/org/jvminternals/SimpleClass.class,。

public class org.jvminternals.SimpleClass SourceFile: 'SimpleClass.java' minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Methodref #6.#17 // java/lang/Object.'':()V #2 = Fieldref #18.#19 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #20 // 'Hello' #4 = Methodref #21.#22 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #23 // org/jvminternals/SimpleClass #6 = Class #24 // java/lang/Object #7 = Utf8 #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lorg/jvminternals/SimpleClass; #14 = Utf8 sayHello #15 = Utf8 SourceFile #16 = Utf8 SimpleClass.java #17 = NameAndType #7:#8 // '':()V #18 = Class #25 // java/lang/System #19 = NameAndType #26:#27 // out:Ljava/io/PrintStream; #20 = Utf8 Hello #21 = Class #28 // java/io/PrintStream #22 = NameAndType #29:#30 // println:(Ljava/lang/String;)V #23 = Utf8 org/jvminternals/SimpleClass #24 = Utf8 java/lang/Object #25 = Utf8 java/lang/System #26 = Utf8 out #27 = Utf8 Ljava/io/PrintStream; #28 = Utf8 java/io/PrintStream #29 = Utf8 println #30 = Utf8 (Ljava/lang/String;)V{ public org.jvminternals.SimpleClass(); Signature: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object.'':()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lorg/jvminternals/SimpleClass; public void sayHello(); Signature: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String 'Hello' 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 6: 0 line 7: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Lorg/jvminternals/SimpleClass;}

這個(gè) class 文件展示了三個(gè)主要部分:常量池、構(gòu)造器方法和 sayHello 方法,。

  • 常量池:提供了通常由符號表提供的相同信息,,詳細(xì)描述見下文。
  • 方法:每一個(gè)方法包含四個(gè)區(qū)域,,
    • 簽名和訪問標(biāo)簽
    • 字節(jié)碼
    • LineNumberTable:為調(diào)試器提供源碼中的每一行對應(yīng)的字節(jié)碼信息,。上面的例子中,Java 源碼里的第 6 行與 sayHello 函數(shù)字節(jié)碼序號 0 相關(guān),,第 7 行與字節(jié)碼序號 8 相關(guān),。
    • LocalVariableTable:列出了所有棧幀中的局部變量。上面兩個(gè)例子中,,唯一的局部變量就是 this,。

這個(gè) class 文件用到下面這些字節(jié)碼操作符:

aload0這個(gè)操作碼是aload格式操作碼中的一個(gè)。它們用來把對象引用加載到操作碼棧,。 表示正在被訪問的局部變量數(shù)組的位置,,但只能是0,、1、2,、3 中的一個(gè),。還有一些其它類似的操作碼用來載入非對象引用的數(shù)據(jù),如iload, lload, float 和 dload,。其中 i 表示 int,l 表示 long,,f 表示 float,d 表示 double,。局部變量數(shù)組位置大于 3 的局部變量可以用 iload, lload, float, dload 和 aload 載入。這些操作碼都只需要一個(gè)操作數(shù),,即數(shù)組中的位置
ldc這個(gè)操作碼用來將常量從運(yùn)行時(shí)常量池壓棧到操作數(shù)棧
getstatic這個(gè)操作碼用來把一個(gè)靜態(tài)變量從運(yùn)行時(shí)常量池的靜態(tài)變量列表中壓棧到操作數(shù)棧
invokespecial, invokevirtual這些操作碼屬于一組函數(shù)調(diào)用的操作碼,,包括:invokedynamic、invokeinterface,、invokespecial,、invokestatic、invokevirtual,。在這個(gè) class 文件中,,invokespecial 和 invokevirutal 兩個(gè)指令都用到了,兩者的區(qū)別是,,invokevirutal 指令調(diào)用一個(gè)對象的實(shí)例方法,,invokespecial 指令調(diào)用實(shí)例初始化方法、私有方法,、父類方法,。
return這個(gè)操作碼屬于ireturn、lreturn、freturn,、dreturn,、areturn 和 return 操作碼組,。每個(gè)操作碼返回一種類型的返回值,其中 i 表示 int,,l 表示 long,,f 表示 float,d 表示 double,,a 表示 對象引用,。沒有前綴類型字母的 return 表示返回 void

跟任何典型的字節(jié)碼一樣,操作數(shù)與局部變量,、操作數(shù)棧,、運(yùn)行時(shí)常量池的主要交互如下所示。

構(gòu)造器函數(shù)包含兩個(gè)指令,。首先,,this 變量被壓棧到操作數(shù)棧,然后父類的構(gòu)造器函數(shù)被調(diào)用,,而這個(gè)構(gòu)造器會(huì)消費(fèi) this,,之后 this 被彈出操作數(shù)棧。

JVM內(nèi)幕:Java虛擬機(jī)詳解

sayHello() 方法更加復(fù)雜,,正如之前解釋的那樣,,因?yàn)樗枰眠\(yùn)行時(shí)常量池中的指向符號引用的真實(shí)引用。第一個(gè)操作碼 getstatic 從System類中將out靜態(tài)變量壓到操作數(shù)棧,。下一個(gè)操作碼 ldc 把字符串 “Hello” 壓棧到操作數(shù)棧,。最后 invokevirtual 操作符會(huì)調(diào)用 System.out 變量的 println 方法,從操作數(shù)棧作彈出”Hello” 變量作為 println 的一個(gè)參數(shù),,并在當(dāng)前線程開辟一個(gè)新棧幀,。

JVM內(nèi)幕:Java虛擬機(jī)詳解

類加載器

JVM 啟動(dòng)時(shí)會(huì)用 Bootstrap 類加載器加載一個(gè)初始化類,然后這個(gè)類會(huì)在public static void main(String[])調(diào)用之前完成鏈接和初始化,。執(zhí)行這個(gè)方法會(huì)執(zhí)行加載,、鏈接、初始化需要的額外類和接口。

加載(Loading)是這樣一個(gè)過程,,找到代表這個(gè)類的 class 文件或根據(jù)特定的名字找到接口類型,,然后讀取到一個(gè)字節(jié)數(shù)組中。接著,,這些字節(jié)會(huì)被解析檢驗(yàn)它們是否代表一個(gè) Class 對象并包含正確的 major,、minor 版本信息。直接父類的類和接口也會(huì)被加載進(jìn)來,。這些操作一旦完成,,類或者接口對象就從二進(jìn)制表示中創(chuàng)建出來了。

鏈接(Linking)是校驗(yàn)類或接口并準(zhǔn)備類型和父類父接口的過程,。鏈接過程包含三步:校驗(yàn)(verifying),、準(zhǔn)備(preparing)、部分解析(optionally resolving),。

校驗(yàn)會(huì)確認(rèn)類或者接口表示是否結(jié)構(gòu)正確,,以及是否遵循 Java 語言和 JVM 的語義要求,比如會(huì)進(jìn)行下面的檢查:

  1. 格式一致且格式化正確的符號表
  2. final 方法和類沒有被重載
  3. 方法遵循訪問控制關(guān)鍵詞
  4. 方法參數(shù)的數(shù)量,、類型正確
  5. 字節(jié)碼沒有不當(dāng)?shù)牟僮鳁?shù)據(jù)
  6. 變量在讀取之前被初始化過
  7. 變量值的類型正確

在驗(yàn)證階段做這些檢查意味著不需要在運(yùn)行階段做這些檢查,。鏈接階段的檢查減慢了類加載的速度,,但是它避免了執(zhí)行這些字節(jié)碼時(shí)的多次檢查,。

準(zhǔn)備過程包括為靜態(tài)存儲(chǔ)和 JVM 使用的數(shù)據(jù)結(jié)構(gòu)(比如方法表)分配內(nèi)存空間。靜態(tài)變量創(chuàng)建并初始化為默認(rèn)值,,但是初始化代碼不在這個(gè)階段執(zhí)行,,因?yàn)檫@是初始化過程的一部分。

解析是可選的階段,。它包括通過加載引用的類和接口來檢查這些符號引用是否正確,。如果不是發(fā)生在這個(gè)階段,符號引用的解析要等到字節(jié)碼指令使用這個(gè)引用的時(shí)候才會(huì)進(jìn)行,。

類或者接口初始化由類或接口初始化方法的執(zhí)行組成,。

JVM內(nèi)幕:Java虛擬機(jī)詳解

JVM 中有多個(gè)類加載器,分飾不同的角色,。每個(gè)類加載器由它的父加載器加載,。bootstrap 加載器除外,它是所有最頂層的類加載器,。

  • Bootstrap 加載器一般由本地代碼實(shí)現(xiàn),,因?yàn)樗?JVM 加載以后的早期階段就被初始化了。bootstrap 加載器負(fù)責(zé)載入基礎(chǔ)的 Java API,,比如包含 rt.jar,。它只加載擁有較高信任級別的啟動(dòng)路徑下找到的類,因此跳過了很多普通類需要做的校驗(yàn)工作。
  • Extension 加載器加載了標(biāo)準(zhǔn) Java 擴(kuò)展 API 中的類,,比如 security 的擴(kuò)展函數(shù),。
  • System 加載器是應(yīng)用的默認(rèn)類加載器,比如從 classpath 中加載應(yīng)用類,。
  • 用戶自定義類加載器也可以用來加載應(yīng)用類,。使用自定義的類加載器有很多特殊的原因:運(yùn)行時(shí)重新加載類或者把加載的類分隔為不同的組,典型的用法比如 web 服務(wù)器 Tomcat,。
JVM內(nèi)幕:Java虛擬機(jī)詳解

加速類加載

共享類數(shù)據(jù)(CDS)是Hotspot JVM 5.0 的時(shí)候引入的新特性,。在 JVM 安裝過程中,安裝進(jìn)程會(huì)加載一系列核心 JVM 類(比如 rt.jar)到一個(gè)共享的內(nèi)存映射區(qū)域,。CDS 減少了加載這些類需要的時(shí)間,,提高了 JVM 啟動(dòng)的速度,允許這些類被不同的 JVM 實(shí)例共享,,同時(shí)也減少了內(nèi)存消耗,。

方法區(qū)在哪里

The Java Virtual Machine Specification Java SE 7 Edition 中寫得很清楚:“盡管方法區(qū)邏輯上屬于堆的一部分,簡單的實(shí)現(xiàn)可以選擇不對它進(jìn)行回收和壓縮,?!薄racle JVM 的 jconsle 顯示方法區(qū)和 code cache 區(qū)被當(dāng)做為非堆內(nèi)存,,而 OpenJDK 則顯示 CodeCache 被當(dāng)做 VM 中對象堆(ObjectHeap)的一個(gè)獨(dú)立的域,。

Classloader 引用

所有的類加載之后都包含一個(gè)加載自身的加載器的引用,反過來每個(gè)類加載器都包含它們加載的所有類的引用,。

運(yùn)行時(shí)常量池

JVM 維護(hù)了一個(gè)按類型區(qū)分的常量池,,一個(gè)類似于符號表的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。盡管它包含更多數(shù)據(jù),。Java 字節(jié)碼需要數(shù)據(jù),。這個(gè)數(shù)據(jù)經(jīng)常因?yàn)樘蟛荒苤苯哟鎯?chǔ)在字節(jié)碼中,取而代之的是存儲(chǔ)在常量池中,,字節(jié)碼包含這個(gè)常量池的引用,。運(yùn)行時(shí)常量池被用來上面介紹過的動(dòng)態(tài)鏈接。

常量池中可以存儲(chǔ)多種類型的數(shù)據(jù):

  • 數(shù)字型
  • 字符串型
  • 類引用型
  • 域引用型
  • 方法引用

示例代碼如下:

Object foo = new Object();

寫成字節(jié)碼將是下面這樣:

0: new #2 // Class java/lang/Object 1: dup 2: invokespecial #3 // Method java/ lang/Object ''( ) V

new 操作碼的后面緊跟著操作數(shù) #2 ,。這個(gè)操作數(shù)是常量池的一個(gè)索引,,表示它指向常量池的第二個(gè)實(shí)體。第二個(gè)實(shí)體是一個(gè)類的引用,,這個(gè)實(shí)體反過來引用了另一個(gè)在常量池中包含 UTF8 編碼的字符串類名的實(shí)體(// Class java/lang/Object),。然后,這個(gè)符號引用被用來尋找 java.lang.Object 類,。new 操作碼創(chuàng)建一個(gè)類實(shí)例并初始化變量,。新類實(shí)例的引用則被添加到操作數(shù)棧。dup 操作碼創(chuàng)建一個(gè)操作數(shù)棧頂元素引用的額外拷貝。最后用 invokespecial 來調(diào)用第 2 行的實(shí)例初始化方法,。操作碼也包含一個(gè)指向常量池的引用,。初始化方法把操作數(shù)棧出棧的頂部引用當(dāng)做此方法的一個(gè)參數(shù)。最后這個(gè)新對象只有一個(gè)引用,,這個(gè)對象已經(jīng)完成了創(chuàng)建及初始化,。

如果你編譯下面的類:

package org.jvminternals;public class SimpleClass { public void sayHello() { System.out.println('Hello'); }}

生成的類文件常量池將是這個(gè)樣子:

Constant pool: #1 = Methodref #6.#17 // java/lang/Object.'':()V #2 = Fieldref #18.#19 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #20 // 'Hello' #4 = Methodref #21.#22 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #23 // org/jvminternals/SimpleClass #6 = Class #24 // java/lang/Object #7 = Utf8 #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lorg/jvminternals/SimpleClass; #14 = Utf8 sayHello #15 = Utf8 SourceFile #16 = Utf8 SimpleClass.java #17 = NameAndType #7:#8 // '':()V #18 = Class #25 // java/lang/System #19 = NameAndType #26:#27 // out:Ljava/io/PrintStream; #20 = Utf8 Hello #21 = Class #28 // java/io/PrintStream #22 = NameAndType #29:#30 // println:(Ljava/lang/String;)V #23 = Utf8 org/jvminternals/SimpleClass #24 = Utf8 java/lang/Object #25 = Utf8 java/lang/System #26 = Utf8 out #27 = Utf8 Ljava/io/PrintStream; #28 = Utf8 java/io/PrintStream #29 = Utf8 println #30 = Utf8 (Ljava/lang/String;)V

這個(gè)常量池包含了下面的類型:

Integer4 字節(jié)常量
Long8 字節(jié)常量
Float4 字節(jié)常量
Double8 字節(jié)常量
String字符串常量指向常量池的另外一個(gè)包含真正字節(jié) Utf8 編碼的實(shí)體
Utf8Utf8 編碼的字符序列字節(jié)流
Class一個(gè) Class 常量,指向常量池的另一個(gè) Utf8 實(shí)體,,這個(gè)實(shí)體包含了符合 JVM 內(nèi)部格式的類的全名(動(dòng)態(tài)鏈接過程需要用到)
NameAndType冒號(:)分隔的一組值,,這些值都指向常量池中的其它實(shí)體。第一個(gè)值(“:”之前的)指向一個(gè) Utf8 字符串實(shí)體,,它是一個(gè)方法名或者字段名,。第二個(gè)值指向表示類型的 Utf8 實(shí)體。對于字段類型,,這個(gè)值是類的全名,,對于方法類型,這個(gè)值是每個(gè)參數(shù)類型類的類全名的列表,。
Fieldref, Methodref, InterfaceMethodref點(diǎn)號(.)分隔的一組值,,每個(gè)值都指向常量池中的其它的實(shí)體。第一個(gè)值(“.”號之前的)指向類實(shí)體,,第二個(gè)值指向 NameAndType 實(shí)體,。

異常表

異常表像這樣存儲(chǔ)每個(gè)異常處理信息:

  • 起始點(diǎn)(Start point)
  • 結(jié)束點(diǎn)(End point)
  • 異常處理代碼的 PC 偏移量
  • 被捕獲異常的常量池索引

如果一個(gè)方法有定義 try-catch 或者 try-finally 異常處理器,那么就會(huì)創(chuàng)建一個(gè)異常表,。它為每個(gè)異常處理器和 finally 代碼塊存儲(chǔ)必要的信息,,包括處理器覆蓋的代碼塊區(qū)域和處理異常的類型,。

當(dāng)方法拋出異常時(shí),,JVM 會(huì)尋找匹配的異常處理器。如果沒有找到,,那么方法會(huì)立即結(jié)束并彈出當(dāng)前棧幀,,這個(gè)異常會(huì)被重新拋到調(diào)用這個(gè)方法的方法中(在新的棧幀中)。如果所有的棧幀都被彈出還沒有找到匹配的異常處理器,,那么這個(gè)線程就會(huì)終止,。如果這個(gè)異常在最后一個(gè)非守護(hù)進(jìn)程拋出(比如這個(gè)線程是主線程),那么也有會(huì)導(dǎo)致 JVM 進(jìn)程終止,。

Finally 異常處理器匹配所有的異常類型,,且不管什么異常拋出 finally 代碼塊都會(huì)執(zhí)行。在這種情況下,,當(dāng)沒有異常拋出時(shí),,finally 代碼塊還是會(huì)在方法最后執(zhí)行。這種靠在代碼 return 之前跳轉(zhuǎn)到 finally 代碼塊來實(shí)現(xiàn)。

符號表

除了按類型來分的運(yùn)行時(shí)常量池,,Hotspot JVM 在永久代還包含一個(gè)符號表,。這個(gè)符號表是一個(gè)哈希表,保存了符號指針到符號的映射關(guān)系(也就是 Hashtable),,它擁有指向所有符號(包括在每個(gè)類運(yùn)行時(shí)常量池中的符號)的指針,。

引用計(jì)數(shù)被用來控制一個(gè)符號從符號表從移除的過程。比如當(dāng)一個(gè)類被卸載時(shí),,它擁有的在常量池中所有符號的引用計(jì)數(shù)將減少,。當(dāng)符號表中的符號引用計(jì)數(shù)為 0 時(shí),符號表會(huì)認(rèn)為這個(gè)符號不再被引用,,將從符號表中卸載,。符號表和后面介紹的字符串表都被保存在一個(gè)規(guī)范化的結(jié)構(gòu)中,以便提高效率并保證每個(gè)實(shí)例只出現(xiàn)一次,。

字符串表

Java 語言規(guī)范要求相同的(即包含相同序列的 Unicode 指針序列)字符串字面量必須指向相同的 String 實(shí)例,。除此之外,在一個(gè)字符串實(shí)例上調(diào)用 String.intern() 方法的返回引用必須與字符串是字面量時(shí)的一樣,。因此,,下面的代碼返回 true:

('j' + 'v' + 'm').intern() == 'jvm'

Hotspot JVM 中 interned 字符串保存在字符串表中。字符串表是一個(gè)哈希表,,保存著對象指針到符號的映射關(guān)系(也就是Hashtable),,它被保存到永久代中。符號表和字符串表的實(shí)體都以規(guī)范的格式保存,,保證每個(gè)實(shí)體都只出現(xiàn)一次,。

當(dāng)類加載時(shí),字符串字面量被編譯器自動(dòng) intern 并加入到符號表,。除此之外,,String 類的實(shí)例可以調(diào)用 String.intern() 顯式地 intern。當(dāng)調(diào)用 String.intern() 方法時(shí),,如果符號表已經(jīng)包含了這個(gè)字符串,,那么就會(huì)返回符號表里的這個(gè)引用,如果不是,,那么這個(gè)字符串就被加入到字符串表中同時(shí)返回這個(gè)引用,。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多