1.Hotspot jvm内存结构图
2.运行时数据区
类加载子系统加载class文件之后,将类信息保存于运行时数据区的方法区,方法区在java8之后被称为元数据空间。运行时数据区按是否被线程共享分为线程私有区和线程共享区,方法区和堆是线程共享的,java虚拟机栈、pc寄存器和本地方法栈是线程私有的,也就是说每一个线程都会有一个java虚拟机栈、pc寄存器和本地方法栈。
-
PC寄存器
PC寄存器是线程私有的,用于存储当前线程执行的指向下一条指令的地址。真正去取指令并执行指令的是执行引擎。程序的条件、循环、跳转都是通过PC寄存器来实现的。其不存在oom异常。
demo演示
//简单的计算k = i + j; package com.xiayu; public class PcRegisterDemo { public static void main(String[] args) { int i = 1; int j = 2; int k = i + j; System.out.println("k:" + k); } }
使用javap工具反编译出对应字节码文件
javap -v PcRegisterDemo.class Classfile /C:/Users/xiyue/Desktop/web/target/classes/com/xiayu/PcRegisterDemo.class Last modified 2020-2-26; size 825 bytes MD5 checksum 259dc12c5aff66aa1730bd83450f33c0 Compiled from "PcRegisterDemo.java" public class com.xiayu.PcRegisterDemo minor version: 0 major version: 49 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #11.#29 // java/lang/Object."
":()V #2 = Fieldref #30.#31 // java/lang/System.out:Ljava/io/PrintStream; #3 = Class #32 // java/lang/StringBuilder #4 = Methodref #3.#29 // java/lang/StringBuilder." ":()V #5 = String #33 // k: #6 = Methodref #3.#34 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #7 = Methodref #3.#35 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; #8 = Methodref #3.#36 // java/lang/StringBuilder.toString:()Ljava/lang/String; #9 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V #10 = Class #39 // com/xiayu/PcRegisterDemo #11 = Class #40 // java/lang/Object #12 = Utf8 #13 = Utf8 ()V #14 = Utf8 Code #15 = Utf8 LineNumberTable #16 = Utf8 LocalVariableTable #17 = Utf8 this #18 = Utf8 Lcom/xiayu/PcRegisterDemo; #19 = Utf8 main #20 = Utf8 ([Ljava/lang/String;)V #21 = Utf8 args #22 = Utf8 [Ljava/lang/String; #23 = Utf8 i #24 = Utf8 I #25 = Utf8 j #26 = Utf8 k #27 = Utf8 SourceFile #28 = Utf8 PcRegisterDemo.java #29 = NameAndType #12:#13 // " ":()V #30 = Class #41 // java/lang/System #31 = NameAndType #42:#43 // out:Ljava/io/PrintStream; #32 = Utf8 java/lang/StringBuilder #33 = Utf8 k: #34 = NameAndType #44:#45 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #35 = NameAndType #44:#46 // append:(I)Ljava/lang/StringBuilder; #36 = NameAndType #47:#48 // toString:()Ljava/lang/String; #37 = Class #49 // java/io/PrintStream #38 = NameAndType #50:#51 // println:(Ljava/lang/String;)V #39 = Utf8 com/xiayu/PcRegisterDemo #40 = Utf8 java/lang/Object #41 = Utf8 java/lang/System #42 = Utf8 out #43 = Utf8 Ljava/io/PrintStream; #44 = Utf8 append #45 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #46 = Utf8 (I)Ljava/lang/StringBuilder; #47 = Utf8 toString #48 = Utf8 ()Ljava/lang/String; #49 = Utf8 java/io/PrintStream #50 = Utf8 println #51 = Utf8 (Ljava/lang/String;)V { public com.xiayu.PcRegisterDemo(); descriptor: ()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 Lcom/xiayu/PcRegisterDemo; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=4, args_size=1 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: iload_1 5: iload_2 6: iadd 7: istore_3 8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 11: new #3 // class java/lang/StringBuilder 14: dup 15: invokespecial #4 // Method java/lang/StringBuilder." ":()V 18: ldc #5 // String k: 20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 23: iload_3 24: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 27: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 30: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 33: return LineNumberTable: line 5: 0 line 6: 2 line 7: 4 line 8: 8 line 9: 33 LocalVariableTable: Start Length Slot Name Signature 0 34 0 args [Ljava/lang/String; 2 32 1 i I 4 30 2 j I 8 26 3 k I } SourceFile: "PcRegisterDemo.java" 其中main方法对应的字节码如下图,PC寄存器保存操作指令对应的序号,执行引擎通过PC寄存器知道该执行到4了,然后取出对应的操作指令iload_1,然后执行相应的操作。
-
java虚拟机栈
java虚拟机栈中包含栈帧,一个方法对应一个栈帧,方法的调用对应着java虚拟机栈的入栈与出栈,一个栈帧主要包含局部变量表、操作数栈、动态链接和方法返回地址。jvm虚拟机栈的最大空间可以通过-Xss参数设置,如-Xss256k,-Xss1m 默认为1024k.
演示demo
package com.xiayu; public class JavaVMStack { public static void methodA(){ String a = "a"; System.out.println(a); } public static void methodB(){ String a = "a"; System.out.println(a); } public static void methodC(){ String a = "a"; System.out.println(a); } public static void methodD(){ methodA(); methodB(); methodC(); } }
通过jclasslib查看字节码文件可以看到,在methodD方法对应的字节码中分别调用methodA(),methodB(0),methodC();
-
局部变量表
局部变量表是数组结构的,其主要存储方法参数,方法内定义的局部变量,以及方法返回值,按照类型可以分为基本类型(byte,short,int,long,boolean,float,double,char)和引用类型,一个方法的局部变量表的大小是在编译阶段就已经确定,并且局部变量表的大小作为maximum local variables属性。如果是实例方法,局部变量表第一个变量是当前对象this,如果是类方法就不会有this变量,类方法中使用this报错也是这个原因。局部变量表存储变量的基本单位是slot,一个slot为32位,byte,short,int,char,boolean都会转换成int类型进行保存,long和double则占据两个slot.方法嵌套调用的次数有栈的大小决定,栈空间越大,可嵌套调用的次数就越多多。局部变量表中的变量只对当前方法调用中有效,在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递。局部变量表中的变量也是作为判断回收内存的根节点。演示demo
-------------------java----------------- public static void methodA(){ String a = "a"; System.out.println(a); } -------------------java----------------- -------------------bytecode----------------- 0 ldc #2 2 astore_0 3 getstatic #3
6 aload_0 7 invokevirtual #4 10 return -------------------bytecode----------------- -
操作数栈
操作数栈是基于数组实现的,但只能pop和push.在方法的执行过程中,根据字节码指令,往栈中写入数据或者提取数据,即入栈和出栈。在执行字节码指令的时候,字节码指令cpu并不能够执行,需要将其翻译成对应的机器指令,操作数栈的深度在编译阶段就确定了。demo演示
package com.xiayu; public class OperatorStack { public int testOperatorStack(){ int i = 1; int j = 2; int e = i + j; return e; } } ----------------------------bytecode---------- Classfile /C:/Users/xiyue/Desktop/web/target/classes/com/xiayu/OperatorStack.class Last modified 2020-2-26; size 434 bytes MD5 checksum 1cd3b7d9c1bd15078c95196d5a2e9e53 Compiled from "OperatorStack.java" public class com.xiayu.OperatorStack minor version: 0 major version: 49 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #3.#19 // java/lang/Object."
":()V #2 = Class #20 // com/xiayu/OperatorStack #3 = Class #21 // java/lang/Object #4 = Utf8 #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 LocalVariableTable #9 = Utf8 this #10 = Utf8 Lcom/xiayu/OperatorStack; #11 = Utf8 testOperatorStack #12 = Utf8 ()I #13 = Utf8 i #14 = Utf8 I #15 = Utf8 j #16 = Utf8 e #17 = Utf8 SourceFile #18 = Utf8 OperatorStack.java #19 = NameAndType #4:#5 // " ":()V #20 = Utf8 com/xiayu/OperatorStack #21 = Utf8 java/lang/Object { public com.xiayu.OperatorStack(); descriptor: ()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 Lcom/xiayu/OperatorStack; public int testOperatorStack(); descriptor: ()I flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=1 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: iload_1 5: iload_2 6: iadd 7: istore_3 8: iload_3 9: ireturn LineNumberTable: line 6: 0 line 7: 2 line 8: 4 line 9: 8 LocalVariableTable: Start Length Slot Name Signature 0 10 0 this Lcom/xiayu/OperatorStack; 2 8 1 i I 4 6 2 j I 8 2 3 e I } 延伸i++问题
public int testOperatorStack(){ int i = 1; i++; int j = 2; ++j; int a = 10; a = a++; int b = 20; b = ++b; return a; } ------------bytecode--------------- 0 iconst_1 1 istore_1 2 iinc 1 by 1 5 iconst_2 6 istore_2 7 iinc 2 by 1 10 bipush 10 12 istore_3 13 iload_3 14 iinc 3 by 1 17 istore_3 18 bipush 20 20 istore 4 22 iload 4 24 iinc 4 by 1 27 istore 4 29 iload_3 30 ireturn ------------bytecode---------------
通过字节码可以知道i++和++j没有区别,a=a++和b=++b有区别,a=a++:再将局部变量表中的变量放置在操作栈后,下一步是直接取出,然后进行加1操作(iinc 3 by 1),而b=++b,放置到操作栈后先进行加1,然后再取出。
-
动态链接
每一个栈帧内部都包含一个指向运行时常量池中该栈所属方法的引用,包含这个引用目的就是为了支持当前方法代码能够实现动态链接。如invokedynamic指令。在java文件被编译到字节码文件中,所有变量和方法引用都作为符号引用保存在class文件的常量池里。如:描述一个方法调用了别的方法,就是通过常量池中指向方法的符号引用来表示的。动态链接就是为了将这些符号引用转换为调用方法的直接引用。举例:一个类成员变量被这个类的多个方法所使用,那么该变量在字节码层面就是一个符号引用,这么做能够节省很多内存。将符号引用转换为直接引用与方法的绑定机制有关
静态链接
当一个字节码文件被装载进jvm内部时,如果被调用的目标方法在编译期可知,且运行期保持不变。这种情况下降调用方法的符号引用转换为直接引用的过程称之为静态链接。
动态链接
如果被调用的方法在编译期无法确定下来,在程序运行期间才可确定,这种链接称为动态链接。在字节码层面上,对应的字节码指令分别是invokeinterface,invokevirtual.
非虚方法:静态方法、私有方法、final方法、实例构造器、父类方法,其他都是虚方法。
- 方法返回地址
存放调用该方法的pc寄存器的值。如在一个方法A字节码偏移值为7的时候调用了另外一个方法B,那么方法B的方法返回地址就是7,方法B执行完了之后就会接着执行方法A偏移量7之后的字节码。如果方法B出现异常信息并且没有捕获异常,就会非正常退出,返回地址要通过异常表来确定,栈帧中一般不会保存这部分信息。
-
- 本地方法栈
。。。