首页 > 技术文章 > 类加载器深入解析

yantt 2020-05-08 16:47 原文

类加载

  • 在JAVA代码中,类型(指类的本身比如class、interface、枚举,不代表对象比如new出来的实例)的加载(其中一种为:将已经存在的类class文件从磁盘上加载到内存中)、连接(将类与类关系处理好)与初始化过程(类的静态变量赋值)都是在程序运行期间RUNTIME )完成的。
  • 提供更大的灵活性,增加更多的可能性。

类的加载、连接与初始化

  • 加载: 查找并加载类的二进制数据
  • 连接
    • 验证:确保被加载的类的正确性
    • 准备: 为类的静态变量分配内存,并将其初始化为默认值
    • 解析:把类中的符号引用转换为直接引用
      注: 调用方法 hello() ,这个方法的地址是0xaab,那么hello就是符号引用,0xaab 就是直接引用。在解析阶段,虚拟机会把所有的类名、方法名、字段名这些符号引用替换为具体的内存地址或偏移量。
  • 初始化:为类的静态变量赋予正确的初始值

类的使用与卸载

  • 使用 : 类的实例进行调用
  • 卸载 : 类的卸载会在内存中卸载相关的内存空间

类的加载、连接与初始化

  • Java程序对类的使用方式分为两种
    - 主动使用(七种)
    (1) 创建类的实例
    (2) 访问某个类或接口的静态变量,或者对该静态变量赋值
    (3) 调用类的静态方法
    (4) 反射
    (5) 初始化类的子类
    (6) Java虚拟机启动时被标明为启动类的类
    (7)JDK1.7 开始提供的动态语言支持:
    java.lang.invok.MethodHandle实例的解析结果REWF_getStatic,REF——putStatic
    注:除了上述七种情况,其他使用Java类的方式都被看作为被动使用,都不会导致类的初始化
    • 被动使用
  • 所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们
类的加载

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区内,然后在内存中创建一个java.lang.Class对象(规范并未说明Class对象位于哪里,HotSpot虚拟机将其放在了方法区)用来封装类在方法区内的数据结构

加载.class文件方式
  • 从本地系统中直接加载
  • 通过网络下载.class文件
  • 从zip、jar等归档文件中加载.calss文件
  • 从专有数据库中提取.class文件
  • 将Java源文件动态编译为.class文件 (动态代理 AOP)

范例一

class MyTest {
	public static void main(String[] args) {
		System.out.println(MyChild.str);
	}

}

class MyParent {
		public static String str = "This is MyParent's str";
		static {
			  System.out.println("This is MyParet.");
		}
}
	
class MyChild extends MyParent  {
		public static String str1 = "This is MyChild's str1";
		static {
			  System.out.println("This is MyChild.");
		}
}

执行结果

This is MyParet.
This is MyParent's str

(1)对于静态变量来说,只有直接定义了该变量的类才会被初始化
(2)上述代码只有主动使用了父类的静态变量并没有主动使用子类MyChild的静态变量;
(3)上述讲到Java虚拟机只有对于主动使用接口和类的时候才会初始化他们;
(4)所以虽然主动调用了MyChild.str 但是由于调用的是父类MyParent的静态变量,所以只会初始化父类MyParent。

范例二

class MyTest {
	public static void main(String[] args) {
		System.out.println(MyChild.str1);
	}

}

class MyParent {
		public static String str = "This is MyParent's str";
		static {
			  System.out.println("This is MyParet.");
		}
}
	
class MyChild extends MyParent  {
		public static String str1 = "This is MyChild's str1";
		static {
			  System.out.println("This is MyChild.");
		}
}

执行结果

This is MyParet.
This is MyChild.
This is MyChild's str1

(1)由于调用的是子类的str1,所以子类会被初始化
(2)上述主动使用第五种方式:初始化一个类的子类,该类也会被初始化,所以MyParent由于子类MyChild被初始化了会先初始化。
(3)当一个类在初始化时,要求其父类全部都已经初始化完成。

本篇文章主要是整理学张龙老师的深入理解JVM课程的学习笔记

推荐阅读