1 类加载
Java代码编译成Class文件后,需要放到内存中运行,而把Class文件读取到内存的操作就是类加载器所做的事情。
类加载器加载Class的步骤如下:
-
装载:查找并加载类的二进制数据
-
链接:
-
验证:确保被加载类的正确性
-
准备:为类的静态变量分配内存,并将其初始化为默认值
-
解析:把类中的符号引用转换为直接引用
-
-
初始化:为类的静态变量赋予正确的初始值
装载->验证->准备->初始化这几个阶段的开始顺序是确定的,解析阶段则不一定。
对于初始化阶段,在如下情况会进行初始化:
-
遇到new、getstatic、putstatic、invokestatic这4条字节码指令时
-
对类进行反射调用时(Class.forName(“com.mchen.load”))
-
初始化一个类的子类(会首先初始化子类的父类)
-
JVM启动时标明的启动类(java.exe执行的类)
-
java.lang.invoke.MethodHandle解析的类
注意:
-
Class.forName()
得到的class是已经初始化完成的,当然也可以指定Class.forName
未初始化的重载方法。 -
Classloader.loaderClass
得到的class是还没有链接(验证,准备,解析三个过程被称为链接,那也不执行初始化)的,同样Classloader.loaderClass
也可以指定执行初始化的重载方法。
2 双亲委派
从JDK1.2之后,类加载器引入了双亲委派模型,其模型图如下:
双亲委派使得JVM中分成两种类加载器,一种由C++实现的在JVM内部,没有父类,一种由Java实现,独立于虚拟机外部,继承java.lang.ClassLoader
类,都有父类。
当内加载器要去加载类时,它并不会自己先去加载,而是把这个请求委托给父类加载器去执行,如果父类加载器还存在父类加载器,则进一步向上委托,依次递归,请求最后到达顶层的启动类加载器,如果父类能够完成类的加载任务,就会成功返回,倘若父类加载器无法完成任务,子类加载器才会尝试自己去加载,这就是双亲委派模式。就是每个儿子都很懒,遇到类加载的活都给它爸爸干,直到爸爸说我也做不来的时候,儿子才会想办法自己去加载。
JVM平台提供的ClassLoader:
-
Bootstrap ClassLoader:加载JVM自身工作需要的类,它由JVM自己实现。它会加载JAVA_HOME/jre/lib下的文件
-
ExtClassLoader:由sun.misc.Launcher.ExtClassLoader(parent属性为null,但是父类加载器是Bootstrap ClassLoader)实现,他会加载JAVA_HOME/jre/lib/ext目录中的文件(或由System.getProperty("java.ext.dirs")所指定的文件)。
-
AppClassLoader:由sun.misc.Launcher.ExtClassLoader实现,他会加载JAVA_HOME/jre/lib/ext目录中的文件(或由System.getProperty("java.ext.dirs")所指定的文件)。
2.1 双亲委派的优点
采用双亲委派模式的好处就是Java类随着它的类加载器一起具备一种带有优先级的层次关系,通过这种层级关系可以避免类的重复加载,当父亲已经加载了该类的时候,就没有必要子类加载器(ClassLoader)再加载一次。其次是考虑到安全因素,Java核心API中定义类型不会被随意替换。
2.2 打破双亲委派
双亲委派并不是也一个强约束,相反由于如下三种原因需要打破双亲委派机制
-
为了兼容JDK1.2之前重写
ClassLoader#load
方法的自定义类加载器(现在已经不提倡了,提倡重写findClass
方法) -
为了扩展标准接口,如SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载,而 SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下,使用
Thread#setContextClassLoader
方法设置类加载器 -
为了实现热插拔,热部署,模块化,意思是添加一个功能或减去一个功能不用重启,只需要把这模块连同类加载器一起换掉就实现了代码的热替换。
网友评论