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()方法被调用时,若是这个符号表里已经包含了这个字符串,那么将返回指向它 的引用,若是不包含,那这个字符串就会被加入到字符串表而且返回它的引用。

相关文章
相关标签/搜索