-
JVM 类加载
三层类加载器及父子关系建立
1.1 三层类加载器
BootStrapClassLoader:
查看加载路劲方法:
URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL urL : urLs) {
System.out.println(url.toExternalForm());
}
运行结果:
file:/C:/Java/jdk1.8.0_101/jre/lib/resources.jar
file:/C:/Java/jdk1.8.0_101/jre/lib/rt.jar
file:/C:/Java/jdk1.8.0_101/jre/lib/sunrsasign.jar
file:/C:/Java/jdk1.8.0_101/jre/lib/jsse.jar
file:/C:/Java/jdk1.8.0_101/jre/lib/jce.jar
file:/C:/Java/jdk1.8.0_101/jre/lib/charsets.jar
file:/C:/Java/jdk1.8.0_101/jre/lib/jfr.jar
file:/C:/Java/jdk1.8.0_101/jre/classes
启动类加载器是由C++实现的加载器,没有对应的.Java类,主要加载JDK核心类库。如:rt.jar 也可以通过-Xbootclasspath指定 对应openJdk源码 参考:
1 int JNICALL
2 JavaMain(void * _args)
3 {
4 ……
5 mainClass = LoadMainClass(env, mode, what);
6 ……
7 }
8
9 static jclass
10 LoadMainClass(JNIEnv *env, int mode, char *name)
11 {
12 jmethodID mid;
13 jstring str;
14 jobject result;
15 jlong start, end;
16 jclass cls = GetLauncherHelperClass(env);
17 NULL_CHECK0(cls);
18 if (JLI_IsTraceLauncher()) {
19 start = CounterGet();
20 }
21 //核心方法调用 checkAndLoadMain
22 NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
23 "checkAndLoadMain",
24 "(ZILjava/lang/String;)Ljava/lang/Class;"));
25
26 str = NewPlatformString(env, name);
27 CHECK_JNI_RETURN_0(
28 result = (*env)->CallStaticObjectMethod(
29 env, cls, mid, USE_STDERR, mode, str));
30
31 if (JLI_IsTraceLauncher()) {
32 end = CounterGet();
33 printf("%ld micro seconds to load main class\n",
34 (long)(jint)Counter2Micros(end-start));
35 printf("----%s----\n", JLDEBUG_ENV_ENTRY);
36 }
37
38 return (jclass)result;
39 }
40
41 jclass
42 GetLauncherHelperClass(JNIEnv *env)
43 {
44 if (helperClass == NULL) {
45 NULL_CHECK0(helperClass = FindBootStrapClass(env,
46 "sun/launcher/LauncherHelper"));
47 }
48 return helperClass;
49 }
50
51 jclass
52 FindBootStrapClass(JNIEnv *env, const char* classname)
53 {
54 if (findBootClass == NULL) {
55 findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,
56 "JVM_FindClassFromBootLoader");
57 if (findBootClass == NULL) {
58 JLI_ReportErrorMessage(DLL_ERROR4,
59 "JVM_FindClassFromBootLoader");
60 return NULL;
61 }
62 }
63 return findBootClass(env, classname);
64 }
65
66 JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,
67 const char* name))
68 JVMWrapper2("JVM_FindClassFromBootLoader %s", name);
69
70 // Java libraries should ensure that name is never null...
71 if (name == NULL || (int)strlen(name) > Symbol::max_length()) {
72 // It's impossible to create this class; the name cannot fit
73 // into the constant pool.
74 return NULL;
75 }
76
77 TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
78 Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
79 if (k == NULL) {
80 return NULL;
81 }
82
83 if (TraceClassResolution) {
84 trace_class_resolution(k);
85 }
86 return (jclass) JNIHandles::make_local(env, k->java_mirror());
87 JVM_END
这套逻辑做的事情就是通过启动类加载器加载类sun.launcher.LauncherHelper,执行该类的方法checkAndLoadMain,加载main函数所在的类,启动扩展类加载器、应用类加载器也是在这个时候完成的
ExtClassLoader:
查看加载路劲方法:
1 public static void main(String[] args) {
2 ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();
3
4 URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
5
6 URL[] urls = urlClassLoader.getURLs();
7 for (URL url : urls) {
8 System.out.println(url);
9 }
10 }
拓展类加载器:主要负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有jar包或者由java.ext.dirs系统属性指定的jar包
AppClassLoader:
查看加载路劲方法:
1 public static void main(String[] args) {
2 String[] urls = System.getProperty("java.class.path").split(":");
3
4 for (String url : urls) {
5 System.out.println(url);
6 }
7
8 System.out.println("================================");
9
10 URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
11
12 URL[] urls1 = classLoader.getURLs();
13 for (URL url : urls1) {
14 System.out.println(url);
15 }
16 }
应用类加载器: 又称为系统类加载器,负责在JVM启动时,加载来自在命令java中的classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的JAR类包和类路径.
自定义类加载器:
1 public class Classloader_1 extends ClassLoader {
2
3 public static void main(String[] args) {
4 Classloader_1 classloader = new Classloader_1();
5
6 try {
7 Class<?> clazz = classloader.loadClass(Classloader_1_A.class.getName());
8
9 System.out.println(clazz);
10 System.out.println(clazz.getClassLoader());
11 } catch (ClassNotFoundException e) {
12 e.printStackTrace();
13 }
14 }
15
16 }
运行结果:
class com.luban.ziya.classloader.Classloader_1_A
jdk.internal.loader.ClassLoaders$AppClassLoader@2f0e140b
为什么还是AppClassLoader呢 ?
因为双亲委派模型,自定义类加载器的父类能够加载到这个类
可以重写 loadClass方法 实现拒绝 AppClassLoader加载
2.详解启动类加载器
上面已经讲了启动类加载器没有实体,只是将一段加载逻辑命名成启动类加载器。启动类加载器做的事情是:加载类sun.launcher.LauncherHelper,执行该类的方法checkAndLoadMain……启动类、扩展类、应用类加载器逻辑上的父子关系就是在这个方法的调用链中生成的?
1、\openjdk\jdk\src\share\classes\sun\launcher\LauncherHelper.java
核心代码:ClassLoader.getSystemClassLoader();
1 public enum LauncherHelper {
2 ……
3 private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
4 ……
5 public static Class<?> checkAndLoadMain(boolean printToStderr,
6 int mode,
7 String what) {
8 ……
9 mainClass = scloader.loadClass(cn);
10 ……
2、\openjdk\jdk\src\share\classes\java\lang\ClassLoader.java
核心代码:sun.misc.Launcher.getLauncher();
1 public static ClassLoader getSystemClassLoader() {
2 initSystemClassLoader();
3 if (scl == null) {
4 return null;
5 }
6 SecurityManager sm = System.getSecurityManager();
7 if (sm != null) {
8 checkClassLoaderPermission(scl, Reflection.getCallerClass());
9 }
10 return scl;
11 }
12
13 private static synchronized void initSystemClassLoader() {
14 if (!sclSet) {
15 if (scl != null)
16 throw new IllegalStateException("recursive invocation");
17 sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
18 ……
3、\openjdk\jdk\src\share\classes\sun\misc\Launcher.java
核心代码:
- private static Launcher launcher = new Launcher();
- extcl = ExtClassLoader.getExtClassLoader();
- loader = AppClassLoader.getAppClassLoader(extcl);
- Thread.currentThread().setContextClassLoader(loader);
1 public class Launcher {
2 private static URLStreamHandlerFactory factory = new Factory();
3 private static Launcher launcher = new Launcher();
4 private static String bootClassPath =
5 System.getProperty("sun.boot.class.path");
6
7 public static Launcher getLauncher() {
8 return launcher;
9 }
10
11 private ClassLoader loader;
12
13 public Launcher() {
14 // Create the extension class loader
15 ClassLoader extcl;
16 try {
17 extcl = ExtClassLoader.getExtClassLoader();
18 } catch (IOException e) {
19 throw new InternalError(
20 "Could not create extension class loader", e);
21 }
22
23 // Now create the class loader to use to launch the application
24 try {
25 loader = AppClassLoader.getAppClassLoader(extcl);
26 } catch (IOException e) {
27 throw new InternalError(
28 "Could not create application class loader", e);
29 }
30
31 // Also set the context class loader for the primordial thread.
32 Thread.currentThread().setContextClassLoader(loader);
33 ……
4、扩展类加载器的创建流程
1 public static ExtClassLoader getExtClassLoader() throws IOException
2 {
3 ……
4 return new ExtClassLoader(dirs);
5 ……
6
7 public ExtClassLoader(File[] dirs) throws IOException {
8 super(getExtURLs(dirs), null, factory);
9 }
10
11 URLClassLoader(URL[] urls, ClassLoader parent,
12 AccessControlContext acc) {
第二个参数传的是null,其实就是parent=null
5、应用类加载器的创建流程
1 public static ClassLoader getAppClassLoader(final ClassLoader extcl)
2 throws IOException {
3 final String s = System.getProperty("java.class.path");
4 final File[] path = (s == null) ? new File[0] : getClassPath(s);
5
6 // Note: on bugid 4256530
7 // Prior implementations of this doPrivileged() block supplied
8 // a rather restrictive ACC via a call to the private method
9 // AppClassLoader.getContext(). This proved overly restrictive
10 // when loading classes. Specifically it prevent
11 // accessClassInPackage.sun.* grants from being honored.
12 //
13 return AccessController.doPrivileged(
14 new PrivilegedAction<AppClassLoader>() {
15 public AppClassLoader run() {
16 URL[] urls =
17 (s == null) ? new URL[0] : pathToURLs(path);
18 return new AppClassLoader(urls, extcl);
19 }
20 });
21 }
22
23 AppClassLoader(URL[] urls, ClassLoader parent) {
24 super(urls, parent, factory);
25 }
应用类、扩展类加载器的父子关系就是这样建立的
3.什么是双亲委派
.Class--->AppClassLoader---->ExtClassLoader--->BootStrapClassLoader
findLoadedClass 有则返回 无则委派parent (ExtClassLoader,BootStrapClassLoader非父子关系 )
通过委派还是没有 那么会通过findClass获得
双亲委派主要体现在 loadClass中
1 protected Class<?> loadClass(String name, boolean resolve)
2 throws ClassNotFoundException
3 {
4 synchronized (getClassLoadingLock(name)) {
5 // First, check if the class has already been loaded
6 Class<?> c = findLoadedClass(name);
7 if (c == null) {
8 long t0 = System.nanoTime();
9 //一下代码为双亲委派核心
10 try {
11 if (parent != null) {
12 c = parent.loadClass(name, false);
13 } else {
14 c = findBootstrapClassOrNull(name);
15 }
16 } catch (ClassNotFoundException e) {
17 // ClassNotFoundException thrown if class not found
18 // from the non-null parent class loader
19 }
20
21 if (c == null) {
22 // If still not found, then invoke findClass in order
23 // to find the class.
24 long t1 = System.nanoTime();
25 c = findClass(name);
26
27 // this is the defining class loader; record the stats
28 PerfCounter.getParentDelegationTime().addTime(t1 - t0);
29 PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
30 PerfCounter.getFindClasses().increment();
31 }
32 }
33 if (resolve) {
34 resolveClass(c);
35 }
36 return c;
37 }
38 }
4.打破双亲委派
如何打破双亲委派机制?
SPI机制,自定义类加载器(重写findClass,loadClass)
双亲委派: 向上委派
打破: 向下委派,不委派
典型示例: JDBC -->DriverManager
SPI:是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制
5.JVM中的沙箱安全机制
跟Linux的权限机制有点像
比如我定义了一个类名为String所在包为java.lang,因为这个类本来是属于jdk的,如果没有沙箱安全机制的话,这个类将会污染到我所有的String,但是由于沙箱安全机制,所以就委托顶层的bootstrap加载器查找这个类,如果没有的话就委托extsion,extsion没有就到aapclassloader,但是由于String就是jdk的源代码,所以在bootstrap那里就加载到了,先找到先使用,所以就使用bootstrap里面的String,后面的一概不能使用,这就保证了不被恶意代码污染