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、详解启动类加载器
- 三种类加载器父子关系是如何建立的
openjdk\jdk\src\share\bin\java.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 class\n",
(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(openjdk\jdk\src\share\classes\sun\misc\Launcher.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:\javaEir\java\bin\java.exe -Dvisualvm.id=85101605776800 "-javaagent:D:\javaEir\idea\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=51918:D:\javaEir\idea\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\javaEir\java\jre\lib\charsets.jar;D:\javaEir\java\jre\lib\deploy.jar;D:\javaEir\java\jre\lib\ext\access-bridge-64.jar;D:\javaEir\java\jre\lib\ext\cldrdata.jar;D:\javaEir\java\jre\lib\ext\dnsns.jar;D:\javaEir\java\jre\lib\ext\jaccess.jar;D:\javaEir\java\jre\lib\ext\jfxrt.jar;D:\javaEir\java\jre\lib\ext\localedata.jar;D:\javaEir\java\jre\lib\ext\nashorn.jar;D:\javaEir\java\jre\lib\ext\sunec.jar;D:\javaEir\java\jre\lib\ext\sunjce_provider.jar;D:\javaEir\java\jre\lib\ext\sunmscapi.jar;D:\javaEir\java\jre\lib\ext\sunpkcs11.jar;D:\javaEir\java\jre\lib\ext\zipfs.jar;D:\javaEir\java\jre\lib\javaws.jar;D:\javaEir\java\jre\lib\jce.jar;D:\javaEir\java\jre\lib\jfr.jar;D:\javaEir\java\jre\lib\jfxswt.jar;D:\javaEir\java\jre\lib\jsse.jar;D:\javaEir\java\jre\lib\management-agent.jar;D:\javaEir\java\jre\lib\plugin.jar;D:\javaEir\java\jre\lib\resources.jar;D:\javaEir\java\jre\lib\rt.jar;C:\Users\yurz\IdeaProjects\spring-framework\luban\out\production\classes;C:\Users\yurz\IdeaProjects\spring-framework\luban\out\production\resources;C:\Users\yurz\IdeaProjects\spring-framework\spring-jdbc\out\production\classes;C:\Users\yurz\IdeaProjects\spring-framework\spring-jdbc\out\production\resources;C:\Users\yurz\IdeaProjects\spring-framework\spring-context\out\production\classes;C:\Users\yurz\IdeaProjects\spring-framework\spring-context\out\production\resources;C:\Users\yurz\IdeaProjects\spring-framework\spring-aop\out\production\classes;C:\Users\yurz\IdeaProjects\spring-framework\spring-aop\out\production\resources;C:\Users\yurz\IdeaProjects\spring-framework\spring-web\out\production\classes;C:\Users\yurz\IdeaProjects\spring-framework\spring-web\out\production\resources;C:\Users\yurz\IdeaProjects\spring-framework\spring-oxm\out\production\classes;C:\Users\yurz\IdeaProjects\spring-framework\spring-oxm\out\production\resources;D:\javaEir\.gradle\caches\modules-2\files-2.1\org.mybatis\mybatis-spring\2.0.5\3bfeffacf579b7f607486c1cd32224643102c316\mybatis-spring-2.0.5.jar;D:\javaEir\.gradle\caches\modules-2\files-2.1\mysql\mysql-connector-java\5.1.44\61b6b998192c85bb581c6be90e03dcd4b9079db4\mysql-connector-java-5.1.44.jar;D:\javaEir\.gradle\caches\modules-2\files-2.1\org.mybatis\mybatis\3.4.5\5200759b13f70652995fd206a52fdc98b01c65cd\mybatis-3.4.5.jar;D:\javaEir\.gradle\caches\modules-2\files-2.1\org.aspectj\aspectjrt\1.9.5\dc063f2557f6734ccb529b4c1d97132e4c8c739\aspectjrt-1.9.5.jar;D:\javaEir\.gradle\caches\modules-2\files-2.1\org.aspectj\aspectjweaver\1.9.5\1740dc9140103b796d1722668805fd4cf852780c\aspectjweaver-1.9.5.jar;D:\javaEir\.gradle\caches\modules-2\files-2.1\javax.annotation\javax.annotation-api\1.2\479c1e06db31c432330183f5cae684163f186146\javax.annotation-api-1.2.jar;C:\Users\yurz\IdeaProjects\spring-framework\spring-tx\out\production\classes;C:\Users\yurz\IdeaProjects\spring-framework\spring-tx\out\production\resources;C:\Users\yurz\IdeaProjects\spring-framework\spring-beans\out\production\classes;C:\Users\yurz\IdeaProjects\spring-framework\spring-beans\out\production\resources;C:\Users\yurz\IdeaProjects\spring-framework\spring-expression\out\production\classes;C:\Users\yurz\IdeaProjects\spring-framework\spring-expression\out\production\resources;C:\Users\yurz\IdeaProjects\spring-framework\spring-core\out\production\classes;C:\Users\yurz\IdeaProjects\spring-framework\spring-core\build\libs\spring-cglib-repack-3.3.0.jar;C:\Users\yurz\IdeaProjects\spring-framework\spring-core\build\libs\spring-objenesis-repack-3.1.jar;C:\Users\yurz\IdeaProjects\spring-framework\spring-instrument\out\production\classes;C:\Users\yurz\IdeaProjects\spring-framework\spring-jcl\out\production\classes;C:\Users\yurz\IdeaProjects\spring-framework\spring-jcl\out\production\resources 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,后面的一概不能使用,这就保证了不被恶意代码污染