ClassLoader那事儿
ClassLoader是什么
ClassLoader中文类加载器,java编写出来的是.java文件,然后编译成.class文件,而ClassLoader就是把class文件加载到jvm内存中;但jvm启动时,通过不同的类加载器,动态的加载class文件;java比较重要的三类加载器Bootstrap ClassLoader、 Extention ClassLoader、Appclass Loader。
Java中的类加载器
Bootstrap ClassLoader、 Extention ClassLoader、Appclass Loader这三个类加载器分别负责加载不同路径下的文件
Bootstrap CLassloder
它是最顶级的类加载,主要加载载核心类库,其路径为%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等,而这个路径是可以通过jvm启动参数-Xbootclasspath来设置
Extention ClassLoader
扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
AppClassLoader
当前应用的classpath的所有类
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package sun.misc; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; import java.security.AccessControlContext; import java.security.AccessController; import java.security.CodeSource; import java.security.PermissionCollection; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import java.security.cert.Certificate; import java.util.HashSet; import java.util.StringTokenizer; import java.util.Vector; import sun.net.www.ParseUtil; public class Launcher { private static URLStreamHandlerFactory factory = new Launcher.Factory(); private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader; private static URLStreamHandler fileHandler; public static Launcher getLauncher() { return launcher; } public Launcher() { Launcher.ExtClassLoader var1; try { var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader"); } try { this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader"); } 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); } } public ClassLoader getClassLoader() { return this.loader; } public static URLClassPath getBootstrapClassPath() { return Launcher.BootClassPathHolder.bcp; }
……………………………… }
从以上源码可以看到
1. 没有看到 Bootstrap ClassLoader,只有bootClassPath,这个应该就是顶级类加载器的加载路径;
2.Launcher 先初始化了Extention ClassLoader ,然后把传入它实例再初始化AppClassLoader。
static class ExtClassLoader extends URLClassLoader { public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
//获取加载的文件,进入getExtDirs可以看到加载的文件路径String var0 = System.getProperty("java.ext.dirs")
final File[] var0 = getExtDirs(); try { return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() { public Launcher.ExtClassLoader run() throws IOException { int var1 = var0.length; for(int var2 = 0; var2 < var1; ++var2) { MetaIndex.registerDirectory(var0[var2]); } //创建类加载 return new Launcher.ExtClassLoader(var0); } }); } catch (PrivilegedActionException var2) { throw (IOException)var2.getException(); } } void addExtURL(URL var1) { super.addURL(var1); } public ExtClassLoader(File[] var1) throws IOException {
//调用父类方法创建,留意到第二个参数为null,这个参数是父加载器,预测ExtClassLoader是没有父加载器的
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
} private static File[] getExtDirs() { String var0 = System.getProperty("java.ext.dirs"); File[] var1; if (var0 != null) { StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator); int var3 = var2.countTokens(); var1 = new File[var3]; for(int var4 = 0; var4 < var3; ++var4) { var1[var4] = new File(var2.nextToken()); } } else { var1 = new File[0]; } return var1; } private static URL[] getExtURLs(File[] var0) throws IOException { Vector var1 = new Vector(); for(int var2 = 0; var2 < var0.length; ++var2) { String[] var3 = var0[var2].list(); if (var3 != null) { for(int var4 = 0; var4 < var3.length; ++var4) { if (!var3[var4].equals("meta-index")) { File var5 = new File(var0[var2], var3[var4]); var1.add(Launcher.getFileURL(var5)); } } } } URL[] var6 = new URL[var1.size()]; var1.copyInto(var6); return var6; } public String findLibrary(String var1) { var1 = System.mapLibraryName(var1); URL[] var2 = super.getURLs(); File var3 = null; for(int var4 = 0; var4 < var2.length; ++var4) { File var5 = (new File(var2[var4].getPath())).getParentFile(); if (var5 != null && !var5.equals(var3)) { String var6 = VM.getSavedProperty("os.arch"); File var7; if (var6 != null) { var7 = new File(new File(var5, var6), var1); if (var7.exists()) { return var7.getAbsolutePath(); } } var7 = new File(var5, var1); if (var7.exists()) { return var7.getAbsolutePath(); } } var3 = var5; } return null; } private static AccessControlContext getContext(File[] var0) throws IOException { PathPermissions var1 = new PathPermissions(var0); ProtectionDomain var2 = new ProtectionDomain(new CodeSource(var1.getCodeBase(), (Certificate[])null), var1); AccessControlContext var3 = new AccessControlContext(new ProtectionDomain[]{var2}); return var3; } static { ClassLoader.registerAsParallelCapable(); } }
看看ExtenTionClassLoader初始化做了什么
从上面可以看到ExtClassLoader extends URLClassLoader,初始化ExtClassLoader先读取java.ext.dirs路径(D:\java7\jre\lib\ext;C:\Windows\Sun\Java\lib\ext)下的文件,然后调用URLClassLoader.supper()-->
SecureClassLoader.supper()-->ClassLoader.supper()-->方法创建,因为传入的parent是null,可以看到ExtClassLoader 没有父加载器的;简单说就是加载java.ext.dirs的文件创建了个无父加载器的类加载器。
public static void main(String[] args) { System.out.println(System.getProperty("java.ext.dirs")); } /* D:\java7\jre\lib\ext;C:\Windows\Sun\Java\lib\ext */
/*
URLClassLoader extends SecureClassLoader implements Closeable
*/
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
ucp = new URLClassPath(urls, factory);
acc = AccessController.getContext();
}
/******************************************************************************/
/*
SecureClassLoader extends ClassLoader
*/
protected SecureClassLoader(ClassLoader parent) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
initialized = true;
}
/******************************************************************************/
/*
ClassLoader类
*/
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
看看APPClassLoader初始化做了什么
static class AppClassLoader extends URLClassLoader { public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException { final String var1 = System.getProperty("java.class.path"); final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1); return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() { public Launcher.AppClassLoader run() { URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2); return new Launcher.AppClassLoader(var1x, var0); } }); } AppClassLoader(URL[] var1, ClassLoader var2) { super(var1, var2, Launcher.factory); } public 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)); } } return super.loadClass(var1, var2); } protected PermissionCollection getPermissions(CodeSource var1) { PermissionCollection var2 = super.getPermissions(var1); var2.add(new RuntimePermission("exitVM")); return var2; } private void appendToClassPathForInstrumentation(String var1) { assert Thread.holdsLock(this); super.addURL(Launcher.getFileURL(new File(var1))); } private static AccessControlContext getContext(File[] var0) throws MalformedURLException { PathPermissions var1 = new PathPermissions(var0); ProtectionDomain var2 = new ProtectionDomain(new CodeSource(var1.getCodeBase(), (Certificate[])null), var1); AccessControlContext var3 = new AccessControlContext(new ProtectionDomain[]{var2}); return var3; } static { ClassLoader.registerAsParallelCapable(); } }
从上面看APPClassLoader也是继承URLClassLoader,与ExtClassLoader不同的是,
1.读取的路径是java.class.path
2.把ExtClassLoader的实例作为APPClassLoader的父加载器,也就是说APPClassLoader的父类加载器是ExtClassLoader
3.加载顺序Bootstrap ClassLoader 到 ExtClassLoader 到 APPClassLoader
父加载器
首先我们先要明确一点是父加载器并不是父类,我们可以通过下面的代码看到
public class MyClassLoader extends URLClassLoader { public MyClassLoader(URL[] repositories) { super(repositories); } public MyClassLoader(URL[] repositories, ClassLoader parent) { super(repositories, parent); } } public static void main(String[] args) { ClassLoader cl = MyClassLoader.class.getClassLoader(); System.out.println("ClassLoader is:"+cl.toString()); System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString()); // System.out.println("ClassLoader\'s grand father is:"+cl.getParent().getParent().toString()); } ClassLoader is:sun.misc.Launcher$AppClassLoader@31fc6b2 ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@1b2dd1b8
1.MyClassLoader 继承URLClassLoader,但是可以到店MyClassLoader的父加载器是AppClassLoader
2.AppClassLoader 的父加载器是ExtClassLoader,这个在上面分析初始化的APPClassLoader的时候,就可以看到传入ExtClassLoader的实例作为它的加载器,那么ExtClassLoader的父加载器上面初始时是null,我们可以验证一下
public static void main(String[] args) { ClassLoader cl = MyClassLoader.class.getClassLoader(); // System.out.println("ClassLoader is:"+cl.toString()); // System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString()); System.out.println("ClassLoader\'s grand father is:"+cl.getParent().getParent().toString()); } Exception in thread "main" java.lang.NullPointerException at com.suntek.vdm.gw.util.NotifyHandler.main(NotifyHandler.java:373)
从上面的代码可以看到,我们的上面是对的ExtClassLoader的父加载器的确是null.
3.那么其他的没有指定父加载器的ClassLoader的父加载器是什么呢?从上面的代码我们可以看到MyClassLoader的的是APPClassLoader,那么这是为什么呢?难道没有指定父加载器的ClassLoader的默认是APP
/* 这ClassLoader是没有指定父加载器创建方法 */ protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } //这里我们可以看到getSytemClassLoader()这个方法获取父加载器 private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); domains = Collections.synchronizedSet(new HashSet<ProtectionDomain>()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); domains = new HashSet<>(); assertionLock = this; } } //看到return的是scl,而scl赋值是initSystemClassLoader()方法 public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkClassLoaderPermission(scl, Reflection.getCallerClass()); } return scl; } //真相就在这里了,就是Launcher的getClassLoader(),而Luancher初始化的前面有看过了,也可以往下看 private static synchronized void initSystemClassLoader() { if (!sclSet) { if (scl != null) throw new IllegalStateException("recursive invocation"); sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l != null) { Throwable oops = null; scl = l.getClassLoader(); try { scl = AccessController.doPrivileged( new SystemClassLoaderAction(scl)); } catch (PrivilegedActionException pae) { oops = pae.getCause(); if (oops instanceof InvocationTargetException) { oops = oops.getCause(); } } if (oops != null) { if (oops instanceof Error) { throw (Error) oops; } else { // wrap the exception throw new Error(oops); } } } sclSet = true; } } //这个是Launcher对的getClassLoader,然后在看看下面的图,明白了,就是APPClassLoader public ClassLoader getClassLoader() { return this.loader; }
一层层往下最终APPClassLoader作为默认的ClassLoader;
也就是没有指定的父加载器的默认为是APPClassLoader;
双亲委托
有了父加载器的概念之后,我们就可以开始双亲委托这个玩意了!这部分,发现个哥们写得不错 https://blog.csdn.net/briblue/article/details/54973413,于是就复制了!
一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先判断这个class是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托。
整个流程可以如下图所示:
大家可以看到2根箭头,蓝色的代表类加载器向上委托的方向,如果当前的类加载器没有查询到这个class对象已经加载就请求父加载器(不一定是父类)进行操作,然后以此类推。直到Bootstrap ClassLoader。如果Bootstrap ClassLoader也没有加载过此class实例,那么它就会从它指定的路径中去查找,如果查找成功则返回,如果没有查找成功则交给子类加载器,也就是ExtClassLoader,这样类似操作直到终点,也就是我上图中的红色箭头示例。
用序列描述一下:
1. 一个AppClassLoader查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。
2. 递归,重复第1部的操作。
3. 如果ExtClassLoader也没有加载过,则由Bootstrap ClassLoader出面,它首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是sun.mic.boot.class
下面的路径。找到就返回,没有找到,让子加载器自己去找。
4. Bootstrap ClassLoader如果没有查找成功,则ExtClassLoader自己在java.ext.dirs
路径中去查找,查找成功就返回,查找不成功,再向下让子加载器找。
5. ExtClassLoader查找不成功,AppClassLoader就自己查找,在java.class.path
路径下查找。找到就返回。如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。
上面的序列,详细说明了双亲委托的加载流程。我们可以发现委托是从下向上,然后具体查找过程却是自上至下。
这里要说一下ExtClassLoader 这个是没有父加载器,就通过BootStrapClassLoader这个来查找的,下面这个代码的ClassLoader这个类的方法,
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(); try { if (parent != null) {
//如果父加强是空,往上找 c = parent.loadClass(name, false); } else {
//否则就通过BootStrapClassLoader找,也就应该是ExtClassLoader c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } 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 class MyClassLoader extends URLClassLoader { public MyClassLoader(URL[] repositories) { super(repositories); } public MyClassLoader(URL[] repositories, ClassLoader parent) { super(repositories, parent); } } public final class MyClassLoaderFactory { public static ClassLoader createClassLoader(File unpacked[], final ClassLoader parent) throws Exception { Set<URL> set = new LinkedHashSet<URL>(); if (unpacked != null) { for (int i = 0; i < unpacked.length; i++) { File file = unpacked[i]; if (!file.exists() || !file.canRead()){ continue; } file = new File(file.getCanonicalPath() + File.separator); URL url = file.toURI().toURL(); set.add(url); } } final URL[] array = set.toArray(new URL[set.size()]); return AccessController.doPrivileged(new PrivilegedAction<MyClassLoader>() { @Override public MyClassLoader run() { if (parent == null){ return new MyClassLoader(array); }else { return new MyClassLoader(array, parent); } } }); } }
private static void startWithClassloader(String path) throws Exception { String classPath= path; File[] packed = new File[] { new File(path) }; ClassLoader catalinaLoader = MyClassLoaderFactory.createClassLoader(null, packed, null);
Thread.currentThread().setContextClassLoader(catalinaLoader); Class<?> startupClass = catalinaLoader.loadClass(MAIN_CLASS); Object startupInstance = startupClass.newInstance(); Method method = startupInstance.getClass().getMethod("start"); method.invoke(startupInstance); }
一步一步来吧,首先自定义一个MyClassLoader类继承了URlClassLoader,再对比一下AppClassLoader 的创建,可以说基本一样;
a.获取路径的File[] 文件;
b.然后就调用了AccessController 的native <T> T doPrivileged(PrivilegedAction<T> action),而关于这个方法的说法,网友们是这么说的:一个调用者在调用doPrivileged方法时,可被标识为 "特权"。在做访问控制决策时,如果checkPermission方法遇到一个通过doPrivileged调用而被表示为 "特权"的调用者,并且没有上下文自变量,checkPermission方法则将终止检查。如果那个调用者的域具有特定的许可,则不做进一步检查,checkPermission安静地返回,表示那个访问请求是被允许的;如果那个域没有特定的许可,则象通常一样,一个异常被抛出
c.调用福利的supper方法创建
Thread.currentThread().setContextClassLoader(catalinaLoader) 这个句有什么用??
a.在java的应用中,每个线程都有一个contextClassLoad,而充init()方法可以看到,默认是父线程的上下文类加载器,当月我们也可以通过Thread.currentThread().setContextClassLoader(catalinaLoader)来设置;
b.这个上下文的加载器的作用简单的说,就是当这个线程加载某些类时,通过这加载器进行加载;
c.网友说法:
每个运行中的线程都有一个成员contextClassLoader,用来在运行时动态地载入其它类
系统默认的contextClassLoader是systemClassLoader,所以一般而言java程序在执行时可以使用JVM自带的类、$JAVA_HOME/jre/lib/ext/中的类和$CLASSPATH/中的类
可以使用Thread.currentThread().setContextClassLoader(...);更改当前线程的contextClassLoader,来改变其载入类的行为
ClassLoader被组织成树形,一般的工作原理是:
1) 线程需要用到某个类,于是contextClassLoader被请求来载入该类
2) contextClassLoader请求它的父ClassLoader来完成该载入请求
3) 如果父ClassLoader无法载入类,则contextClassLoader试图自己来载入
注意:WebApp?ClassLoader的工作原理和上述有少许不同:
它先试图自己载入类(在ContextBase?/WEB-INF/...中载入类),如果无法载入,再请求父ClassLoader完成
public class Thread implements Runnable { /* Make sure registerNatives is the first thing <clinit> does. */ private static native void registerNatives(); static { registerNatives(); } private char name[]; private int priority; private Thread threadQ; private long eetop; /* Whether or not to single_step this thread. */ private boolean single_step; /* Whether or not the thread is a daemon thread. */ private boolean daemon = false; /* JVM state */ private boolean stillborn = false; /* What will be run. */ private Runnable target; /* The group of this thread */ private ThreadGroup group; /* The context ClassLoader for this thread */ private ClassLoader contextClassLoader;
……………… }
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name.toCharArray(); Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager what to do. */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ g.checkAccess(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); }
最后通过反射把程序运行起来
一个题目
/** * a.加载->校验->准备->初始化->使用->卸载(准备和初始化) * b.父类静态->本身静态->父类变量和代码块->父类构造器->本身变量和代码块->本身构造器 * c.静态只会加载一次,静态变量一开始会在准备阶段赋值默认值,如st = null, flag = false; * c.而st = new StaticTest()这步骤被镶嵌到static StaticTest st = new StaticTest(); * d.开始执行new StaticTest(),因为静态变量都已经初始化,因此进入:本身变量和代码块->本身构造器 * e.结束new StaticTest(),进入下一个静态变量初始化System.out.println("2");再下一个static boolean flag = true; * f.执行方法staticFunction(); */ public class StaticTest { public static void main(String[] args) { staticFunction(); } { System.out.println("1"); } static StaticTest st = new StaticTest(); static { System.out.println("2"); } StaticTest() { System.out.println("3"); double temp = 3*0.1; if (temp == a){ System.out.println(a+","+flag); }else { System.out.println(a+","+flag); } } private static void staticFunction(){ if (flag){ System.out.println("4"); }else { System.out.println("5"); } } double a=0.3; static boolean flag = true; }
1
3
0.3,false
2
4