类加载时机
- 使用new关键字实例化对象时;
- 读取或设置一个类型的静态字段时(被 final 修饰,已在编译期把结果放入常量池的静态字段除外);
- 调用一个类的静态方法时。
类加载过程
1. 加载
- 通过一个类的全限定名来获取定义此类的二进制字节流 ;
- 将这个字节流所代表的静态存储结构转换为运行时数据结构;
- 在内存中生成一个代表这个类的java.lang.Class对象,作为这个类的各种数据的访问入口。
2. 验证
- 文件格式验证:验证字节流是否符合 Class 文件格式的规范;
- 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java 语言规范》的要求(如除了java.lang.Object外,所有的类都应该有父类);
- 字节码验证:通过数据流分析和控制流分析,确定程序语义是合法的,符合逻辑的(如允许把子类对象赋值给父类数据类型,但不能把父类对象赋值给子类数据类型);
- 符号引用验证:验证类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。如果无法验证通过,则会抛出一个java.lang.IncompatibleClassChangeError的子类异常,如java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。
3. 准备
4. 解析
- 符号引用:符号引用用一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
- 直接引用:直接引用是指可以直接指向目标的指针、相对偏移量或者一个能间接定位到目标的句柄。
5. 初始化
- ()方法由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生,编译器收集顺序由语句在源文件中出现的顺序决定。
- ()方法与类的构造方法(即在虚拟机视角中的实例构造器()方法)不同,它不需要显示的调用父类的构造器,Java 虚拟机会保证在子类的()方法执行前,父类的()方法已经执行完毕。
- 由于父类的()方法先执行,也就意味着父类中定义的静态语句块要优先于子类变量的赋值操作。
- ()方法对于类或者接口不是必须的,如果一个类中没有静态语句块,也没有对变量进行赋值操作,那么编译器可以不为这个类生成()方法。
- 接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成()方法。
- Java 虚拟机必须保证一个类的()方法在多线程环境中被正确的加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的()方法,其他线程都需要阻塞等待。
类加载器
双亲委派模型
- 启动类加载器:启动类加载器(Bootstrap ClassLoader)由 C++ 语言实现(以 HotSpot 为例),它是虚拟机自身的一部分;
- 其他所有类的类加载器:由 Java 语言实现,独立存在于虚拟机外部,并且全部继承自java.lang.ClassLoader。
- 启动类加载器 (Boostrap Class Loader):负责把存放在\lib目录中,或被-Xbootclasspath参数所指定的路径中存放的能被 Java 虚拟机识别的类库加载到虚拟机的内存中;
- 扩展类加载器 (Extension Class Loader):负责加载\lib\ext目录中,或被java.ext.dirs系统变量所指定的路径中的所有类库。
- 应用程序类加载器 (Application Class Loader):负责加载用户类路径(ClassPath)上的所有的类库。
模块化下的类加载器
- 仍维持三层类加载器和双亲委派的架构,但扩展类加载器被平台类加载器所取代;
- 当平台及应用程序类加载器收到类加载请求时,要首先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载;
- 启动类加载器、平台类加载器、应用程序类加载器全部继承自java.internal.loader.BuiltinClassLoader,BuiltinClassLoader 中实现了新的模块化架构下类如何从模块中加载的逻辑,以及模块中资源可访问性的处理。
面试题
简述JVM类加载过程
- 通过全类名获取类的二进制字节流。
- 将类的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成类的Class对象,作为方法区数据的入口。
简述JVM中的类加载器
- BootstrapClassLoader启动类加载器:加载/lib下的jar包和类。 由C++编写。
- ExtensionClassLoader扩展类加载器: /lib/ext目录下的jar包和类。由Java编写。
- AppClassLoader应用类加载器,加载当前classPath下的jar包和类。由Java编写。
简述双亲委派机制
双亲委派机制的优点
- 避免类的重复加载。相同的类被不同的类加载器加载会产生不同的类,双亲委派保证了Java程序的稳定运行。
- 保证核心API不被修改。
- 如何破坏双亲委派机制
- 重载loadClass()方法,即自定义类加载器。
如何构建自定义类加载器
以上就是这个博客的全部内容了!!!