原文: JVM Interals
Thread Hotspot JVM 将Java线程一对一映射到操作系统原生线程上
Java线程状态初始化,如thread-local sotrage, allocation buffers, synchronization objects, stacks, program counter
原生线程创建,调用run()方法
run()方法返回,处理未捕获异常
线程终止,释放Java线程及原生线程资源,并判断JVM是否需要退出(如此线程是最后一个非守护线程)
JVM System Thread 由main方法创建的系统后台线程
VM thread 执行需要JVM在safe-point时才能执行的操作,如GC, thread stack dump, thread suspension, biased locking revocation
Periodic task thread
GC threads
Compiler threads 运行时编译字节码到native code
Signal dispatch thread 接收操作系统signal并处理
Per thread 与线程一一对应的资源
Program Counter (PC) 下一条指令或操作数的地址,如果是native方法,PC值为undefined
VM Stack 虚拟机栈,存储栈帧(Frame),每次方法调用时入栈,方法返回或异常时出栈。当栈容量超过最大限制时,会抛出StackOverflowError异常,当新线程没有足够内存创建栈时,会抛出OutOfMemoryError异常
Native Method Stack 本地方法栈,支持native方法执行,类似虚拟机栈,有的实现将两者合并
Stack Frame 栈帧(Stack Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、方法返回值和异常分派(Dispatch Exception)。
栈帧随着方法调用而创建,随着方法结束(返回或抛出异常)而销毁。栈帧包含局部变量表(Local Variables Array)、操作数栈(Operand Stack)和指向当前方法所属的类的运行时常量池的引用。
Local Variable Array 方法调用时所有的局部变量,包括参数及对this的引用,局部变量可以是基本类型,引用及返回地址,其中long和double占用两个局部变量。当方法调用时,参数会传递至从0开始的连续位置中,如果是实例方法,0存储方法所在对象的应用(即this)。局部变量表是可重用的,超出作用域的变量的位置可以被新变量使用。局部变量表长度在编译器决定。
Oprand Stack 用于字节码操作,性质类似CPU寄存器,例如调用iadd字节码指令会将栈顶两个int值出栈相加后再入栈
Dynamic Linking (Reference to runtime constant pool) C/C++代码在编译链接阶段会将符号引用(symbolic reference)转化为实际内存地址,对于Java来说,链接是在运行时动态进行的。Java class编译时,所有的变量及方法引用都以符号引用形式保存在class的常量池中,由JVM选择解析时机,eager/static 或者lazy/later resolution。解析只发生一次,同时会触发必要的类加载,解析后的direct reference以相对于方法或变量运行时位置存储结构的偏移量存储
Shared Between Threads 线程间共享的资源
Heap 存储对象实例及数组,栈帧被设计为创建后大小不可变,因此可变对象都存放在堆中,栈帧中仅保存基础类型及引用
Interned Strings String常量池,在类加载时,所有的String常量都会被保存到常量池中。运行时也可以显式调用String.intern(),如果常量池中已经存在该string,则直接返回引用,否则将该string加入常量池中并返回引用。 Java 6中String常量池放在Permgen中,容易引发OOM问题,Java 7开始已经将String常量池放到堆中,默认大小为60013,如果重度使用intern的话需要配置的更大避免hash冲突导致性能下降,详细信息参考String.intern in Java 6, 7 and 8
Non-Heap Memory Method Area 方法区,保存每个类的结构信息,如运行时常量池,字段和方法数据,构造函数和普通方法的字节码等等,包括:
Classloader Reference
相对应的classloader也会维护一份所有已加载类定义的引用
Run Time Constant Pool
Field data
Method data
Method code
Run Time Constant Pool JVM为每个类维护一个运行时常量池,保存运行时所需的数据,数据类型如下:
numeric literals
string literals
class references
field references
method references
字节码操作时会直接引用常量池中的数据,例如:
1 2 3 4 5 6 7 # 源码 # Object foo = new Object (); # 字节码 0 : new #2 // Class java/lang/Object 1 : dup 2 : invokespecial #3 // Method java/lang/Object ."<init>"()V
Code Cache 保存JIT编译后的本地代码,以前遇到过JDK的bug,Code Cache占用过高触发回收后JIT失效,因此可以考虑相对设置的大一些避免触发回收-XX:ReservedCodeCacheSize=256m
Class File Structure 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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] }
测试代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.test;public class TestClass { public static final String HELLO = "Hello" ; private int m; public int inc (int x) { return m + x; } public int inc_r (int x) { if (x <= 0 ) return m; return 1 + inc_r(x - 1 ); } public static int inc_one (int x) { return ++x; } }
编译结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 $ javap -v -p TestClass public class com.test.TestClass minor version: 0 ## 版本号,1.1 为45 ,后续每个版本加1 ,52 为1.8 major version: 52 ## JDK 1.0 .2 之后编译出的Class文件都带有ACC_SUPER标志,用于兼容invokespecial的语义变更 flags: ACC_PUBLIC, ACC_SUPER ## 常量池,包含Class中所有字符串常量,类或接口名,字段名,方法名和其他常量 Constant pool: ## 方法和字段表示方式类似,由class_index和name_and_type_index组成 #1 = Methodref #5. #23 #2 = Fieldref #4. #24 #3 = Methodref #4. #25 ## 类和接口,值为name_index #4 = Class #26 #5 = Class #27 #6 = Utf8 HELLO #7 = Utf8 Ljava/lang/String; #8 = Utf8 ConstantValue #9 = String #28 #10 = Utf8 m #11 = Utf8 I #12 = Utf8 <init> #13 = Utf8 ()V #14 = Utf8 Code #15 = Utf8 LineNumberTable #16 = Utf8 inc #17 = Utf8 (I)I #18 = Utf8 inc_r #19 = Utf8 StackMapTable #20 = Utf8 inc_one #21 = Utf8 SourceFile #22 = Utf8 TestClass.java ## 用于表示字段和方法,由name_index和descriptor_index组成 #23 = NameAndType #12 :#13 #24 = NameAndType #10 :#11 #25 = NameAndType #18 :#17 #26 = Utf8 com/test/TestClass #27 = Utf8 java/lang/Object #28 = Utf8 Hello { ## field_info表,字段信息 ## 字段名,name_index,引用自常量池 public static final java.lang.String HELLO; ## 字段描述,descriptor_index,引用自常量池 descriptor: Ljavabiao/lang/String; ## 定义字段被访问权限和基础属性的掩码标志 flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ## 属性,attribute_info表,ConstantValue属性表示一个常量字段的值 ## 只有ACC_STATIC, ACC_FINAL的基本类型和String才会使用ConstantValue方式赋值,其他字段都在执行构造函数时赋值 ConstantValue: String Hello private int m; ## int类型 descriptor: I flags: ACC_PRIVATE ## method_info表,方法信息,包括字节码 ## 初始化函数 public com.test.TestClass(); ## 返回值为void,无参数 descriptor: ()V flags: ACC_PUBLIC ## 属性,attribute_info表,Code属性保存字节码 Code: ## 栈最大深度,本地变量数量,参数数量(实例方法第一个参数默认为this,因此size为1 ) stack=1 , locals=1 , args_size=1 ## 推送this到栈顶(将第一个引用类型局部变量推送至栈顶) 0 : aload_0 ## 调用初始化方法(调用特殊的实例方法,例如私有方法,父类方法和初始化方法) 1 : invokespecial #1 ## 返回void 4 : return ## 对应源代码行号,用于调试 LineNumberTable: line 3 : 0 public int inc(int); ## 参数为int,返回值为int descriptor: (I)I flags: ACC_PUBLIC Code: stack=2 , locals=2 , args_size=2 ## this入栈 0 : aload_0 ## m入栈(获取指定类的实例域,并将其值压入栈顶) 1 : getfield #2 ## int参数入栈 4 : iload_1 ## 将栈顶2 个int相加并将结果入栈 5 : iadd ## 返回栈顶int 6 : ireturn LineNumberTable: line 15 : 0 public int inc_r(int); descriptor: (I)I flags: ACC_PUBLIC Code: ## stack记录单次调用的最大栈深度 stack=4 , locals=2 , args_size=2 0 : iload_1 1 : ifgt 9 4 : aload_0 5 : getfield #2 8 : ireturn 9 : iconst_1 10 : aload_0 11 : iload_1 12 : iconst_1 13 : isub ## 调用对象的实例方法 14 : invokevirtual #3 17 : iadd 18 : ireturn LineNumberTable: line 18 : 0 line 19 : 9 ## 帮助在编译期进行字节码验证,代替加载阶段性能开销较高的基于数据流的类型推导验证器 ## 按照控制流(if /goto)将代码分为多个frame ## StackMapTable记录每个frame的字节码偏移量及局部变量和操作数栈的变化 ## 每个方法的第一个frame都是隐式生成的,这边显示的从第二个开始 ## 参考:http: StackMapTable: number_of_entries = 1 ## 偏移量为9 (if 结束后),局部变量和操作数栈没有变化(same) frame_type = 9 public static int inc_one(int); descriptor: (I)I flags: ACC_PUBLIC, ACC_STATIC Code: ## static方法,因此没有this参数 stack=1 , locals=1 , args_size=1 0 : iinc 0 , 1 3 : iload_0 4 : ireturn LineNumberTable: line 22 : 0 } SourceFile: "TestClass.java"
Classloader