你了解Java的类加载过程吗?

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_43847987/article/details/101059855

一个java文件编译到最后执行的过程:
编译:就是将java文件通过javac命令编译成字节码,也就是.class文件
运行:即使将编译结果.class文件交给虚拟机执行
类加载过程:是指JVM虚拟机把.class文件中的类信息加载进内存,并进行解析生成class对象的过程
类加载的主要过程:
加载,链接——验证、准备、解析,初始化

一、加载
加载指的是把class文件字节码文件从各个来源通过类加载器装载入内存,在加载阶段主要完成三件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口

字节码的来源
一般加载来源包括从本地路径下编译生成的.class文件,jar包中.class文件,从数据库中读取,从远程网络获取(Applet),以及动态代理实时编译。

类加载器
包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。

二、链接
验证
确保Class文件的字节流把贫寒的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

  1. 对文件格式的验证:验证字节流是否符合Class文件格式的规范——是否以魔数开头,主次版本号是否在当前虚拟机处理范围之内。
  2. 元数据验证:对字节码描述的信息进行语义分析——这个类是否有父类? 这个类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突。
  3. 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的——比如要保证类型转换的合理性。
  4. 符号引用验证:确保解析动作正常执行——符号引用中通过字符串描述的全限定名是否能找到对应的类,在指定类是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。

准备
正式为类变量分配内存并设置类变量的初始值——这些变量所使用的内存都是在方法区中进行分配。
注:这里进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。
这里所说的初始值“通常情况”下是数据类型的零值
比如八种基本数据类型的初值都默认为0;boolean默认为false,对象引用为null
例如:

public static int num=12;

num这个变量在准备阶段过后的初始值为0,而不是12

既然有通常情况,就会有特殊情况
如果类字段属性表中ContantsValue属性(被final修饰),那在准备阶段变量值会被初始化为指定的值,例如:

public static final int num=12;

此时在准备阶段值就为12

解析

将常量池的符号引用替换为直接引用的过程

  • 符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。
  • 直接引用:直接引用是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用目标必定已经在内存中存在。

三、初始化
真正开始执行类中定义的java程序代码。
这个阶段主要是对类变量初始化,是执行类构造器的过程。

虚拟机对5种情况必须立即对类进行初始化

  • 遇到new,getstatic,putstatic或invokestatic这4条字节码指令时
  • 使用java.lang.reflect包的时候对类进行反射调用的时候
  • 初始化一个类的时候,其父类尚未初始化,则优先初始化其父类
  • 当虚拟机启动时,用户需要指定一个要执行的主类,会先初始化这个主类
  • 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

不进行初始化的情况:

  • 通过子类引用父类的静态字段,只会初始化父类,不会初始化子类。
  • 通过数组定义来引用类。
  • 调用类的常量

猜你喜欢

转载自blog.csdn.net/weixin_43847987/article/details/101059855