第7章-类加载机制

tech2023-10-18  99

第7章-类加载机制

类加载发生在虚拟机之外

1 概述

1.1 什么是类加载机制

虚拟机把描述类的数据从Class文件加载到内存中,并对数据进行校验、转换解析、和初始化,最终形成可以被java虚拟机直接使用的java类型,这就是类加载机制。

类加载机制发生在java程序运行阶段

1.2 类加载的时机

当JVM第一次主动引用类的时候,加载该类。但当是被动引用的时候并不会引发类的加载操作。也就是说,JVM对于类的加载,并不是一口气直接加载所有的类,只是在用到某个类的时候,才对此类进行加载。

1.3 主动引用和被动引用

主动引用

遇到new 、getstatic、putstatic、invokestatic字节码指令 new:实例化对象getstatic、putstatic:读取或者设置一个类的静态字段(被final修饰的除外,因为已经被放入了常量池)调用一个类的静态方法 对类进行反射调用的时候,如果类没有初始化,则需要先初始化。当初始化一个类,但其父类没有初始化,此时需要首先初始化父类。 接口却不同,接口初始化时,不需要提前对父类进行初始化,只有真正使用父接口的时候才初始化。 当虚拟机启动,执行一个主类(包含main方法的类),虚拟机需要先对主类进行初始化。JDK 1.7 动态语言支持:一个 java.lang.invoke.MethodHandle 的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic。

被动引用

通过子类引用父类静态字段,只会引发父类的初始化,不会引发子类的初始化。

package com.wangye.并发; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; class parentClass{ static { System.out.println("我是父类"); } static int value = 10; } class sonClass extends parentClass { static { System.out.println("我是子类"); } } public class CountExample { public static void main(String[] args) { System.out.println(sonClass.value); } } /* 我是父类 10 */

通过数组定义来引用类,不会发生初始化

package com.wangye.并发; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; class parentClass{ static { System.out.println("我是父类"); } static int value = 10; } class sonClass extends parentClass { static { System.out.println("我是子类"); } } public class CountExample { public static void main(String[] args) { parentClass [] parents = new parentClass[10]; Arrays [] arrays = new Arrays[10]; } }

常量被定义后,如果用ClassName.常量名不会引发类的初始化

因为常量在编译期间已经进入常量池

package com.wangye.并发; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; class parentClass{ static { System.out.println("我是父类"); } static final int value = 10; } public class CountExample { public static void main(String[] args) { System.out.println(parentClass.value); } }
总结

只有发生5种主动引用的时候,类才会被初始化。也就是说类初始化调入内存是lazy-load,并不是所有的类全部初始化调入内存。

2 类加载的过程

加载 --> 验证 --> 准备 --> 解析 --> 初始化 --> 使用 --> 卸载 |<------- 连接 ------->| |<------------- 类加载 ---------------->|

类加载主要分为加载、连接、初始化三个阶段。

加载

加载过程

通过一个类的全限定名来获取定义此类的二进制字节流将字节流中的数据转换为方法区的运行时数据结构(因为方法区存储类的相关信息)在内存中生成一个代表该类的Class对象,,作为方法区这个类的各种数据的访问入口(反射)

数组的加载和非数组的加载

非数组类的加载

java中的类加载器 启动类加载器扩展类加载器APP加载器 用户自己定义的类加载器

数组类的加载

数组不通过类加载器加载,而是由java虚拟机直接创建

HotSpot虚拟机中,class对象虽然是对象,但是他存储在方法区,而不是在heap。

验证
目的:确保.class文件中的字节流信息符合虚拟机的要求
准备
为类变量正式分配内存并设置类变量初始值的阶段,这时候使用的内存就是方法区的内存。此处进行分配的只有static修饰的变量,也就是类变量。此处不会处理实例变量,因为实例变量属于对象的信息,对象的信息都将存储在java Heap上。初始值通常就是数据类型的0值 public static int value = 123 准备阶段value为0,初始化之后value为123 public static final int value = 123 准备阶段value为123static final修饰的值为常量,赋值后便不再更改,所以说此时static final修饰的直接在准备阶段赋值,不会更改。
解析
将常量池中的符号引用替换成直接引用符号引用 符号引用只是一组符号来描述所引用的目标 方法名 、 变量名等 直接引用 直接指向目标内存地址的句柄或指针。
初始化
初始化阶段阶段是执行程序员意愿代码的阶段,此阶段,将显式的初始化类变量和其他资源。初始化阶段可以理解为就是执行类构造器clinit方法的过程clinit方法小结 clinit方法是类构造器的方法,clinit方法自动收集类中所有类变量的赋值动作和静态语句块合并而成。静态语句块只能访问其之前的变量,定义在之后的变量却不能访问,但是可以赋值。(非法向前引用)在执行子类的clinit方法时,总是父类的clinit方法优先执行,所有父类的静态语句块先于子类执行。但clinit方法对于类和接口来说都不是必须的,那是因为clinit只是和静态元素、静态语句有关,如果一个类或者接口中没有没有静态语句块、没有静态元素的赋值操作,则不会生成clinit方法接口却不需要先执行父类的clinit方法,而是用到谁执行谁。虚拟机在多线程的情况下,会对clinit加锁,能保证多个线程初始化此类时,只有一个线程去执行clinit。
类加载器

两个类相等

两个类来源于同一个Class文件被同一个虚拟机加载被同一个类加载器加载必须满足以上三个条件

类加载器的分类

启动类加载器扩展类加载应用程序类加载器用户自定义类加载器只有启动类加载器是C++编写的 属于虚拟机内部,其余三个都是java编写,独立于虚拟机外部,并且全部继承ClassLoader.

类加载器之间的关系

我们可以简单的把上下级之间的加载器理解为父子关系,但是这种父子关系并不是通过继承方式实现的,而是通过组合方式。可以查看源码。

双亲委派机制

工作原理

当前类加载器收到类加载的请求后,先不自己尝试加载类,而是先将请求类委派给父类加载器,如果父类还是无法处理,那就继续向上委托,直到启动类加载器。当父类加载器失败之后,才会交由当前类加载器进行加载因为安全性更高一点,子类加载器是没法加载父类加载器加载的类的,父类加载过的类不需要重复加载,这样防止恶意代码冒充java核心库,来兴风作浪。

源码

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
自定义类加载器
打破双亲委派机制
最新回复(0)