类加载子系统
-
虚拟机的类加载
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型
类加载的时机
-
对类立即进行初始化的四种情况(主动引用)
-
遇到new,getstatic,putstatic或invokestatic等四个指令时,如果未对齐进行初始化,则进行初始化
-
使用java.lang.reflect进行反射调用时,还未初始化
-
初始化时,父类未初始化,则对父类先进行初始化
接口在初始化时,不会要求其父接口全部完成初始化,只有在使用到父接口时,才会进行初始化
-
当虚拟机启动时,用户需要指定一个要执行的主类时
-
-
被动引用
-
通过子类引用父类的静态字段,不会导致子类初始化
-
通过数组定义来引用类,不会触发此类的初始化
-
常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
-
类加载的过程
类加载的全过程---加载,验证,准备,解析和初始化
-
加载(Loading)
-
通过一个类的全限定名获取定义此类的二进制文件流
-
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
-
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
-
加载.class文件的方式
-
从本地系统中直接加载
-
通过网络获取,典型场景,Web Applet
-
从zip压缩包中读取,成为日后jar,war格式的基础
-
运行时计算生成,使用最多的是:动态代理技术
-
由其他文件生成,典型场景:JSP应用
-
从专有数据库中提取.class文件,比较少见
-
从加密文件中获取,典型的防Class文件被反编译的保护措施
-
-
-
验证(Verify)
-
目的在于确保Class文件的字节流中包含信息符合当前虚拟机 要求,保证被加载类的正确性,不会 危害虚拟机自身安全
-
主要包括四种验证
-
文件格式验证
-
元数据验证
-
字节码验证
-
符号 引用验证
-
-
-
准备(Prepare)
-
为类变量分配内存并且设置该类变量的默认初始值
-
不包含用final修饰的static,因为final在编译的时候就会分配,准备阶段会显式初始化
-
不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中
-
-
解析(Resolve)
-
将常量池内的符合引用转换为 直接引用的过程。
-
解析操作往往会伴随着JVM在执行完初始化之后在执行。
-
符号引用就是一组符号 来描述所引用的目标。直接引用就是直接指向目标的指针,相对偏移量或一个间接定位到目标的句柄。
-
解析 动作主要针对类或接口,字段,类方法,接口方法,方法类型等。对应常量池中的CONSTANT_Class_info,CONSTANT_Fieldrer_info,CONSTANT_Methodref_info等
-
-
初始化
-
初始化阶段就是执行类构造器方法<clinit>()的过程。
-
此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
-
构造器方法中指令按语句在源文件中出现的顺序执行。
-
<clinit>()不同于类的构造器。
-
若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕。
-
虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁。
-
类加载器的分类
JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)
从概念上讲,自定义类加载器一般指的是程序中由开发人员自定义的 一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
虚拟机自带的加载器
-
启动类加载器(引导类加载器,BootstrapClassLoader)
-
这个类加载使用C/C++语言实现的,嵌套在JVM内部。
-
它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar,resources.jar或sun.boot.class.path路径下的内容),用于提供 JVM自身需要的类
-
并不继承java.lang.ClassLoader,没有父加载器。
-
加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
-
出于安全考虑,Bootstrap启动类加载器只加载包名为java,javax,sun等开头的类。
-
-
扩展类加载器(Extension ClassLoader)
-
Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
-
派生于ClassLoader类。
-
父类加载器为启动类加载器。
-
从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
-
-
应用程序类加载器(系统类加载器,AppClassLoader)
-
java语言编写,由sun.misc.Launcher$AppClassLoader实现。
-
派生于ClassLoader类。
-
父类加载器为扩展类加载器。
-
它负责加载环境变量classpath或系统属性java.class.path指定路径下单类库。
-
该类加载是程序中默认的类加载器 ,一般来说,Java应用的类都是由它来完成加载。
-
通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。
-
-
用户自定义类加载器
为什么要自定义类加载器?
-
隔离加载类
-
修改类加载的方式
-
扩展加载源
-
防止源码泄露
-
双亲委派机制
Java虚拟机对class文件采用的是按需加载的方式,既当需要使用该类时才会将它的class的文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,既把请求交由父类处理,它是一种任务委派模式。
工作原理
-
如果一个加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
-
如果父类加载器还存在其父类加载器,则进一步向上委托,一次递归,请求最终将到达顶层的启动类加载器 ;
-
如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式
优势:
避免类的重复加载
保护程序安全,防止核心API被随意篡改