双亲委派机制原理:如果一个类加载器收到了类加载的请求,该类加载器并不会立即去加载,而是把加载任务先委托给父加载器加载;如果父加载器还有父加载器,则会继续向上委托,一直委托到启动类加载器;如果父加载器可以完成加载Class字节码的任务,就成功加载返回,若需要加载的类不在父加载器加载范围内,子加载器尝试去加载,一直到向下委托到可以加载的加载器。
双亲委派的本质规定了类加载的顺序是:引导类加载器先加载,若加载不到,由扩展类加载器进行加载,若扩展类加载器也加载不到,才会由系统类加载器进行加载。
双亲委派的优势
双亲委派的弊端
双亲委派虽然保证了类加载的职责比较明确,但也带来一个问题:顶层的ClassLoader加载的类无法访问底层ClassLoader加载的类。比如在java的核心类库(一般由BootstrapClassLoader加载器加载)提供一个接口,该接口的实现由应用加载器进行加载,比如该接口绑定了一个工厂方法,用于创建该接口的实例,而接口和工厂方法都在启动类加载器中加载,实现却由应用加载器加载,这就会导致工厂方法无法创建由应用类加载器加载的应用实例的问题。
从上文中双亲委派的优势和弊端中可以看出,双亲委派机制固然有它的优势,但并不是所有类的加载行为都会遵守双亲委派机制的。
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//创建扩展类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//把扩展类加载器设置为应用类加载器的父加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//把应用类加载器设置为上下文加载器
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
第二种违反双亲委派机制行为也即热替换代码的实现。热替换指程序运行过程中修改了某个类不停止服务的行为,修改后的程序必须立即运行在系统之中。对于java来说并非天生支持热替换,当一个类已经加载到系统中,然后修改了类文件,无法让系统重新加载修改过的类。因此如果达到热替换,立即加载修改过的类文件可以借助ClassLoader。
当服务运行过程中修改了一个类文件,此时可以创建一个新的类加载器来加载修改过的类文件达到热替换的功能,但即使是同一个类文件,由不同的类加载器加载,生成的class对象也是不同的。
java安全模型的核心就是沙箱(sandbox),沙箱其实一个限制程序运行的环境。沙箱机制就是将java代码限定在JVM虚拟机特定的运行环境中,并且严格限制代码对本地系统资源的访问,通过这种措施来保证对代码的有限隔离,防止对本地系统的破坏。
可见沙箱机制优势:保证程序运行安全、保护java原声JDK代码。
沙箱主要限制系统的资源访问系统资源,系统资源主要包括CPU、内存、文件系统、网络,不同级别的沙箱对这些资源访问的限制也是很不同的。所有Java程序运行都可以指定沙箱,定制安全策略。
自定义加载器的优势:
自定义加载器时可以继承java.lang.ClassLoader,继承ClassLoader后可以重写loadClass方法或者findClass方法。但由于loadClass方法中实现了双亲委派模型机制,重写了该方法容易使双亲委派模型失效,一般推荐使用实现findClass的方法。
自定义类加载器
package com.lzj;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class MyClassLoader extends ClassLoader{
private String classPath;
public MyClassLoader(String classPath){
this.classPath = classPath;
}
public MyClassLoader(ClassLoader parent, String classPath){
super(parent);
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
//获取字节码文件路径
String fileName = classPath + className + ".class";
BufferedInputStream bis = null;
ByteArrayOutputStream baos = null;
try {
//创建输入流用于读取字节码来源
bis = new BufferedInputStream(new FileInputStream(fileName));
//创建输出流用于把读出的字节码内容输出到byte数组中
baos = new ByteArrayOutputStream();
byte[] data = new byte[1024];
int length;
while((length = bis.read(data)) != -1){
baos.write(data, 0, length);
}
//获取自定义的字节码数组
byte[] classCodes = baos.toByteArray();
//调用defineClass方法将字节码数组转化为class实例
Class<?> clazz = defineClass(null, classCodes, 0, classCodes.length);
return clazz;
} catch (IOException e){
e.printStackTrace();
} finally {
if (baos != null){
try{
baos.close();
}catch (IOException e){
e.printStackTrace();
}
}
if (bis != null){
try{
bis.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
return null;
}
}
测试类加载器
package com.lzj;
public class ClassloaderTest {
public static void main(String[] args) {
MyClassLoader loader = new MyClassLoader(Thread.currentThread().getContextClassLoader().getParent(), "D:/myproject/java/src/com/lzj/");
try {
Class<?> clazz = loader.loadClass("Persion");
System.out.println(clazz.getClassLoader());
System.out.println(clazz.getClassLoader().getParent());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出结果如下所示,说明自定义的类加载器是MyClassLoader类型的,其父类加载器为ExtClassLoader类加载器
com.lzj.MyClassLoader@1540e19d
sun.misc.Launcher$ExtClassLoader@14ae5a5
如果创建MyClassLoader类加载器时不指定父类加载器,则MyClassLoader类加载器的父加载器默认为ApplicationClassLoader加载器,如下所示
package com.lzj;
public class ClassloaderTest {
public static void main(String[] args) {
MyClassLoader loader = new MyClassLoader("D:/myproject/java/src/com/lzj/");
try {
Class<?> clazz = loader.loadClass("Persion");
System.out.println(clazz.getClassLoader());
System.out.println(clazz.getClassLoader().getParent());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出结果如下所示
com.lzj.MyClassLoader@1540e19d
sun.misc.Launcher$AppClassLoader@58644d46