Class Loader Subsystem
Class Loader Subsystem
类加载子系统负责从文件系统或者网络中加载class
文件,class
文件在文件开头有特定的文件标识。ClassLoader
只负责class
文件的加载,能否运行由Execution Engine
决定。
加载的类信息存放于Method Area
方法区,Method Area
中还会存放run-time constant pool
运行时常量池信息final
,字符串字面量和数字常量这部分常量信息是class
文件中常量池部分的内存映射
加载过程
类加载特性和机制
Loading
- 通过一个类的全限定名全类名获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为
Method Area
的运行时数据结构 - 在内存中创建一个代表这个类的
java.lang.Class
对象,作为访问这个类的数据的访问入口 - 加载
.class
文件方式- 从本地文件系统直接加载
- 通过网络获取,典型场景
Web Applet
- 从压缩包中读取,
jar
包,war
包基础 - 运行时计算生成,动态代理技术
- 由其他文件生成,
JSP
应用 - 从专有数据库中提前
.class
文件,比较少见 - 从加密文件中获取,防止反编译的保护措施
ClassLoader
JVM规范:所有派生于抽象类java.lang.ClassLoader
的ClassLoader
都划分为User-Defined ClassLoader
自定义类加载器
JVM支持两种 ClassLoader
Bootstrap ClassLoader
引导类加载器,c
和c++
实现,嵌套在jvm
内部User-Defined ClassLoader
自定义类加载器,java
实现,派生于抽象类java.lang.ClassLoader
常见类加载器:
|
|
各种类加载器不是上下层关系,也不是父子类的继承关系,是包含关系,类似a文件夹里有b和c这种包含关系
Bootstrap ClassLoader
引导类加载器:c
和c++
实现,嵌套在jvm
内部- 加载
java
核心库JAVA_HOME/jre/lib/rt.jar、resources.jar
或sun.boot.class.path
路径下的内容用于提供JVM
自身需要的类 - 没有
parent
父类加载器。 - 加载
ExtClassLoader
扩展类加载器和AppClassLoader
系统类加载器,并指定他们的parent
父类加载器 - 处于安全考虑,
Bootstrap ClassLoader
只加载包名为java、javax、sun
等开头的类
User-Defined ClassLoader
自定义类加载器ExtClassLoader
扩展类加载器。ClassLoader extClassLoader = systemClassLoader.getParent();
- java实现。实现位置
sun.misc.Launcher
内静态内部类static class ExtClassLoader extends URLClassLoader
parent
父类加载器为Bootstrap ClassLoader
- 从
java.ext.dirs
系统属性所指定的目录中加载类库 - 从JDK的安装目录
jre/lib/ext
子目录(扩展目录)下加载类库,如果用户创建的jar放在此目录下,也会自动由ExtClassLoader
加载
- java实现。实现位置
SystemClassLoader
系统类加载器或称为应用程序类加载器AppClassLoader
。ClassLoader systemClassLoader=ClassLoader.getSystemClassLoader();
- java实现。实现位置
sun.misc.Launcher
内静态内部类static class AppClassLoader extends URLClassLoader
parent
父类加载器为ExtClassLoader
扩展类加载器- 负责加载环境变量
classpath
或系统属性java.class.path
指定路径下的类库 - 程序中的默认类加载器,一般java应用的类都是由其来完成加载
- 通过
CLassLoader.getSystemClassLoader()
可以获取到该类加载器
- java实现。实现位置
User Defined Class Loader
用户自定义类加载器- java日常应用程序开发中,类加载几乎是由
Bootstrap ClassLoader、ExtClassLoader、AppClassLoader
3种类加载器相互配合执行的,在必要时,还可以自主实现类加载器,来定制类的加载方式 - 自定义类加载器功能:
- 隔离加载类
- 引入多个框架或中间件时,防止同名同路径的类冲突,通过自定义类加载器仲裁,防止冲突
- 修改类加载的方式
- 需要时加载、动态加载
- 扩展加载源
- 扩展class字节码文件的来源方式。本地文件系统、网络、数据库等
- 防止源码泄漏
- 通过自定义类加载器加载加密过的class字节码。分发加密class字节码文件,从而防止反编译。
- 隔离加载类
- 自定义类加载器实现:
- 开发人员可以通过继承抽象类
java.lang.Classloader
类的方式实现自己的类加载器 JDK1.2
之前,在自定义类加载器时,总会去继承ClassLoader
类并重写loadClass
方法,来实现自定义的类加载器JDK1.2
之后,不再建议用户去重写loadClass方法,建议把自定义的类加载逻辑写在findClass
方法中- 在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承
URLClassLoader
类,这样就可以避免自己去编写findClass
方法及其获取字节码流的方式,使自定义类加载器编写更简洁
- 开发人员可以通过继承抽象类
- java日常应用程序开发中,类加载几乎是由
Linking
Verify
- 确保
class
文件的字节流中包含的信息符合当前jvm
要求,保证被加载类的正确性,保证不会危害jvm
自身安全 - 验证方式
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
Prepare
- 为类变量分配内存并且设置该类变量的默认初始值
static int iClass=1;
此时赋默认初始值iClass=0
- 不包含用
final
修饰的static
静态常量,因为final
在编译时分配,prepare
阶段会显示初始化 - 这里不会为实例变量分配初始化,类变量会分配在
Method Area
,而实例变量随着对象一起分配到Heap Area
Resolve
- 将
class文件的常量池
内的symbolic reference
符号引用转换为直接引用的过程。virtual method table
虚方法表创建并开始初始化 - 事实上,
Resolve
解析操作往往会伴随JVM
在执行完Initialization
初始化之后再执行 symbolic reference
符号引用就是一组符号来描述所应用的目标,symbolic reference
符号引用的字面量形式明确定义在jvm规范的class文件格式中。 直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄Resolve
解析主要针对类、接口、字段、类方法、接口方法、方法类型等。对应class文件的常量池
CONSTANT_Class_info
CONSTANT_Fieldref_info
CONSTANT_Methodref_info
Initialization
Initialization
初始化阶段就是执行<clinit>()
类构造器方法不是类的构造器的过程当有静态变量赋值,静态代码块赋值时才会有clinit
<clinit>()
类构造器方法不需要定义,是javac
编译器自动收集类中的所有类静态变量的赋值动作和静态代码块中的语句合并
而来<clinit>()
类构造器方法中指令按语句在源文件中出现的顺序执行<clinit>()
类构造器方法不同于类的构造器,类的构造器在jvm视角下是<init>()
- 若A类具有父类B类,
jvm
会保证B类的<clinit>()
执行完毕后,A类的<clinit>()
立即执行 jvm
必须保证一个类的<clinit>()
方法在多线程下被同步加锁。类只会被加载一次,并放置到method area
JDK8为metadata
双亲委派机制
JVM对class文件采用的是按需加载的方式,当需要使用该类时才会将它的class文件加载到内存,生成Class对象
JVM采用双亲委派机制,即把加载类的请求交给parent
父类加载器处理,是一种任务委派模式
工作原理
- 如果一个类加载器A收到了类加载的请求,并不会立即去执行加载,而是把这个请求委托给
parent
父类加载器B加载 - 如果该
parent
父类加载器B还存在parent
父类加载器C,则类加载器C继续向上委托,直到请求到达顶层的BootstrapClassloader
- 如果
parent
父类加载器X可以完成类加载任务,则成功加载返回。如果parent
父类加载器X不能完成加载任务,子加载器才会尝试自己去加载
优势:
- 避免重复加载类
- 保护程序安全,防止核心API被随意篡改:
- 自定义
java.lang.String.main():Error: Main method not found in class java.lang.String
- 自定义类
java.lang.MyBougainvillea:Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
- 自定义
- 类隔离说明:
A1,A2
类 由ExtClassLoader
加载;B1,B2
类 由AppClassLoader
加载。- B1 可以访问 A1 A2 B2
- B2 可以访问 A1 A2 B1
- A1 可以访问 A2
- A2 可以访问 A1
沙箱安全机制
- 自定义
java.lang.String
类,加载自定义String类的时候根据双亲委派原则会率先使用BootstrapClassLoader
加载 - 此时加载的是
jdk
自带的rt.jar
包中java\lang\String.class
报错信息说没有main方法是因为rt.jar
下的String
类没有main
方法 - 这样保证对
java
核心源代码的保护这就是沙箱安全机制
类的主动使用和被动使用
判断JVM中两个Class对象是否为同一个类
- 类的完整类名必须一致,包括包名
- 加载这个类的
ClassLoader
指ClassLoader
实例对象必须相同 在JVM
中即使这两个类对象Class
对象来源同一个class文件,被同一个jvm
所加载,但只要加载他们的ClassLoader实例对象
不同,那么这两个类对象Class
对象也是不相等==
的
JVM
必须知道一个类型是由BootstrapClassLoader
加载还是由用户类加载器
加载
如果一个类型是由用户类加载器
加载,那么JVM
会将这个用户类加载器
的一个reference
引用作为类型信息
的一部分保存在Method Area
中
Dynamic Linking
动态链接解析一个类型
到另一个类型
的reference
引用的时候,JVM
需要保证这两个类型
的类加载器
是相同的
主动使用
- 类会执行
Initialization
阶段- 创建类的实例
- 访问某一个类或接口的静态变量或者对静态变量赋值
- 调用类的静态方法
- 反射:
Class.forName("org.bougainvillea.jvm.Demo")
- 初始化一个类的子类加载一个类的时候其父类会先初始化
JVM
启动时被标明为启动类的类JDK7
开始提供的动态语言支持java.lang.invoke.MethodHandle
实例的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic
句柄对应的类没有初始化则初始化
被动使用
- 类加载阶段不会执行 Initialization 阶段。比如:静态内部类。
- 除了主动使用的七种情况,其他使用java类的方式都被看作是对类的被动使用,都不会导致类的初始化。
Tags: