原文:https://www.cnblogs.com/dooor/p/5289994.html
参考自:https://blog.csdn.net/WantFlyDaCheng/article/details/81808064
1.JVM类加载过程
类加载到类被卸载过程包括7个阶段:
1.加载 通过类的全限定名把类文件的二进制流加入进来,通过这个字节流(这个二进制流也是我们代理类的方法),然后通过这个二进制流把静态存储结构转化为运行时方法区的结构(不包括类变量,类变量在准备阶段),在内 存中生成一个Class对象,作为方法区访问的入口。
2.验证 验证是验证Class文件的字节流包含的信息是否符合当前虚拟机的要求规范,防止恶意攻击、
3.准备 在方法区为类变量分配内存和设置初始值,这个时候的初始值是数据的0值,不是我们定义的值,如果是常量的话准备阶段就会设置为我们定义的值
4.解析 将符号引用(这里的符号引用指的是字面量的形式,只需要无歧义地定位到目标)替换为直接变量
5.初始化 类初始化 阶段是我们加载过程的最后一步,执行类构造器,合并static语句,有static的顺序决定。
6.使用
7.卸载
(一)类加载机制
JVM把class文件加载到内存,并对数据进行校验、准备,解析和初始化,最终形成JVM可以直接使用的java类型的全过程。
image.png
(1)加载
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与
class文件被加载之后,方法去会被分出一块内存,存储这个.class文件的所有信息,然而这个.class文件并不能被我们所直接利用
,我们还需要一个可以直接使用的对象,此时堆内存就会发挥作用了,当类的信息被加载在方法区之后
,JVM就会在堆当中创建一个java.lang.Class对象,这个对象就可以作为方法区数据的访问入口,即通过反射去获取类的信息
,实际上,我们每一次new一个对象的时候,底层就是java.lang.Class对象去完成的,一个类在JVM当中只能有一个java.lang.Class对象。
总结如下:
1.类的加载的最终产品是位于堆区中的Class 对象
2.Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口
image.png
(二)连接
(1)验证:确保加载的类信息符合JVM规范,没有安全方面的问题,一般情况由javac编译的class文件是不会有问题的,但是可能有人的class文件是自己通过其他方式编译出来的,这就很有可能不符合jvm的编 译规则,这一步就是要过滤掉这部分不合法文件
(2)准备: 正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法去中进行分配,设置的初始值为JVM默认的初始值,并非程序编写者所赋予的真正值,这里注意,只是为静态变量分配内存,此时是没有对象实例的,即成员变量此时不会被分配内存或赋初值
(3)解析:把类中的符号引用转化为直接引用(指针)
(三)初始化:
为类的静态变量赋予正确的初始值,上述的准备阶段为静态变量赋予的是虚拟机默认的初始值,此处赋予的才是程序编写者为变量分配的真正的初始值
(四)使用
(五)卸载
(二)类什么时候会被加载?
Java程序对类的使用方式可分为两种
– 主动使用
– 被动使用
当类被引用的加载,类只会加载一次
类的主动引用(一定会发生类的初始化)
new一个类的对象
调用类的静态成员(除了final常量)和静态方法
使用java.lang.reflect包的方法对类进行反射调用
当虚拟机启动,java Demo01,则一定会初始化Demo01类,说白了就是先启动main方法所在的类
当初始化一个类,如果其父类没有被初始化,则先初始化它父类
类的被动引用(不会发生类的初始化)
当访问一个静态域时,只有真正声名这个域的类才会被初始化
通过子类引用父类的静态变量,不会导致子类初始化
通过数组定义类的引用,不会触发此类初始化
引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)
class Test2{
public static final int n = 2;
static{
System.out.println("test");
}
}
public class Test1 {
public static void main(String[] args) {
System.out.println(Test2.n);
}
}
正确答案是2 。 Test2中的静态代码块是没有运行的,因为Test2并没有初始化
注意,这里的final关键字不可缺少,我们知道变量被关键字fianl修饰之后就不可修改,亦即此变量相当于编译期常量(是相当于并非就是常量),常量在java编译期已经确定,不需要初始化,
但是把fianal去掉,或者把 final int n = 2 改为 final int n = new Random()。,运行的结果将变为 test ,2 ,因为n的值为变量或者n值在编译期不能确定,就必须经过初始化(运行期)才能使用n的值。
image.png
网友评论