面试题-请你谈谈Java的类加载过程
阅读原文时间:2021年04月20日阅读:1

什么是Java的类加载过程

一个Java文件从编码完成到最终执行,一般主要包括编译和运行两个过程
编译:将Java文件通过javac命令编译成class文件(字节码)
运行:将class文件交给JVM执行

类加载过程:JVM将字节码中类信息加载进内存,并解析生成对象的class对象的过程(JVM不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个需要运行的类时才会加载,而且只加载一次)

Java的类加载过程

Java类加载的过程主要分为加载、链接、初始化三个部分,而链接又可以细分为验证、准备、解析

加载

加载是指把class字节码文件从各个来源通过类加载器装载入内存,主要完成以下三件事:

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

class字节码来源:

  1. 本地路径下编译生成的class文件
  2. jar、war、ear包中的class文件
  3. 远程网络调用的class文件
  4. 动态代理实时编译的class文件

类加载器:

  1. 启动类加载器
  2. 扩展类加载器
  3. 应用类加载器
  4. 用户自定义类加载器

什么是类加载器?
通过一个类的全限定名来获取描述此类的二进制字节流,这个动作是在JVM外部实现的,实现这个动作的代码模块称为类加载器(光碟=类,光驱=类加载器);对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在JVM中的唯一性,每一个类加载器都拥有一个独立的类名称空间(两个类的内容相同,但类加载器不同,则这两个类就是不同的)。

为什么需要自定义类加载器?

  1. 由于Java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载
  2. class文件来源可能不标准,需要自己实现一个类加载器,从指定源进行加载、

验证

作用:保证加载进来的字节流符合JVM规范,防止造成安全错误
内容:

  1. 对文件格式的验证:常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?
  2. 对元数据的验证:该类是否继承了被final修饰的类?类中的字段、方法是否与父类冲突?是否出现了不合理的重载?
  3. 对字节码的验证:类型转换时候合理?
  4. 对符号引用的验证:校验符号引用中通过全限定名是否能找到对应的类?校验符号引用中的访问性是否可被当前类访问?

准备

准备主要是为类变量分配内存,并赋予初始值(并非代码中赋予的值,而是JVM根据不同变量类型的默认初始值,但对于常量类或枚举,会赋予实例化对应的值)

解析

解析是将常量池内的符号引用替换为直接引用的过程
符号引用:能够唯一性识别一个方法、变量、类的相关信息的字符串
直接引用:一个内存地址或者一个偏移量
如:调用地址为0x2019的hello()方法,hello为符号引用,0x2019为直接引用

初始化

初始化主要完成对类变量的初始化,也就是执行类构造器的过程
该类的父类尚未初始化,则优先初始化其父类
该类包含多个静态变量和静态代码块,则按照自上而下的顺序一次执行

双亲委派机制

双亲委派机制是指如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,当父类加载器反馈自己无法完成这个请求时,子类加载器才会尝试自己去加载完成。

好处:维护了类环境的稳定和高效运转

实现:在类加载过程中,先检查请求的类是否已经被加载过了,如果没有就调用父类的加载器加载,如果父类加载器为null,就默认使用启动类加载器作为父类加载器,如果父类加载失败,就会抛出classNotFoundException类,再调用自己的findClass方法进行加载