java的类加载过程及双亲委派机制
在jvm加载class文件主要由类加载器完成,不同类库下的class加载的加载器也不相同,jvm中有以下四种类加载器:
以下将通过一段代码更好地展示java类加载的过程:
/**
* jvm获取类加载器过程
*/
public class GetClassLoader {
public static void main(String[] args) {
// 核心类库下面的class加载,结果为null,此类加载器为c++本地方法调用获得,java无法打印
System.out.println(String.class.getClassLoader());
// 拓展类库下面的class加载
System.out.println(DESKeyFactory.class.getClassLoader().getClass().getName());
// 自己定义的类(此处为当前类)class加载
System.out.println(GetClassLoader.class.getClassLoader().getClass().getName());
System.out.println("------------------------------------------------------");
// 应用程序类加载器:负责加载ClassPath路径下的类包,加载自己的类
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
// 拓展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
ClassLoader extClassLoader = appClassLoader.getParent();
// 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println("bootStrapLoader: "+bootstrapClassLoader);
System.out.println("extClassLoader: "+extClassLoader);
System.out.println("appClassLoader: " + appClassLoader);
System.out.println();
System.out.println("bootstrapLoader加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println("--------------------------------------------");
System.out.println("extClassLoader 加载以下文件: ");
System.out.println(System.getProperty("java.ext.dirs"));
}
}
执行结果:
null
sun.misc.Launcher $ ExtClassLoader sun.misc.Launcher $
AppClassLoader
-------------------------------------------------------
bootStrapLoader: null
extClassLoader: sun.misc.Launcher $ExtClassLoader@2d209079
appClassLoader: sun.misc.Launcher $AppClassLoader@18b4aac2
-------------------------------------------------------
bootstrapLoader加载以下文件:
file:/C:/JDK/jre/lib/resources.jar
file:/C:/JDK/jre/lib/rt.jar
file:/C:/JDK/jre/lib/sunrsasign.jar
file:/C:/JDK/jre/lib/jsse.jar
file:/C:/JDK/jre/lib/jce.jar
file:/C:/JDK/jre/lib/charsets.jar
file:/C:/JDK/jre/lib/jfr.jar
file:/C:/JDK/jre/classes
-------------------------------------------------------
extClassLoader 加载以下文件: C:\JDK\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
值得注意的是String类的类加载器打印出来为null,这是因为string是由引导类加载器加载完成,引导类加载器则是由c++发起调用,创建的类加载器,java无法将本地方法创建的类加载器打印出来
jvm的类加载器具有层级关系,关系如下
jvm加载类的时候,会先委托父加载器在其路径下寻找目标类,父加载器找不到再委托上层父加载器加载,如果最上层的父加载器仍然没有找到目标类,那么则会交由该类加载器在自己的类路径中查找并载入目标类。
用APPclassload源码学习双亲委派机制是最适合的
下面是AppClassLoader的loadClass方法
public synchronized Class loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
try {
// 这里调用了ClassLoader类的loadClass方法
return super.loadClass(var1, var2);
} catch (ClassNotFoundException var5) {
throw var5;
} catch (RuntimeException var6) {
throw var6;
} catch (Error var7) {
throw var7;
}
}
代码中关键的处调用了父类(ClassLoader)的loadClass方法
下面是loadClass方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先检查该类是否已经被加载了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果有上级类加载器且没有加载类
if (parent != null) {
// 调用父加载器的loadClass()方法
c = parent.loadClass(name, false);
} else {
// 没有父加载器,则当前加载器为引导类加载器,该方法最后为C++执行的本地方法
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 如果仍然没有加载到,调用URLclassLoader重写的findClass()方法,在当前类加载器路径中寻找需要加载的类
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
说明:
从loadClass类的这两段代码中我们可以知道,如果父加载器都没有找到该类,则调用子类重写的findClass()方法,所以实现自定义类加载的关键为重写findClass()方法
public class MyClassLoader extends ClassLoader {
private String path;
public MyClassLoader(String path) {
this.path = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
path = path+"/"+name.replace('.', '/').concat(".class");
FileInputStream fis = null;
try {
fis = new FileInputStream(path);
int lenth = fis.available();
byte[] bytes = new byte[lenth];
fis.read(bytes);
return defineClass(name, bytes,0,lenth);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class MyTestClass1 {
public void printStr(String name) {
System.out.println("Hello "+name);
}
}
将这个类的class文件复制出来,放在自己新建的文件夹下(不要放在当前项目类路径下,当前类路径下的class文件需要删除或者改名,否认会默认被APPClassLoader加载)
public class GetClassLoader {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
MyClassLoader classLoader = new MyClassLoader("C:/code/com/test");
Class clazz = classLoader.loadClass("testGetClassLoader.MyTestClass");
Object obj = clazz.newInstance();
Method printStr = clazz.getMethod("printStr", String.class);
printStr.invoke(obj, "小明");
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
Hello 小明
testGetClassLoader.MyClassLoader
从打印的类加载器名可以看到我们已经成功用自己写的类加载器加载了这个类,一个简单的类加载器就完成了。
学会写自己的类加载器,那么如何打破双亲委派机制应该就不难了。
双亲委派主要靠classLoader的loadClass方法实现,想要打破双亲委派,实现加载自己写的核心类,那么就需要重写loadClass方法
/**
* 重写loadClass方法
*
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
// 如果是核心类,由于有安全保护机制,所以不允许从外部加载,只能交由上层类加载器加载
if (name.startsWith("java")) {
c = this.getParent().loadClass(name);
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
}
return c;
}
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
MyClassLoader classLoader = new MyClassLoader("C:/code/com/test");
Class clazz = classLoader.loadClass("testGetClassLoader.MyTestClass1");
Object obj = clazz.newInstance();
Method printStr = clazz.getMethod("printStr", String.class);
printStr.invoke(obj, "小明");
System.out.println(clazz.getClassLoader().getClass().getName());
}
执行结果
Hello 小明
testGetClassLoader.MyClassLoader
从执行结果上来看已经实现了打破双亲委派机制的目的
以上就是java中的双亲委派及双亲委派的打破,在工作中可能会很有帮助