类加载机制
Java 类加载机制
Java 虚拟机一般使用 Java 类的流程为:首先将开发者编写的 Java 源代码(.java文件)编译成 Java 字节码(.class文件)(classLoad.defineClass方法执行的),然后类加载器会读取这个 .class 文件(通过类的名字)加载到jvm内存中(classLoad.loadClass方法执行的),对其字节码分析并转换成 java.lang.Class 的实例。有了该 Class 实例后,Java 虚拟机可以利用 newInstance 之类的方法创建其真正对象了。
类加载五个阶段
1) 加载:(加载时机?)
在加载阶段,虚拟机主要完成三件事:
1.通过一个类的全限定名来获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区域的运行时数据结构。
3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区域数据的访问入口
2) 验证:检查夹加载的class文件的正确性。
验证阶段作用是保证Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害。
如果验证失败,就会抛出一个java.lang.VerifyError异常或其子类异常。
验证过程分为四个阶段:
1.文件格式验证:验证字节流文件是否符合Class文件格式的规范,并且能被当前虚拟机正确的处理。
2.语义验证:是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言的规范。
3.字节码验证:主要是进行数据流和控制流的分析,保证被校验类的方法在运行时不会危害虚拟机。
4.符号引用验证:符号引用验证发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在解析阶段中发生。
3) 准备;给类中的静态变量分配内存空间。(final修饰的赋值真实的值 非final设置0)
准备阶段为变量分配内存并设置类变量的初始化。在这个阶段分配的仅为类的变量(static修饰的变量),而不包括类的实例变量。对已非final的变量,JVM会将其设置成“零值”,而不是其赋值语句的值:
pirvate static int size = 12; 那么在这个阶段,size的值为0,而不是12。 final修饰的类变量将会赋值成真实的值。
4) 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址。
主要包括四种类型引用的解析:类或接口的解析、字段解析、方法解析、接口方法解析。
5) 初始化:对静态变量和静态代码块执行初始化工作。
在准备阶段,类变量已经经过一次初始化了,在这个阶段,则是根据程序员通过程序制定的计划去初始化类的变量和其他资源。这些资源有static{}块,构造函数,父类的初始化等

什么时候触发类的加载
1.遇到new,getstatic,putstatic,invokestatic这4条指令;
2.使用java.lang.reflect包的方法对类进行反射调用;
3.初始化一个类的时候,如果发现其父类没有进行过初始化,则先初始化其父类(注意!如果其父类是接口的话,则不要求初始化父类);
4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类;
5.当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则先触发其类初始化;
由谁来加载
类的加载机制

类加载器的树状组织结构
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:
引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader() 来获取它。
除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader 类的方式实现自己的类加载器,以满足一些特殊的需求。
除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过 getParent() 方法可以得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器
总结:
1、类的加载过程采用委托模式实现(双亲委派)
2、每个 ClassLoader 都有一个父加载器。(如果没有找到父类 默认父类加载器是Bootstarp classloader)
3、类加载器在加载类之前会先递归的去尝试使用父加载器加载。如果没有找到父类 默认父类加载器是Bootstarp classloader)
4、虚拟机有一个内建的启动类加载器(bootstrap ClassLoader),该加载器没有父加载器,但是可以作为其他加载器的父加载器。
ClassLoader 是 Java 提供的类加载器,绝大多数的类加载器都继承自 ClassLoader,它们被用来加载不同来源的 Class 文件。
ClassLoader 中与加载类相关的方法 (比较重要的方法)
1. getParent() 返回该类加载器的父类加载器。
2.loadClass(String name) 加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。 (先去父类找是否有此类 如果父类不存在或者父类找不到 则找父类的父类Bootstarp classloader寻找类 如果都没有 则调用findClass(String name) 方法 )
3. findClass(String name) 查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。 findclass类 FileSystemClassLoader 的 findClass() 方法首先根据类的全名在硬盘上查找类的字节代码文件(.class文件),然后读取该文件内容,最后通过 defineClass() 方法来把这些字节代码转换成 java.lang.Class 类的实例。
4.findLoadedClass(String name) 查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class
5.defineClass(String name, byte[] b, int off, int len) 把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为 final 的。
(总结 如果开发者想破坏双亲委派模型 那么就重写loadClass方法 如果想使用双亲委派模型 那么只需要重写findClass方法就可以)
参考图

参考资料:
https://www.cnblogs.com/paddix/p/5268559.html
https://mp.weixin.qq.com/s?src=11×tamp=1572835712&ver=1953&signature=-LSXUuq4mKcEfo1-T0OEAKBpX1zpr2haU2-lJn8etnrQT5ddzOj7UMQ6BlRCZAFILBWHEKKyr*EF50q9GwXLYlOh7HB3cRa1m2cTA8i23KqIYP7CNNYHMPL-1a9zMsmV&new=1
重点:https://www.oschina.net/question/2548434_2168615

浙公网安备 33010602011771号