深入理解JVM-各个阶段的作用
JAVA程序运行的步骤
Java程序的运行的步骤一般如下
- 编写
myapp.java
- 编译
javac myapp.java
- 执行
java myapp
- 执行命令后会启动JVM执行你的程序,第一步先将你的myapp.class加载到进来即ClassLoad,其中ClassLoad又分为五个步骤,加载,验证,准备,解析,初始化。将程序的Class文件以及静态变量加载到方法区。然后在堆内创建对象。
- 执行引擎通过main方法运行
JVM架构
整体的架构图如下。下面具体介绍每一步的具体作用
class loader
我们所说的类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。
举个通俗点的例子来说,JVM在执行某段代码时,遇到了class A, 然而此时内存中并没有class A的相关信息,于是JVM就会到相应的class文件中去寻找class A的类信息,并加载进内存中,这就是我们所说的类加载过程。
由此可见,JVM不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个需要运行的类时才会加载,且只加载一次
ClassLoad分为五个步骤,加载,验证,准备,解析,初始化
load
加载阶段是整个类加载的第一步,需要完成三件事情
- 通过一个类的全名获取定义此类的二进制流
- 将这个二进制流所代表的的静态存储结构转化为方法区运行时数据
- 在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的入口
我们可以从各个位置获取到二进制流,所以load阶段不仅局限于jar。还包括socket jsp proxy等,只要能获取到二进制流都可以。如果要实现自己的类加载方法,需要继承ClassLoader,重写loadClass 或者findClass方法。
源码如下
基本上都是调用一些Native方法。
后面会细说类加载以及双亲委派模型
加载的目的,是把class字节码文件从各个来源通过类加载器装载入内存中。
这里有两个重点:
字节码来源。一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译
类加载器。一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。
vertify
验证的目的是确保Class文件的字节流中包含的信息全部符合java虚拟机规范。验证阶段大致分为四个阶段的动作
- 文件格式验证
保证输入的字节流能够正确的解析,并存储与方法区之内,只有通过了这个验证字节流才能被允许存储在方法区,后面的验证直接在方法区中的存储上,不会再操作字节流了 - 元数据验证
主要是验证语义,如是否有父类,是否能被继承,是不是抽象类等。 - 字节码验证
验证方法体。保证方法能正确执行。 - 符号引用验证
确保resolve能够正确运行,resolve主要是将字符引用转换为直接引用。见下面
prepare
初始化一些类变量,不是实例变量。实例变量将会在对象实例化的时候随对象一起分配在java堆中。而且只是初始化默认值,即0 ,0.0, null不会赋值,以及一些常量值如final 修饰的。
resolve
将符号引用转换为直接引用。可以理解为将java代码内部的调用过程,转为JVM内部的指向。比如我在代码里写了个new Hello();那么即是符号引用,转为直接引用变为了这个Hello对象在内存中的位置。如果类未被加载,则先加载
initialize
这是类加载的最后阶段,这里所有的静态变量会被赋初始值, 并且静态块将被执行
类加载器
Runtime data areas
execution engine
执行字节码,与本地方法交互,不同的平台有不同jdk,即不同的本地方法