java类加载体系
BootStrap ClassLoader > ExtClassLoader > AppClassLoader
每种类加载器都有自己的加载目录
- Bootstrap ClassLoader
系统类(rt.ar)的类加载器,采用C++代码加载 - Extension ClassLoader
扩展类(ext.jar)的类加载器,采用ExtClassLoader加载 - Application ClassLoader
用户类路径(classpath)上类的类加载器,采用AppClassLoader加载 - 自定义类加载器
自定义的类加载器,继承ClassLoader即可
java双亲委派
一个java类加载进jvm内存的过程
每个类加载器对他加载过的类,都有一个缓存。
-
向上委托查找,向下委托加载。
Bootstrap 类加载器是用 C++ 实现的,是虚拟机自身的一部分,主要负责加载核心的类库。如果获取它的对象,将会返回 null
双亲委派的作用
- 避免类的重复加载。当父类加载器加载了该类时,子类加载器就不会再加载一次。
- 防止java核心[API]被随意替换。
类加载流程:
- 当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。
- 当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。
- 如果Bootstrap ClassLoader加载失败(在
\lib中未找到所需类),就会让Extension ClassLoader尝试加载。 - 如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。
- 如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载。
- 如果均加载失败,就会抛出ClassNotFoundException异常。
jdk类加载对象
ClassLoader > SecureClassLoader > URLClassLoader > ExtClassLoader,AppClassLoader
打印类加载器
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
//父子关系:AppClassLoader <- ExtClassLoader <- BootStrap ClassLoader
ClassLoader c1 = ClassLoaderTest.class.getClassLoader();
System.out.println("c1:"+c1); // AppClassLoader
System.out.println("c1 parent:"+c1.getParent()); //ExtClassLoader
//BootStrap ClassLoader是由c++开发,是JVM虚拟机的一部分,本身不是java类
System.out.println("c1 parent parent:"+c1.getParent().getParent()); // null
// String int等基础类由 BootStrap ClassLoader 加载
ClassLoader c2 = String.class.getClassLoader();
System.out.println("c2:"+c2); // null
System.out.println(c1.loadClass("java.util.List").getClass().getClassLoader()); // null
// java指令可以通过增加-verbose:class -verbose:gc 参数在启动时打印出类加载情况
// BootStrap ClassLoader 加载java基础类,非java实现
System.out.println("BootStrap ClassLoader加载目录:"+System.getProperty("sun.boot.class.path"));
// ExtClassLoader加载JAVA_HOME/ext下的jar, 可通过-D java.ext.dirs另行指定目录
System.out.println("ExtClassLoader加载目录:"+System.getProperty("java.ext.dirs"));
// AppClassLoader加载CLASSPATH应用下的jar, 可通过-D java.class.path另行指定目录
System.out.println("AppClassLoader加载目录:"+System.getProperty("java.class.path"));
}
}
新建一个工程,导出为jar
我们新建一个工程,导出为jar文件,里面包含一个类
package com.test.say;
public class SayHello {
StringBuffer sb = new StringBuffer();
public String say(String hi, int age, String name) {
return sb.append(hi+" ").append("我今年"+age+"岁了").append("我叫:"+name).toString();
}
}
使用URLClassLoader加载jar
package com.test.load;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class JarLoad {
public static void main(String[] args) throws Exception{
URL jarPath = new URL("file:D:/jar/say.jar");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{jarPath});
Class> clz = urlClassLoader.loadClass("com.test.say.SayHello");
Object obj = clz.newInstance();
Method method = clz.getMethod("say", String.class, int.class, String.class);
String ret = (String)method.invoke(obj, "你好", 22, "张三");
System.out.println(ret);
}
}
自定义类加载器,直接加载class文件
我们把上面生成的SayHello.class改为SayHello.myclass(这样反编译jdui就无法查看),然后自定义类加载器加载SayHello.myclass
自定义SayClassLoader
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.security.SecureClassLoader;
public class SayClassLoader extends SecureClassLoader {
private String clzPath;
public SayClassLoader(String clzPath) {
this.clzPath = clzPath;
}
/**
* 需要实现从myClass加载
*/
@Override
protected Class> findClass(String name) {
// name: com.test.say.SayHello
// filePath:D:\eclipse_workspace\SayHello\bin\com\test\say\SayHello.myclass
String filePath = this.clzPath + name.replace(".", "\\").concat(".myclass");
byte[] b = fileConvertToByteArray(new File(filePath));
return this.defineClass(name, b, 0, b.length);
}
/**
* 把一个文件转化为byte字节数组。
*/
private byte[] fileConvertToByteArray(File file) {
byte[] data = null;
try {
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
data = baos.toByteArray();
fis.close();
baos.close();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
}
使用自定义的类加载器
SayClassLoader sayClassLoader = new SayClassLoader("D:\\eclipse_workspace\\SayHello\\bin\\");
Class> clz = sayClassLoader.loadClass("com.test.say.SayHello");
Object obj = clz.newInstance();
Method method = clz.getMethod("say", String.class, int.class, String.class);
String ret = (String)method.invoke(obj, "hi", 22, "李四");
System.out.println(ret);
自定义类加载器,从jar中加载class
自定义类加载器,从jar中找到对应的class文件,加载到jvm中。
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.SecureClassLoader;
/**
* 实现从jar包中找到class文件,来加载类
*/
public class JarLoader extends SecureClassLoader{
private String jarPath; //jar文件的路径
public JarLoader(String jarPath) {
this.jarPath = jarPath;
}
@Override
protected Class> findClass(String name) {
String classPath = name.replace(".", "/").concat(".class");
URL fileUrl;
try {
String path = "jar:file:\\"+this.jarPath+"!/"+classPath;
// path: jar:file:\D:\jar\say.jar!/com/test/say/SayHello.class
fileUrl = new URL(path);
byte[] b = fileConvertToByteArray(fileUrl.openStream());
return this.defineClass(name, b, 0, b.length);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 把一个文件转化为byte字节数组。
*/
private byte[] fileConvertToByteArray(InputStream ins) {
byte[] data = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = ins.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
data = baos.toByteArray();
ins.close();
baos.close();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
}
使用自定的jar加载器
JarLoader jarLoader = new JarLoader("D:\\jar\\say.jar");
Class> clz = jarLoader.loadClass("com.test.say.SayHello");
Object obj = clz.newInstance();
Method method = clz.getMethod("say", String.class, int.class, String.class);
String ret = (String)method.invoke(obj, "how are you", 22, "王五");
System.out.println(ret);
到底加载的是哪个类
先看下面场景,假如我的当前工程下面有一个包名类名以及方法声明都一样的java文件,此时使用自定义的jar,会是怎么样的?
测试加载jar包里的类
public class JarLoad {
public static void main(String[] args) throws Exception{
JarLoader jarLoader = new JarLoader("D:\\jar\\say.jar");
Class> clz = jarLoader.loadClass("com.test.say.SayHello");
Object obj = clz.newInstance();
Method method = clz.getMethod("say", String.class, int.class, String.class);
String ret = (String)method.invoke(obj, "how are you", 22, "王五");
System.out.println(ret);
}
}
可以看到,使用的是当前工程的SayHello,而不是预期的jar包里的SayHello,问题就出在双亲委派机制。
打破双亲委派
上述的JarLoader除了重写findClass还需要重写loadClass
@Override
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class> c= null;
synchronized (getClassLoadingLock(name)) {
// 1. 先从缓存中取
c = findLoadedClass(name);
if (!name.startsWith("com.test.say")) {
c = this.getParent().loadClass(name);
} else {
c = this.findClass(name);
}
}
return c;
}