1、三层类加载器及父子关系的建立
- 类加载器四种
1、启动类加载器
2、拓展类加载器
3、应用程序类加载器
4、自定义类加载器
bootstrap classloader--回去加载jre/lib/rt.jar下的内容
extension classloader--会去加载 jre/lib/ext/*.jar
application classloader--会去加载calss_path指定目录下的jar
user classloader--加载我们自己定义的class
2、详解启动类加载器
- 三种类加载器父子关系是如何建立的
openjdkjdksrcsharebinjava.c中搜索JavaMain,有一个 mainClass = LoadMainClass(env, mode, what);,进入LoadMainClass
/* * Loads a class and verifies that the main class is present and it is ok to * call it for more details refer to the java implementation. */ static jclass LoadMainClass(JNIEnv *env, int mode, char *name) { jmethodID mid; jstring str; jobject result; jlong start, end; jclass cls = GetLauncherHelperClass(env); NULL_CHECK0(cls); if (JLI_IsTraceLauncher()) { start = CounterGet(); } NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls, "checkAndLoadMain", "(ZILjava/lang/String;)Ljava/lang/Class;")); str = NewPlatformString(env, name); CHECK_JNI_RETURN_0( result = (*env)->CallStaticObjectMethod( env, cls, mid, USE_STDERR, mode, str)); if (JLI_IsTraceLauncher()) { end = CounterGet(); printf("%ld micro seconds to load main classn", (long)(jint)Counter2Micros(end-start)); printf("----%s----n", JLDEBUG_ENV_ENTRY); } return (jclass)result; }
- 有一个checkAndLoadMain很关键 ,会加载main函数所在的类,以及启动扩展类加载器、应用类加载器(完成启动类、扩展类、应用类加载器逻辑上的父子关系)。
- 上面有一个 jclass cls = GetLauncherHelperClass(env);
jclass GetLauncherHelperClass(JNIEnv *env) { if (helperClass == NULL) { NULL_CHECK0(helperClass = FindBootStrapClass(env, "sun/launcher/LauncherHelper")); } return helperClass; }
- 进入FindBootStrapClass(jdk/src/solaris/bin/java_md_common.c),可以看到源码,这里的BootStrapClass就是启动类加载器,是通过C++代码来调用的
jclass FindBootStrapClass(JNIEnv *env, const char *classname) { HMODULE hJvm; if (findBootClass == NULL) { hJvm = GetModuleHandle(JVM_DLL); if (hJvm == NULL) return NULL; /* need to use the demangled entry point */ findBootClass = (FindClassFromBootLoader_t *)GetProcAddress(hJvm, "JVM_FindClassFromBootLoader"); if (findBootClass == NULL) { JLI_ReportErrorMessage(DLL_ERROR4, "JVM_FindClassFromBootLoader"); return NULL; } } return findBootClass(env, classname); }
- LoadMainClass中找到BootStrapClass(启动类加载器),然后去执行类sun/launcher/LauncherHelper中的方法checkAndLoadMain(通过JNI技术)。
- 看一下checkAndLoadMain方法
public static Class<?> checkAndLoadMain(boolean printToStderr, int mode, String what) { initOutput(printToStderr); // get the class name String cn = null; switch (mode) { case LM_CLASS: cn = what; break; case LM_JAR: cn = getMainClassFromJar(what); break; default: // should never happen throw new InternalError("" + mode + ": Unknown launch mode"); } cn = cn.replace('/', '.'); Class<?> mainClass = null; try { mainClass = scloader.loadClass(cn); } catch (NoClassDefFoundError | ClassNotFoundException cnfe) { if (System.getProperty("os.name", "").contains("OS X") && Normalizer.isNormalized(cn, Normalizer.Form.NFD)) { try { // On Mac OS X since all names with diacretic symbols are given as decomposed it // is possible that main class name comes incorrectly from the command line // and we have to re-compose it mainClass = scloader.loadClass(Normalizer.normalize(cn, Normalizer.Form.NFC)); } catch (NoClassDefFoundError | ClassNotFoundException cnfe1) { abort(cnfe, "java.launcher.cls.error1", cn); } } else { abort(cnfe, "java.launcher.cls.error1", cn); } } // set to mainClass appClass = mainClass; /* * Check if FXHelper can launch it using the FX launcher. In an FX app, * the main class may or may not have a main method, so do this before * validating the main class. */ if (mainClass.equals(FXHelper.class) || FXHelper.doesExtendFXApplication(mainClass)) { // Will abort() if there are problems with the FX runtime FXHelper.setFXLaunchParameters(what, mode); return FXHelper.class; } validateMainClass(mainClass); return mainClass; }
- 从 mainClass = scloader.loadClass(cn); ,点击scloader,可以来到private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();,是它的声明位置
public enum LauncherHelper { INSTANCE; private static final String MAIN_CLASS = "Main-Class"; private static StringBuilder outBuf = new StringBuilder(); private static final String INDENT = " "; private static final String VM_SETTINGS = "VM settings:"; private static final String PROP_SETTINGS = "Property settings:"; private static final String LOCALE_SETTINGS = "Locale settings:"; // sync with java.c and sun.misc.VM private static final String diagprop = "sun.java.launcher.diag"; final static boolean trace = sun.misc.VM.getSavedProperty(diagprop) != null; private static final String defaultBundleName = "sun.launcher.resources.launcher"; private static class ResourceBundleHolder { private static final ResourceBundle RB = ResourceBundle.getBundle(defaultBundleName); } private static PrintStream ostream; private static final ClassLoader scloader = ClassLoader.getSystemClassLoader(); private static Class<?> appClass; // application class, for GUI/reporting purposes
- 进入getSystemClassLoader
@CallerSensitive public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkClassLoaderPermission(scl, Reflection.getCallerClass()); } return scl;
- SourceInsight的Project Symbols中搜索getLauncher,来到package sun.misc;、public class Launcher(openjdkjdksrcshareclassessunmiscLauncher.java中),可以看到构造函数
public Launcher() { // Create the extension class loader ClassLoader extcl; try { extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader", e); } // Now create the class loader to use to launch the application try { loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader", e); } // Also set the context class loader for the primordial thread. Thread.currentThread().setContextClassLoader(loader); // Finally, install a security manager if requested String s = System.getProperty("java.security.manager"); if (s != null) { SecurityManager sm = null; if ("".equals(s) || "default".equals(s)) { sm = new java.lang.SecurityManager(); } else { try { sm = (SecurityManager)loader.loadClass(s).newInstance(); } catch (IllegalAccessException e) { } catch (InstantiationException e) { } catch (ClassNotFoundException e) { } catch (ClassCastException e) { } } if (sm != null) { System.setSecurityManager(sm); } else { throw new InternalError( "Could not create SecurityManager: " + s); } } }
- 创建Extclassloader
- 创建Appclassloader
- 设置到线程上下文加载器中
- 进入getAppClassLoader,来到static class AppClassLoader extends URLClassLoader,可以看到一个构造函数,看到parent字眼,很鲜明地说明,AppClassLoader是通过父加载器ExtClassLoader来初始化的
/* * Creates a new AppClassLoader */ AppClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent, factory); ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this); ucp.initLookupCache(this); }
- 进入getExtClassLoader,来到static class ExtClassLoader extends URLClassLoader,可以看到一个构造函数,结合下面URLClassLoader中的代码,看到parent字眼,很鲜明地说明,ExtClassLoader的父加载器传入的是null。这里的null是java中的空指针,而实际上扩展类加载器的父加载器是启动类加载器,是逻辑上的父子关系,是通过C++到Java的执行来完成的
/* * Creates a new ExtClassLoader for the specified directories. */ public ExtClassLoader(File[] dirs) throws IOException { super(getExtURLs(dirs), null, factory); SharedSecrets.getJavaNetAccess(). getURLClassPath(this).initLookupCache(this); }
总结:
- jdk源码中的LoadMainClass方法中,先通过GetLauncherHelperClass获取启动类加载器BootStrapClass。
- 调用sun/launcher/LauncherHelper中的方法checkAndLoadMain
1)加载main函数所有类。
2)启动扩展类加载器,应用类加载器(完成启动类、扩展类、应用类加载器逻辑上的父子关系)
补充;
1.ExtClassLoader类加载器
public class ExtClassLoaderPath { public static void main(String[] args) { String property = "java.ext.dirs"; System.out.println("====================" + property + "'s Urls===================="); String os_name = System.getProperty("os.name"); String[] urls; if (os_name.toLowerCase().contains("win")) { urls = System.getProperty(property).split(";"); } else { urls = System.getProperty(property).split(":"); } for (String url : urls) { System.out.println(url); } System.out.println("====================ExtClassLoaderPath's Urls===================="); URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader().getParent(); URL[] urls1 = classLoader.getURLs(); for (URL url : urls1) { System.out.println(url); } }
2.AppClassLoaderPath
public class AppClassLoaderPath { public static void main(String[] args) { String property = "java.class.path"; System.out.println("====================" + property + "'s Urls===================="); String os_name = System.getProperty("os.name"); String[] urls; if (os_name.toLowerCase().contains("win")) { urls = System.getProperty(property).split(";"); } else { urls = System.getProperty(property).split(":"); } for (String url : urls) { System.out.println(url); } System.out.println("====================AppClassLoader's Urls===================="); URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); URL[] urls1 = classLoader.getURLs(); for (URL url : urls1) { System.out.println(url); } } }
3、什么是双亲委派
类加载器加载某个类的请求,若未加载则不会先去加载这个类,而是把请求委派给父类加载器,每一层都是这样。所有请求最终会传给bootstrap加载器。只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
4、打破双亲委派
- 打破双亲委派的意思其实就是不委派、向下委派
- 打破双亲委派的两种方式
1、自定义类加载器
2、SPI机制(向下委派)
- 一种服务发现机制,通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
5、代码演示自定义类加载器
- 1、extends ClassLoader
- 2、Override findClass、loadClass等(允许不Override)
- 3、如果Override loadClass,可以打破双亲委派
- 4、如果Override findClass,不算打破双亲委派,理解这一点很重要
重写findClass
public class Classloader extends ClassLoader { public static void main(String[] args) throws ClassNotFoundException { Classloader classloader = new Classloader(); Class<?> clazz1 = classloader.loadClass("com.luban.Classloader"); System.out.println("clazz1: " + clazz1.getClassLoader()); System.out.println("clazz1 hashcode: " + clazz1.hashCode()); Classloader classloader2 = new Classloader(); Class<?> clazz2 = classloader2.loadClass("com.luban.Classloader"); System.out.println("clazz2: " + clazz2.getClassLoader()); System.out.println("clazz2 hashcode: " + clazz1.hashCode()); System.out.println(clazz1 == clazz2); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { System.out.println("Classloader findClass"); return null; }
结果
D:javaEirjavabinjava.exe -Dvisualvm.id=85101605776800 "-javaagent:D:javaEirideaIntelliJ IDEA 2019.3.2libidea_rt.jar=51918:D:javaEirideaIntelliJ IDEA 2019.3.2bin" -Dfile.encoding=UTF-8 -classpath D:javaEirjavajrelibcharsets.jar;D:javaEirjavajrelibdeploy.jar;D:javaEirjavajrelibextaccess-bridge-64.jar;D:javaEirjavajrelibextcldrdata.jar;D:javaEirjavajrelibextdnsns.jar;D:javaEirjavajrelibextjaccess.jar;D:javaEirjavajrelibextjfxrt.jar;D:javaEirjavajrelibextlocaledata.jar;D:javaEirjavajrelibextnashorn.jar;D:javaEirjavajrelibextsunec.jar;D:javaEirjavajrelibextsunjce_provider.jar;D:javaEirjavajrelibextsunmscapi.jar;D:javaEirjavajrelibextsunpkcs11.jar;D:javaEirjavajrelibextzipfs.jar;D:javaEirjavajrelibjavaws.jar;D:javaEirjavajrelibjce.jar;D:javaEirjavajrelibjfr.jar;D:javaEirjavajrelibjfxswt.jar;D:javaEirjavajrelibjsse.jar;D:javaEirjavajrelibmanagement-agent.jar;D:javaEirjavajrelibplugin.jar;D:javaEirjavajrelibresources.jar;D:javaEirjavajrelibrt.jar;C:UsersyurzIdeaProjectsspring-frameworklubanoutproductionclasses;C:UsersyurzIdeaProjectsspring-frameworklubanoutproductionresources;C:UsersyurzIdeaProjectsspring-frameworkspring-jdbcoutproductionclasses;C:UsersyurzIdeaProjectsspring-frameworkspring-jdbcoutproductionresources;C:UsersyurzIdeaProjectsspring-frameworkspring-contextoutproductionclasses;C:UsersyurzIdeaProjectsspring-frameworkspring-contextoutproductionresources;C:UsersyurzIdeaProjectsspring-frameworkspring-aopoutproductionclasses;C:UsersyurzIdeaProjectsspring-frameworkspring-aopoutproductionresources;C:UsersyurzIdeaProjectsspring-frameworkspring-weboutproductionclasses;C:UsersyurzIdeaProjectsspring-frameworkspring-weboutproductionresources;C:UsersyurzIdeaProjectsspring-frameworkspring-oxmoutproductionclasses;C:UsersyurzIdeaProjectsspring-frameworkspring-oxmoutproductionresources;D:javaEir.gradlecachesmodules-2files-2.1org.mybatismybatis-spring2.0.53bfeffacf579b7f607486c1cd32224643102c316mybatis-spring-2.0.5.jar;D:javaEir.gradlecachesmodules-2files-2.1mysqlmysql-connector-java5.1.4461b6b998192c85bb581c6be90e03dcd4b9079db4mysql-connector-java-5.1.44.jar;D:javaEir.gradlecachesmodules-2files-2.1org.mybatismybatis3.4.55200759b13f70652995fd206a52fdc98b01c65cdmybatis-3.4.5.jar;D:javaEir.gradlecachesmodules-2files-2.1org.aspectjaspectjrt1.9.5dc063f2557f6734ccb529b4c1d97132e4c8c739aspectjrt-1.9.5.jar;D:javaEir.gradlecachesmodules-2files-2.1org.aspectjaspectjweaver1.9.51740dc9140103b796d1722668805fd4cf852780caspectjweaver-1.9.5.jar;D:javaEir.gradlecachesmodules-2files-2.1javax.annotationjavax.annotation-api1.2479c1e06db31c432330183f5cae684163f186146javax.annotation-api-1.2.jar;C:UsersyurzIdeaProjectsspring-frameworkspring-txoutproductionclasses;C:UsersyurzIdeaProjectsspring-frameworkspring-txoutproductionresources;C:UsersyurzIdeaProjectsspring-frameworkspring-beansoutproductionclasses;C:UsersyurzIdeaProjectsspring-frameworkspring-beansoutproductionresources;C:UsersyurzIdeaProjectsspring-frameworkspring-expressionoutproductionclasses;C:UsersyurzIdeaProjectsspring-frameworkspring-expressionoutproductionresources;C:UsersyurzIdeaProjectsspring-frameworkspring-coreoutproductionclasses;C:UsersyurzIdeaProjectsspring-frameworkspring-corebuildlibsspring-cglib-repack-3.3.0.jar;C:UsersyurzIdeaProjectsspring-frameworkspring-corebuildlibsspring-objenesis-repack-3.1.jar;C:UsersyurzIdeaProjectsspring-frameworkspring-instrumentoutproductionclasses;C:UsersyurzIdeaProjectsspring-frameworkspring-jcloutproductionclasses;C:UsersyurzIdeaProjectsspring-frameworkspring-jcloutproductionresources com.luban.Classloader clazz1: sun.misc.Launcher$AppClassLoader@18b4aac2 clazz1 hashcode: 692404036 clazz2: sun.misc.Launcher$AppClassLoader@18b4aac2 clazz2 hashcode: 692404036 true
fingClass没有被加载,说明没有调自定义类加载器,而是向上委培,调了应用类加载器。
加上loadclass
package com.luban; public class Classloader extends ClassLoader { public static void main(String[] args) throws ClassNotFoundException { Classloader classloader = new Classloader(); Class<?> clazz1 = classloader.loadClass("com.luban.Classloader"); System.out.println("clazz1: " + clazz1.getClassLoader()); System.out.println("clazz1 hashcode: " + clazz1.hashCode()); Classloader classloader2 = new Classloader(); Class<?> clazz2 = classloader2.loadClass("com.luban.Classloader"); System.out.println("clazz2: " + clazz2.getClassLoader()); System.out.println("clazz2 hashcode: " + clazz1.hashCode()); System.out.println(clazz1 == clazz2); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { System.out.println("Classloader findClass"); return null; } @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("com.luban")) { // 打破双亲委派 c = findClass(name); } else { 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; } } }
结果
Classloader findClass Classloader findClass Exception in thread "main" java.lang.NullPointerException at com.luban.Classloader.main(Classloader.java:8) Process finished with exit code 1
说明if判断打破双亲委派
6、JVM中的沙箱安全机制
- 有这样的判断AccessController.doPrivileged,就是沙箱安全保护
比如我定义了一个类名为String所在包为java.lang,因为这个类本来是属于jdk的,如果没有沙箱安全机制的话,这个类将会污染到我所有的String,但是由于沙箱安全机制,所以就委托顶层的bootstrap加载器查找这个类,如果没有的话就委托extsion,extsion没有就到aapclassloader,但是由于String就是jdk的源代码,所以在bootstrap那里就加载到了,先找到先使用,所以就使用bootstrap里面的String,后面的一概不能使用,这就保证了不被恶意代码污染
原文地址:jvm_类加载器
文章评论