1. 程式人生 > 實用技巧 >JVM 類載入

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,後面的一概不能使用,這就保證了不被惡意程式碼汙染