编译与反编译
编译反编译 类加载
装载链接
验证准备解析 类加载器
实例双亲委派机制 运行时数据区
方法区堆Java 栈本地方法栈程序计数器
编译与反编译 编译javac -eancoding utf-8 ./Test.java
指定编码格式,生成 Test.class 字节码文件,不易开发人员解读
字节码文件由 JVM 识别并运行,如果需要方便开发人员查看编译后字节码文件对应的内容,可以使用下面的指令
javap -p -v ./Test.class > ./Test.txt
查看类文件全部指令信息,字节码将汇编编译成方便查看的格式。
| 装载 | 链接 | 初始化 |
|---|---|---|
| 验证 ⇨ 准备 ⇨ 解析 |
Class文件 -> 字节流 -> 类加载器
装载- 将编译后的字节码文件存放到方法区中在堆中创建一个 java.lang.Class 的对象,去作为方法区中数据的访问入口
验证字节码文件的文件格式、数据、符号引用是否正确
准备为类的静态变量分配内存
解析将符号引用转变成为了直接引用。
符号引用是存放在运行时常量池里面的。
符号引用在 class 文件中只是文字上的一些描述,需要在虚拟机中开辟出一块其对应的真正的物理内存
| 加载器 | 功能 | |
|---|---|---|
| BootStrap Classloader | ⇨ | 加载 $JAVA_HOME/jre/lib/rt.jar 里所有的 class 或者 -Xbootclasspath 选项指定的 jar 包, |
| ⬆ | ||
| ExtClassLoader | ⇨ | 加载 Java 平台中扩展功能的一些 jar 包,包括 $JAVA_HOME/jre/lib/*.jar 或 -Djava.ext.dirs 指定的目录下的 jar 包 |
| ⬆ | ||
| AppClassLoader | ⇨ | 加载 classpath 中指定的 jar 包及 -Djava.class.path 所指定目录下的类和 jar 包 |
| ⬆ | ||
| Custom ClassLoader | ⇨ | 通过 java.lang.ClassLoader 的子类自定义加载 class ,属于应用程序根据自身需要自定义的 ClassLoader,如 tomcat,jboss 都会根据 j2ee 规范自行实现 ClassLoader |
public class Test
{
public static void main(String[] args) {
//classpath下的类加载器 AppClassLoader
System.out.println(new Test().getClass().getClassLoader());
//ExtClassLoader
System.out.println(new Test().getClass().getClassLoader().getParent());
//BootStrap Classloader c语言实现,java层面上无法显示
System.out.println(new Test().getClass().getClassLoader().getParent().getParent());
}
}
打印结果
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@39a054a5
null
双亲委派机制
如果当前加载器存在父加载器,则委派给父加载器去寻找类对象,直至顶层。父类中不存在该类,则有自身去寻找并加载,若都找不到该类,则返回 ClassNotFoundException 类型错误。
保存已经被虚拟机加载的类型信息、常量、静态变量、及时编译后的代码缓存等数据。
方法区在虚拟机启动时创建,所有线程共享,生命周期与 java 进程所绑定。
运行时常量池在方法区中
存储对象信息,线程共享
Java 栈Java栈中存放的是一个一个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。
动态链接:指向当前方法所属的类的运行时常量池的引用。符号引用在初始化或者第一次使用时被转化为直接引用。符号引用是指在编译过程中,被引用的变量、方法、类等信息还没有被加载到虚拟机中,所以只能以字符串性质的描述存放在 class 文件的常量池中。动态链接就是为了让这些符号引用转化为可以调用的直接引用。
操作数栈:在指令进行过程中,会将局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者。
方法返回地址:存放调用该方法的程序计数器的值。方法正常执行,将返回该方法的调用者的下一条指令的地址。
本地方法栈本地方法栈则是为执行本地方法服务的。在JVM规范中,并没有对本地方法栈的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。
程序计数器又叫PC寄存器,保存程序当前执行的指令的地址(也可以说保存下一条指令的所在存储单元的地址),当CPU需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令。
因为一个CPU在同一时刻只能处理一个指令,因此多线程的实现实质上是在多个线程之间频繁切换从而实现并行进行实现的。通过记录当前线程的下一个指令的地址,从而实现在切换回当前线程的时候 JVM 能够知道该执行什么指令。



