本文参照的《深入Java虚拟机》第5章内容
本文所说的java虚拟机指的是一个运行中的java虚拟机实例。
一个java虚拟机实例的任务就是:负责运行一个java程序,有着相同的生命周期。
每个java程序都有属于自己的java虚拟机实例,如果在同一台机器上运行3个java程序,将得到3个java虚拟机实例。
一个虚拟机的行为分别是按照子系统,内存区,数据类型以及指令这几个术语来描述的。如下图:
1.类装载器子系统
负责查找并装载类类型,即把描述类的数据从class文件加载到内存,并对数据进行校验,转换分析和初始化,最终形成可以被虚拟机直接使用的JAVA类型。
分为:启动类装载器和用户自定义类装载器。
类的加载顺序为:
1)装载
查找并装载类型的二进制数据
2)连接(验证,准备,解析)
验证:确保被导入类型的正确性
准备:为类(静态)变量分配内存,并初始化为默认值
解析:把存在常量池的类型中的符号引用转换为直接引用,举例如下图:
3)初始化:
将执行类构造器< init>()方法,注意这里的方法不是构造方法。该方法将会显式调用父类构造器,接下来按照java语句顺序为类变量和静态语句块赋值。
把类(静态)变量初始化为正确的初始值。
类被初始化的5种情况:1.new一个对象 ;2.初始化其子类;3.调用main方法;4.调用静态变量(final修饰的编译时常量除外);5.调用反射方法。
从类的角度看,类在虚拟机内存的生命周期包括:加载->连接(验证->准备->解析)->初始化->使用->卸载。
当虚拟机加载一个class类文件时,他会从class文件包含的二进制数据中解析类型信息,并把这些类型信息(数据结构)放入方法区中。当程序运行时,虚拟机会把运行时创建的该对象示例都放到堆中。
2.方法区
用来存储基本类型信息(类权限定名,直接超类权限定名,类类型,访问修饰符等),常量池,字段 方法信息,静态变量,一个到classLoader的引用,一个到Class类的引用等的区域,线程共享。
常量池:包括直接常量(String,integer和float),和对其他类型,字段和方法的符号引用。
方法信息:方法名,返回类型,参数数量及类型,修饰符,方法的字节码,异常表,操作数栈和该方法栈帧中的局部变量大小
3.堆
用来存储java程序运行时创建的所有实例或数组,线程共享。堆里的对象由垃圾收集器(GC)自动管理。
4.pc寄存器
存的是下一条即将被执行指令的“地址”。如果该线程正在执行一个本地方法,那么pc里的当前值是“undefined”
5.java栈
每当启动一个新线程,java虚拟机会为其分配一个java栈,线程私有。java栈以帧为单位,保存线程的运行状态,每一个帧代表线程调用的一个java方法。
对java栈的操作只有两只:以桢为单位的压栈和出栈。
栈帧:有3部分---局部变量区,操作数栈,帧数据区。
帧数据区:用于常量池解析,正常方法返回,异常中止处理。
一个方法运行的栈帧,如下图:
一个方法调用另一个方法的栈运行图,如下:
6.本地方法栈
为本地方法提供服务的。一个线程在执行java方法的过程中,调用了本地方法的例子如下图:
7.执行引擎![](https://img-blog.csdn.net/20170920160517732?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXVodWFsZWU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
负责执行被装载类的方法中的指令,有一套对应的指令集。
字节码执行是执行引擎是最重要的一部分,概念模型的总体外观是一致的:输入字节码,过程是字节码解析的等效过程,输出结果。不同的虚拟机有不同的具体实现,大体有解释执行和编译执行两种选择。方法的字节码流是由指令序列构成,每一条指令包含一个操作码,0个或多个操作数。
java指令集是以栈为中心设计的,可以更好得实现平台无关性。
另添加以下相关内容:
类文件结构
class类文件是一组以8位字节为基础的二进制流,它包含以下几个部分:
魔数:类文件开头的四个字节被定义为CAFEBABE,只有开头为CAFEBABE的文件才可以被虚拟机接受,接
class文件版本:下来四个字节为class文件的版本号,高版本JDK可以兼容以前版本的class文件,但不能运行以后版本的class文件。
常量池:分为 字面量和符号引用。字面量包含:文本字符串,声明为final的常量值等,符号引用包含:类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。
访问标志:常量池结束后,紧接着两个字节表示访问标志,用于识别一些类或接口层次的访问信息,例如是否是public,是否是static等。
类索引,父类索引,和接口索引集合:类索引用来确定这个类的全限定名,父类为父类的全限定名,接口索引集合为接口的全限定名。
字段表集合:用于描述接口或者类中声明的变量,但不包含方法中的变量。
方法表集合:用于表述接口或者类中的方法。
属性表集合:class文件,字段表,方法表中的属性都源自这里。
例子:查看java源代码对应的class文件
源码如下:
package com.founder.rcp.controller;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Random;
import com.founder.rcp.service.impl.ReserveTrigerServiceImpl;
public class Test {
public TestCommon c1;
public ReserveTrigerServiceImpl rt;
public String s = "1";
public TestCommon c2 = TestCommon.test;
public static void main(String[] args) {
System.out.println("主方法");
}
public String method1(){
System.out.println("方法1");
return "1";
};
}
查看class字节码文件的内容,可以用jdk自带的javap反汇编指令。如下图:
C:\Windows\system32>javap -v -p -c -s D:\workspace_ylt\ylt-yyzl\build\classes\co
m\founder\rcp\controller\Test.class
Classfile /D:/workspace_ylt/ylt-yyzl/build/classes/com/founder/rcp/controller/Te
st.class
Last modified 2017-11-15; size 968 bytes
MD5 checksum b90a7113987633afad3d236ca012f973
Compiled from "Test.java"
public class com.founder.rcp.controller.Test
SourceFile: "Test.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER//访问标志access_flag
Constant pool://常量池
#1 = Class #2 // com/founder/rcp/controller/Test --C_class_info
#2 = Utf8 com/founder/rcp/controller/Test
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
//字面量和符号引用
#5 = Utf8 c1
#6 = Utf8 Lcom/founder/rcp/controller/TestCommon;
#7 = Utf8 rt
#8 = Utf8 Lcom/founder/rcp/service/impl/ReserveTrigerServiceImpl;
#9 = Utf8 s
#10 = Utf8 Ljava/lang/String;
#11 = Utf8 c2
//CONSTANT_Methodref_info表
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Methodref #3.#16 // java/lang/Object."<init>":()V
#16 = NameAndType #12:#13 // "<init>":()V
#17 = String #18 // 1
#18 = Utf8 1
//CONSTANT_Fieldref_info表( s, c2)
#19 = Fieldref #1.#20 // com/founder/rcp/controller/Test.s:Ljava/lang/String;
#20 = NameAndType #9:#10 // s:Ljava/lang/String;
#21 = Fieldref #22.#24 // com/founder/rcp/controller/TestCommon.test:Lcom/founder/rcp/controller/TestCommon;
#22 = Class #23 // com/founder/rcp/controller/TestCommon
#23 = Utf8 com/founder/rcp/controller/TestCommon
#24 = NameAndType #25:#6 // test:Lcom/founder/rcp/controller/TestCommon;
#25 = Utf8 test
#26 = Fieldref #1.#27 // com/founder/rcp/controller/Test.c2:Lcom/founder/rcp/controller/TestCommon;
#27 = NameAndType #11:#6 // c2:Lcom/founder/rcp/controller/TestCommon;
#28 = Utf8 LineNumberTable
#29 = Utf8 LocalVariableTable
#30 = Utf8 this
#31 = Utf8 Lcom/founder/rcp/controller/Test;
#32 = Utf8 main
#33 = Utf8 ([Ljava/lang/String;)V
#34 = Fieldref #35.#37 // java/lang/System.out:Ljava/io/PrintStream;
#35 = Class #36 // java/lang/System
#36 = Utf8 java/lang/System
#37 = NameAndType #38:#39 // out:Ljava/io/PrintStream;
#38 = Utf8 out
#39 = Utf8 Ljava/io/PrintStream;
#40 = String #41 // 主方法
#41 = Utf8 主方法
#42 = Methodref #43.#45 // java/io/PrintStream.println:(Ljava/lang/String;)V
#43 = Class #44 // java/io/PrintStream
#44 = Utf8 java/io/PrintStream
#45 = NameAndType #46:#47 // println:(Ljava/lang/String;)V
#46 = Utf8 println
#47 = Utf8 (Ljava/lang/String;)V
#48 = Utf8 args
#49 = Utf8 [Ljava/lang/String;
#50 = Utf8 method1
#51 = Utf8 ()Ljava/lang/String;
#52 = String #53 // 方法1
#53 = Utf8 方法1
#54 = Utf8 SourceFile
#55 = Utf8 Test.java
{
//fields:field_info字段表集合
public com.founder.rcp.controller.TestCommon c1;
Signature: Lcom/founder/rcp/controller/TestCommon;
flags: ACC_PUBLIC
public com.founder.rcp.service.impl.ReserveTrigerServiceImpl rt;
Signature: Lcom/founder/rcp/service/impl/ReserveTrigerServiceImpl;
flags: ACC_PUBLIC
public java.lang.String s;
Signature: Ljava/lang/String;
flags: ACC_PUBLIC
public com.founder.rcp.controller.TestCommon c2;
Signature: Lcom/founder/rcp/controller/TestCommon;
flags: ACC_PUBLIC
//methods:method_info方法表集合
public com.founder.rcp.controller.Test();
Signature: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #15 // Method java/lang/Object."<init>
":()V
4: aload_0
5: ldc #17 // String 1
7: putfield #19 // Field s:Ljava/lang/String;
10: aload_0
11: getstatic #21 // Field com/founder/rcp/controlle
r/TestCommon.test:Lcom/founder/rcp/controller/TestCommon;
14: putfield #26 // Field c2:Lcom/founder/rcp/contr
oller/TestCommon;
17: return
LineNumberTable:
line 12: 0
line 15: 4
line 16: 10
line 12: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this Lcom/founder/rcp/controller/Test;
public static void main(java.lang.String[]);
Signature: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #34 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #40 // String 主方法
5: invokevirtual #42 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 19: 0
line 20: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
public java.lang.String method1();
Signature: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #34 // Field java/lang/System.out:Ljav
a/io/PrintStream;
3: ldc #52 // String 方法1
5: invokevirtual #42 // Method java/io/PrintStream.prin
tln:(Ljava/lang/String;)V
8: ldc #17 // String 1
10: areturn
LineNumberTable:
line 23: 0
line 24: 8
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/founder/rcp/controller/Test;
}