JVM內部原理

JVM內部原理

原文連接:http://blog.jamesdbloom.com/JVMInternals.html
原文做者:James D Bloom
如下爲本人翻譯,僅用於交流學習,版權歸原做者全部,轉載註明出處,請不要用於商業用途
[TOC]
這篇文章詳細描述了Java虛擬機的內在結構。下面這張圖來自《The Java Virtual Machine Specification Java SE 7 Edition》,它展現了一個典型的JVM的主要的內部結構。
這裏寫圖片描述 html

接下來的2個部分,將詳細介紹這幅圖中全部組成結構。 第一部分涵蓋了每一個線程都會生成的結構, 第二部分涵蓋了單獨的每一個線程生成的結構。 java

  • 線程
    o JVM 系統線程
    o 每一個線程
    o 程序計數器 (PC)
    o 棧
    o 本地棧
    o 棧的限制
    o 棧幀
    o 局部變量表
    o 操做數棧
    o 動態鏈接
  • 線程間共享
    o 堆
    o 內存管理
    o 堆外內存
    o 即時(JIT)編譯
    o 方法區
    o Class 文件結構
    o 類加載器
    o 快速類加載
    o 方法區在哪裏
    o 類加載器的引用
    o 運行時常量池
    o 異常表
    o 符號表
    o 內部字符串 (String Table)

線程

線程是一個程序裏的運行單元。JVM容許一個應用有多個線程並行的執行。在Hotspot JVM裏,每一個線程都與操做系統的本地線程直接映射。在一個Java線程準備好了全部的狀態後,好比線程本地存儲,緩存分配,同步的對象,棧以及程序計數 器,這時一個操做系統中的本地線程也同時建立。當Java線程終止後,本地線程也會回收。操做系統所以負責全部線程的安排調度到任何一個可用的CPU上。 一旦本地線程初始化成功,它就會調用Java線程中的run()方法。當run()方法返回,發生了未捕獲異常,Java線程終止,本地線程就會決定是否 JVM也應該被終止(是不是最後一個非守護線程) 。當線程終止後,本地線程和Java線程持有的資源都會被釋放。 web

JVM 系統線程

若是你使用jconsole或者是任何一個調試工具,都能看到在後臺有許多線程在運行。這些後臺線程不包括調用public static void main(String[])的main線程以及全部這個main線程本身建立的線程。這些主要的後臺系統線程在Hotspot JVM裏主要是如下幾個:
虛擬機線程:這種線程的操做是須要JVM達到安全點纔會出現。這些操做必須在不一樣的線程中發生的緣由是他們都須要JVM達到安全點,這樣堆纔不會變化。這種線程的執行類型包括」stop-the-world」的垃圾收集,線程棧收集,線程掛起以及偏向鎖撤銷。
週期任務線程:這種線程是時間週期事件的體現(好比中斷),他們通常用於週期性操做的調度執行。
GC線程:這種線程對在JVM裏不一樣種類的垃圾收集行爲提供了支持。
編譯線程:這種線程在運行時會將字節碼編譯成到本地代碼。
信號調度線程:這種線程接收信號併發送給JVM,在它內部經過調用適當的方法進行處理。 bootstrap

每一個線程

每一個執行線程都包含如下的部分: 數組

程序計數器(PC)

當前非native指令(或者字節碼)的地址。若是當前方法是native的,那麼這個程序計數器即是無用的。全部CPU都有程序計數器,一般來 說,程序計數器在每次指令執行後自增,它會維護下一個將要執行的指令的地址。JVM經過程序計數器來追蹤指令執行的位置,在方法區中,程序計數器其實是 指向了一個內存地址。 緩存

每一個線程都有本身的棧,它維護了在這個線程上正在執行的每一個方法的棧幀。這個棧是一個後進先出的數據結構,因此當前正在執行的方法在棧的頂端,每當 一個方法被調用時,一個新的棧幀就會被建立而後放在了棧的頂端。當方法正常返回或者發生了未捕獲的異常,棧幀就會從棧裏移除。棧是不能被直接操做的,尤爲 是棧幀對象的入棧和出棧,所以,棧幀對象有可能在堆裏分配而且內存不須要連續。 安全

本地棧

並非全部的JVM都支持本地方法。不過那些支持的一般會建立出每一個線程的本地方法棧。若是一個JVM已經實現了使用C-linkage 模型來支持Java本地調用,那麼這個本地方法棧將會是一個C 棧。在這種狀況下,參數的順序以及返回值和傳統的c程序在本地棧下幾乎是同樣的。一個native方法一般(取決於不一樣的JVM實現)會回調JVM,而且 調用一個Java方法。這種native到Java的調用會發生在棧裏(一般指Java棧)。這個線程會離開這個本地棧而且在棧上建立一個新的棧幀。 數據結構

棧的限制

一個棧能夠是動態的大小,也能夠是指定的大小。若是一個線程須要一個大一點的棧,可能會致使StackOverflowError異常。若是一個線程須要一個新的棧幀而又沒有足夠的內存來分配,就會發生OutOfMemoryError異常。 多線程

棧幀

一個新的棧幀被建立,而後加到每一個方法調用的棧上。當方法正常返回或者遇到了未捕獲的異常,這個棧幀將被移除。想要了解更多的關於異常處理的能夠看下面的「異常表」部分。
每一個棧幀包含了:
局部變量表
返回值
操做數棧
當前方法所在的類的運行時常量池引用 併發

局部變量表

局部變量表包含了這個方法執行期間全部用到的變量,包括this引用,全部方法參數以及其餘的局部聲明變量。對於類方法(好比靜態方法)來講,全部方法參數都是從0位置開始,然而,對於實例方法來講這個0的位置是留給this的。
一個局部變量能夠是:
• boolean
• byte
• char
• long
• short
• int
• float
• double
• reference
• returnAddress
在局部變量表裏,全部類型都是佔了一個槽位,可是long和double除外,它們佔了2個連續槽位,由於他們是64位寬度。

操做數棧

操做數棧用於字節碼指令執行期間,就像通用寄存器在CPU裏使用同樣。大部分JVM的字節碼各自操做出棧,入棧,複製,交換,或者執行操做,使其生 產和消費各類數據。所以,在字節碼裏,指令把值在局部變量表和操做數棧之間頻繁移動。好比,一個簡單的變量初始化致使兩個字節碼在操做數棧裏交互影響。

int i;

編譯後獲得下面字節碼:

 0: iconst_0    // 將 0 入棧到操做數棧的頂端。  1: istore_1    // 從操做數棧頂端彈出並保存到局部變量

想要了解更多關於局部變量表和操做數棧,運行時常量池之間的交互,請看下面的「class文件結構」。

動態連接

每一個棧幀都包含了運行時常量池的引用。這個引用指向了這個棧幀正在執行的方法所在的類的常量池,它對動態連接提供了支持。

C/C++ 代碼一般編譯成一個對象文件,而後多個文件被連接起來生成一個可用的文件好比一個可執行文件或者動態連接庫。在連接階段,符號引用在每一個對象文件裏被替換成一個和最終執行相關的實際的內存地址。在Java裏,這個連接過程在運行時是自動發生的。

當Java文件被編譯時,全部的變量和方法引用都做爲符號引用被保存在class文件的常量池裏。一個符號引用是一個邏輯引用並非一個實際的指向 一個物理內存地址的引用。不一樣的JVM實現能選擇何時去解決符號引用,它一般發生在class文件加載後的驗證階段,當即調用或者靜態解析,另一種 發生的時候是當符號引用第一次被使用,也叫作延遲或者延期解析。不管如何當每一個引用第一次使用的時候,JVM必須保證解析發生,並在解析發生錯誤的時候拋 出來。綁定是一個字段,方法或者類在符號引用被替換爲直接引用而後被肯定的過程,這僅僅發生一次,由於符號引用是徹底被替換的。若是符號引用關聯到某個 類,而這個類卻還沒準備好,那就會引起類加載。每一個直接引用被保存爲偏移地址而不是和變量或者方法在運行時的位置有關的存儲結構。

線程間共享

堆是被用於在運行時分配類實例和數組。數組和對象可能永遠不會存儲在棧上,由於一個棧幀並非設計爲在建立後會隨時改變大小。棧幀僅僅保存引用,這 個引用指向對象或者數組在堆中的位置。與局部變量表(每一個棧幀裏)中的基本數據類型和引用不一樣,對象老是被存儲在堆裏,因此他們在方法結束後不會被移除, 僅僅在垃圾收集的時候纔會被移除。

爲了支持垃圾收集,堆被分爲三個部分:

年輕代o 經常又被劃分爲Eden區和Survivor區

老年代 (也被叫作年老代)
持久代

內存管理

對象和數組不會被明確的釋放,只有垃圾收集器會自動釋放他們。
一般他們的工做流程以下:

  1. 新對象和數組被分配在年輕代。
  2. 年輕代會發生Minor GC。 對象若是仍然存活,將會從eden區移到survivor區。
  3. Major GC 一般會致使應用線程暫停,它會在2個區中移動對象,若是對象依然存活,將會從年輕代移到老年代。
  4. 當每次老年代進行垃圾收集的時候,會觸發持久代帶也進行一次收集。一樣,在發生full gc的時候他們2個也會被收集一次。

堆外內存

堆外內存的對象在邏輯上是JVM的一部分,可是它卻不是在堆裏建立的。
堆外內存包括:

持久代包含o 方法區o 內部字符串

代碼緩存 用於編譯和保存已經被JIT編譯器編譯成的native代碼的方法。

即時 (JIT)編譯

在JVM裏,Java字節碼被解釋運行,可是它沒有直接運行native代碼快。爲了提升性能,Oracle Hotspot VM會尋找字節碼的」熱點」區域,它指頻繁被執行的代碼,而後編譯成native代碼。這些native代碼會被保存在堆外內存的代碼緩存區。用這種方 式,Hotspot會盡力去選擇最合適的方法來權衡直接編譯native代碼的時間和直接解釋執行代碼的時間。

方法區

方法區存儲的是每一個class的信息,例如:

類加載器引用運行時常量池o 全部常量o 字段引用o 方法引用o 屬性• 字段數據o 每一個方法
名字
類型
修飾符
屬性
方法數據o 每一個方法
 名字
 返回類型
 參數類型(按順序)
 修飾符
 屬性
方法代碼o 每一個方法
 字節碼
 操做數棧大小
 局部變量大小
 局部變量表
 異常表
 每一個異常處理
 開始位置
 結束位置
 代碼處理在程序計數器中的偏移地址
 被捕獲的異常類的常量池索引

全部線程都共享一樣的方法區,因此訪問方法區的數據和動態連接的過程都是線程安全的。若是兩個線程嘗試訪問一個類的字段或者方法而這個類尚未加載,這個類就必定會首先被加載並且僅僅加載一次,這2個線程也必定要等到加載完後纔會繼續執行。

類文件結構

一個編譯好的類文件包含以下的結構:

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:
關於類文件的固定的信息,以及這個類文件被編譯的JDK版本號。

constant_pool
和符號表相似,詳情請看下面的「運行時常量池」

access_flags
提供了類的修飾符清單

this_class
指向常量池的索引,它提供了類的全限定名,如org/jamesdbloom/foo/Bar

super_class
指向常量池的索引,它提供了一個到父類符號引用,如java/lang/Object

interfaces
指向常量池索引集合,它提供了一個符號引用到全部已實現的接口
fields
指向常量池索引集合,它完整描述了每一個字段

methods
指向常量池索引集合,它完整描述了每一個方法的簽名,若是這個方法不是抽象的或者不是native的,那麼字節碼中會體現出來

attributes
不一樣值的集合,它提供了額外的關於這個類的信息,包括任何帶有RetentionPolicy.CLASS 或者RetentionPolicy.RUNTIME的註解

經過使用javap這個命令,咱們能夠在已編譯的class文件中看到字節碼信息。
若是你編譯下面這段代碼

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

}

這時運行以下命令即可以看到接下來的輸出
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_SUPER Constant pool: #1 = Methodref          #6.#17 // java/lang/Object."<init>":()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               <init>
   #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 // "<init>":()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."<init>":()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;
}

這個類文件說明在常量池有3個主要的部分,構造函數和sayHello方法。

常量池 – 它提供了和符號表同樣的信息,詳細描述能夠看後面的章節。

方法 - 每一個方法包含4個區域o 簽名和訪問標識o 字節碼o 行號表 – 它爲調試器提供了指向字節碼關聯的代碼行信息,例如,sayHello方法中,字節碼0表明的是第6行Java代碼,字節碼8表明的是第7行Java代碼。o 局部變量表 – 棧幀裏全部局部變量的集合,在全部的例子裏局部變量都是指這個。

接下來介紹這個類文件中用到的字節碼操做符。

aload_0
這個操做符是一組aload 格式操做符中的一種。他們加載一個對象引用到操做數棧。指向局部變量集合中被訪問的地址,可是值只能是0,1,2或者3。其餘相似的操做符用於加載非對象 引用,如iload_ ,lload_ ,fload_ 和dload_,其中i是int類型,l是long類型,f是float類型,d是double類型。局部變量索引超過3的也能夠用iload, lload, fload, dload 和aload加載。這些操做符都只加載單一的而且是明確的局部變量索引的操做數。

ldc
這種操做符用於將一個常量從運行時常量池推入到操做數棧。

getstatic
這種操做符用於將一個在運行時常量池裏的靜態值從靜態字段列表推入到操做數棧。

invokespecial, invokevirtual
這種操做符是一系列方法調用操做符中的一種,好比 invokedynamic, invokeinterface,invokespecial, invokestatic, invokevirtual。在這個類文件裏invokespecial 和 invokevirutal用於不一樣用途,invokevirutal用於調用一個基於對象的類方法,而invokespecial指令用於調用實例初始 化方法,以及private方法,父類的方法。

return
這種操做符是一組操做符中的一種,好比ireturn, lreturn,freturn, dreturn, areturn 和return。每一個操做符被指定了返回聲明,他們返回不一樣的值,i用於int,l用於long,f用於float,d用於double,而a是對象的引 用。沒有return符號的將只返回void。

與局部變量,操做數棧以及運行時常量池交互的大部分操做數中,任何一個典型的字節碼都以下所示。
.
構造函數有2個指令,第一個this被推入到操做數棧,接下來父類的構造函數被調用,它使用了this,並從操做數棧裏彈出。
這裏寫圖片描述

這個sayHello() 方法會更復雜,它必須經過運行時常量池將符號引用轉成實際的引用,就像以前介紹的那樣。第一個操做符getstatic將一個引用從System類裏移出 並推入到操做數棧的靜態字段。接下來的操做符ldc將字符串」Hello」推入操做數棧。最後一個操做符invokevirtual調用 System.out的println方法,把從操做數棧彈出字符串」Hello」做爲參數而且建立一個當前線程的新的棧幀。

這裏寫圖片描述

類加載器

JVM啓動的時候經過bootstrap類加載器加載一個初始類。這個類在調用public static void main(String[])方法以前被連接和初始化。這個方法的執行將依次致使所需的類的加載,連接和初始化。

加載 是一個經過指定的名字來查找當前類和接口並把它讀取到一個字節數組的過程。下一步這個字節數組將被解析成一個肯定的並帶有major version和minor version的類對象。任何被直接父類指定了名字的類或者接口都會被加載。一旦這個過程完成,一個類或者接口對象便經過一個二進制表示的數據來建立完 成。

連接 是一個類或接口驗證以及類型、直接父類和父接口準備的過程。連接包含了3步,驗證,準備以及部分解析。

驗證 是一個肯定類或者接口是不是正確結構以及是否聽從Java語言和JVM規定的語法的過程。好比下面:
1. 符號表中一致的,正確的格式
2. final 方法 / 類沒有被重寫
3. 方法聽從訪問控制關鍵字
4. 方法有正確的參數個數和類型
5. 字節碼沒有不正確的操做棧結構
6. 變量在使用前已經初始化
7. 變量有正確的類型值
驗證階段的這些檢查意味着它們不須要在運行的時候才進行。連接階段的驗證雖然拖慢了加載速度,可是它避免了在字節碼運行時還須要進行各類檢查。

準備 涉及到靜態存儲的內存分配以及JVM會用到的任何的數據結構好比方法表。靜態字段被建立和初始化爲默認值,然而,在這個階段並不會像初始化階段中那樣會有初始化或者代碼被執行。

解析 是一個可選階段,它經過加載引用的類或者接口來檢查符號引用,以及檢查引用的正確性。若是這時沒有發生符號引用的解析,它會被延期到字節碼指令使用以前進行。

初始化 一個類或者接口的初始化包含了執行類或者接口的初始化方法
這裏寫圖片描述
在JVM裏有許多不一樣角色的類加載器。每一個類加載器委託給父類加載器去加載,除了最頂層的bootstrap classloader。

Bootstrap Classloader 一般被本地代碼實現,由於它在JVM裏是最先被實例化的。這個bootstrap classloader 負責加載最基本的Java APIs,包括rt.jar。它僅僅加載啓動的classpath裏找到的擁有最高信任的類,這也就致使它會跳過不少給普通類進行的驗證工做。

Extension Classloader 它加載Java標準擴展APIs類,好比security擴展類。

System Classloader 系統類加載器是默認的應用加載器,它加載classpath下的應用類。

User Defined Classloaders 是一個可替換的用於加載應用類的類加載器。一個用戶自定義的類加載器通常用於多種特殊緣由包括運行時重加載或者一般在web server中將所須要的已加載的類分紅不一樣組,好比Tomcat。

這裏寫圖片描述

快速類加載

在HotSpot JVM 5.0版本中介紹了一種叫作類數據共享(CDS)的特性。在JVM安裝過程當中,它會加載一些關鍵的JVM類到內存映射共享存檔裏,好比rt.jar。 CDS減小了加載這些類的時間,提升了JVM啓動的速度,而且容許這些類被共享在JVM裏不一樣的示例之間,下降了內存佔用。

方法區在哪裏

The Java Virtual Machine Specification Java SE 7 Edition 明確說明: 「儘管全部的方法區在邏輯上是屬於堆的一部分,但一些簡單的實現可能不會選擇去進行垃圾收集或者進行壓縮。」 相反,在Oracle JVM 的jconsole裏會發現這個方法區(以及代碼緩存)並非堆的一部分。在OpenJDK代碼裏能夠看到這個CodeCache在虛擬機裏和 ObjectHeap是不一樣的字段。

類加載器的引用

因此被加載的類都包含了一個指向加載他們本身的類加載器的引用。一樣這個類加載器也包含了他本身加載的全部類的引用。

運行時常量池

JVM維護了每一個類型的常量池,一個運行時數據結構和符號表很類似,儘管它包含了更多的數據。Java中的字節碼須要數據支持,一般這種數據會很大 以致於不能直接存到字節碼裏,換另外一種方式,能夠存到常量池,這個字節碼包含了指向常量池的引用。在動態連接的時候會用到運行時常量池,上面部分有介紹。
幾種在常量池內存儲的數據類型包括:
數量值
字符串值
類引用
字段引用
方法引用

例以下面這段代碼:

Object foo = new Object();

將會被編譯成以下字節碼

 0:     new #2          // Class java/lang/Object
 1: dup
 2: invokespecial #3    // Method java/ lang/Object "<init>"( ) V

這個new操做符(操做數代碼) 後面緊跟#2 操做符。這個操做符是一個指向常量池的索引,所以它指向的是常量池的第2個入口。第2個入口是一個類引用,這個入口接下來引用的是另外一個常量池入口,它包 含類的名字,是一個UTF8常量字符串,內容爲// Class java/lang/Object ,這個符號鏈接能夠用於查找java.lang.Object這個類。new操做符建立了一個類實例而且實例化了它的值。一個指向新的類實例的引用會被加 入到操做數棧。dup操做符這時會建立一個操做數棧最頂層元素的額外的拷貝,而且把它再次加入到操做數棧的頂部。最後在第2行經過 invokespecial一個實例初始化方法被調用。這個操做數一樣包含一個指向常量池的引用。這個初始化方法從操做數池的頂端彈出一個元素並把它做爲 參數傳給方法。最後便生成了一個指向這個新建立並被初始化的對象的引用。

若是你編譯下面這個簡單類:

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

這個已生成的類文件中的常量池像以下這樣:

Constant pool:
   #1 = Methodref          #6.#17 // java/lang/Object."<init>":()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               <init>
   #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 // "<init>":()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

這個常量池包含以下類型:
Integer
一個4字節的int類型常量
Long
一個8字節的long類型常量
Float
一個4字節的float類型常量
Double
一個8字節的double類型常量
String
一個字符串常量,指向另外一個UTF8入口,在這個常量池裏包含了實際的字節數據。
UTF8
一個表明UTF8編碼字符序列的字節流。
Class
一個指向另外一個UTF8入口的類常量 , 在這個常量池內部包含了JVM內部格式化的類名字的徹底限定符(在動態連接過程裏用到)。
NameAndType
冒號分隔的一對值,每一個值指向另外一個常量池的入口。第1個值(冒號前面)指向一個UTF8字符串入口,這個字符串是一個方法名或者字段名。第2個值指向一 個UTF8入口,它表明一種類型,當前面是字段的時候,它就是字段類的全限定名,若是是方法,它就是每一個參數類型的全限定名集合。
Fieldref, Methodref, InterfaceMethodref
逗號分隔的一對值,每一個值指向另外一個常量池的入口。第1個值(逗號前面)指向一個類入口。第2個值指向一個NameAndType入口。

異常表

異常表保存了每一個異常處理信息好比:
• 起始位置
• 結束位置
• 程序計數器記錄的代碼處理的偏移地址
• 被捕獲的異常類在常量池中的索引

若是一個方法定義了一個try-catch 或者try-finally的異常處理,那麼一個異常表就會被建立。它包含了每一個異常處理或者finally塊的信息,這些信息包括異常處理應用的範圍, 被處理的異常的類型以及處理代碼的位置。當一個異常被拋出,JVM會在當前的方法裏尋找一個匹配的處理,若是沒有找到,那麼這個方法會強制結束並彈出當前 棧幀,而且異常會從新拋給上層調用的方法(在新的當前棧幀)。若是在全部棧幀被彈出前仍然沒有找到合適的異常處理,那麼這個線程將終止。若是這個異常在最 後一個非守護線程裏拋出,將會致使JVM本身終止,好比這個線程是個main線程。
.
無論異常何時拋出,最終異常處理能匹配到了全部異常類型,代碼就會繼續執行。在這種狀況下,若是方法結束後沒有異常拋出,那麼finally塊仍然被執行,在return被執行前,它經過直接跳到finally塊來完成目標。

符號表

在持久代裏,除了有各類類型的運行時常量池外,JVM還維護了一個符號表。這個符號表是一個Hashtable,它從符號指針映射到符號(好比 Hashtable<Symbol*, Symbol>),而且還包含了一個指向全部符號的指針,包括每一個類的運行時常量池中維護的符號。

引用計數器被用於控制當一個符號從符號表移除的時候。好比當一個類被卸載,全部在運行時常量池中維護的符號的引用計數將減小。當符號表裏的一個符號 引用計數器變成0,這個符號表就知道這個符號將再也不被引用,而且這個符號會從符號表裏卸載。對符號表和字符串表來講,爲了提升效率和保證每一個入口只出現一 次,全部的入口被維護在一個標準化的格式裏。

內部字符串 (String Table)

Java語言規範裏要求徹底相同的字符串字面量,應該包含一樣的Unicode字符序列,而且必須是一樣的字符串實例。另外,若是 String.intern() 被一個String 實例調用,它應該返回一個相同的引用。若是一個字符串是一個固定的字面量,那麼下面會是返回true。

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

在Hotspot JVM裏,字符串表維護了內部的字符串,它是一個Hashtable結構,從對象指針映射到符號(例如Hashtable<oop, Symbol>),而且是維護在持久代裏。對符號表和字符串表來講,爲了提升效率和保證每一個入口只出現一次,全部的入口被維護在一個標準化的格式 裏。

類被加載的時候,字符串的字面量是經過編譯器自動的被內部化,而且加入到符號表裏。此外,經過調用String.intern()方 法,String類的實例可以明確的被內部化。當String.intern()方法被調用時,若是這個符號表裏已經包含了這個字符串,那麼將返回指向它 的引用,若是不包含,那這個字符串就會被加入到字符串表而且返回它的引用。

相關文章
相關標籤/搜索