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、AppClassLoader3种类加载器相互配合执行的,在必要时,还可以自主实现类加载器,来定制类的加载方式 - 自定义类加载器功能:
- 隔离加载类
- 引入多个框架或中间件时,防止同名同路径的类冲突,通过自定义类加载器仲裁,防止冲突
- 修改类加载的方式
- 需要时加载、动态加载
- 扩展加载源
- 扩展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_infoCONSTANT_Fieldref_infoCONSTANT_Methodref_info
Initialization
Initialization初始化阶段就是执行<clinit>()类构造器方法不是类的构造器的过程当有静态变量赋值,静态代码块赋值时才会有clinit<clinit>()类构造器方法不需要定义,是javac编译器自动收集类中的所有类静态变量的赋值动作和静态代码块中的语句合并而来<clinit>()类构造器方法中指令按语句在源文件中出现的顺序执行<clinit>()类构造器方法不同于类的构造器,类的构造器在jvm视角下是<init>()- 若A类具有父类B类,
jvm会保证B类的<clinit>()执行完毕后,A类的<clinit>()立即执行 jvm必须保证一个类的<clinit>()方法在多线程下被同步加锁。类只会被加载一次,并放置到method areaJDK8为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: