1. 类加载流程图, 左边是C++流程,右边是jvm流程
2. 查看汇编指令, 进入字节码目录:
javap -v 类名.class , 例如:
3. 类加载流程示例:
package com.zqd.day01; /** * @author ADeng * @date 2022/5/31 * @desc */ public class A { static { System.out.println("init A"); } /** 打印结果: init A init B constract B A main */ public static void main(String[] args) { B b = new B(); System.out.println("A main"); C c = null; } } class B { static { System.out.println("init B"); } public B() { System.out.println("constract B"); } } class C { static { System.out.println("init C"); } public C() { System.out.println("constract C"); } }
4 类加载器:启动类加载器,扩展类加载器, 应用类加载器, 自定义类加载器
4.1 启动类加载器
因为启动类加载器是由C++编写的,通过Java程序去查看显示的是null, 因此,启动类加载器无法被Java程序调用
启动类加载器不像其他类加载器有实体,它是没有实体的,JVM将C++处理类加载的一套逻辑定义为启动类加载器
查看启动类加载器的加载路径, 也可以通过-Xbootclasspath指定
URL[] urLs = Launcher.getBootstrapClassPath().getURLs(); for (URL urL : urLs) { System.out.println(urL); }
openjdk源码
int JNICALL JavaMain(void * _args) { …… mainClass = LoadMainClass(env, mode, what); …… } 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; } jclass GetLauncherHelperClass(JNIEnv *env) { if (helperClass == NULL) { NULL_CHECK0(helperClass = FindBootStrapClass(env, "sun/launcher/LauncherHelper")); } return helperClass; } jclass FindBootStrapClass(JNIEnv *env, const char* classname) { if (findBootClass == NULL) { findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT, "JVM_FindClassFromBootLoader"); if (findBootClass == NULL) { JLI_ReportErrorMessage(DLL_ERROR4, "JVM_FindClassFromBootLoader"); return NULL; } } return findBootClass(env, classname); } JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env, const char* name)) JVMWrapper2("JVM_FindClassFromBootLoader %s", name); // Java libraries should ensure that name is never null... if (name == NULL || (int)strlen(name) > Symbol::max_length()) { // It's impossible to create this class; the name cannot fit // into the constant pool. return NULL; } TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL); Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL); if (k == NULL) { return NULL; } if (TraceClassResolution) { trace_class_resolution(k); } return (jclass) JNIHandles::make_local(env, k->java_mirror()); JVM_END
就是通过启动类加载器加载类sun.launcher.LauncherHelper,执行该类的方法checkAndLoadMain
,加载main函数所在的类,启动扩展类加载器、应用类加载器也是在这个时候完成的
4.2 扩展类加载器
查看类加载器加载的路径, 可以通过java.ext.dirs指定
public static void main(String[] args) { ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent(); URLClassLoader urlClassLoader = (URLClassLoader) classLoader; URL[] urls = urlClassLoader.getURLs(); for (URL url : urls) { System.out.println(url); } }
4.3 应用类加载器
默认加载用户程序的类加载器, 查看类加载器加载的路径
public static void main(String[] args) { String[] urls = System.getProperty("java.class.path").split(":"); for (String url : urls) { System.out.println(url); } System.out.println("================================"); URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); URL[] urls1 = classLoader.getURLs(); for (URL url : urls1) { System.out.println(url); } }
4.4 自定义类加载器
继承类java.lang.ClassLoader
5 类加载器创建链
上面已经描述了启动类加载器没有实体,只是将一段加载逻辑命名成启动类加载器。
启动类加载器做的事情是:加载类sun.launcher.LauncherHelper,执行该类的方法checkAndLoadMain,
启动类、扩展类、应用类加载器逻辑上的父子关系就是在这个方法的调用链中生成的?
5.1 \openjdk\jdk\src\share\classes\sun\launcher\LauncherHelper.java
核心代码:ClassLoader.getSystemClassLoader();
public enum LauncherHelper { …… private static final ClassLoader scloader = ClassLoader.getSystemClassLoader(); …… public static Class<?> checkAndLoadMain(boolean printToStderr, int mode, String what) { …… mainClass = scloader.loadClass(cn); ……
5.2 \openjdk\jdk\src\share\classes\java\lang\ClassLoader.java
核心代码:sun.misc.Launcher.getLauncher();
public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkClassLoaderPermission(scl, Reflection.getCallerClass()); } return scl; } private static synchronized void initSystemClassLoader() { if (!sclSet) { if (scl != null) throw new IllegalStateException("recursive invocation"); sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); ……
5.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);
public class Launcher { private static URLStreamHandlerFactory factory = new Factory(); private static Launcher launcher = new Launcher(); private static String bootClassPath = System.getProperty("sun.boot.class.path"); public static Launcher getLauncher() { return launcher; } private ClassLoader loader; 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); ……
5.4 扩展类加载器的创建流程
第二个参数传的是null,其实就是parent=null
public static ExtClassLoader getExtClassLoader() throws IOException { …… return new ExtClassLoader(dirs); …… public ExtClassLoader(File[] dirs) throws IOException { super(getExtURLs(dirs), null, factory); } URLClassLoader(URL[] urls, ClassLoader parent, AccessControlContext acc) {
5.5 应用类加载器的创建流程
public static ClassLoader getAppClassLoader(final ClassLoader extcl) throws IOException { final String s = System.getProperty("java.class.path"); final File[] path = (s == null) ? new File[0] : getClassPath(s); // Note: on bugid 4256530 // Prior implementations of this doPrivileged() block supplied // a rather restrictive ACC via a call to the private method // AppClassLoader.getContext(). This proved overly restrictive // when loading classes. Specifically it prevent // accessClassInPackage.sun.* grants from being honored. // return AccessController.doPrivileged( new PrivilegedAction<AppClassLoader>() { public AppClassLoader run() { URL[] urls = (s == null) ? new URL[0] : pathToURLs(path); return new AppClassLoader(urls, extcl); } }); } AppClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent, factory); }
应用类、扩展类加载器的父子关系就是这样建立的
6 双亲委派
如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类, 而是把这个请求委派给父类加载器
, 每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;
只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载
7. 打破双亲委派
7.1 描述
因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制
,父类加载器无法加载到需要的文件, 以Driver接口为例,由于Driver接口定义在jdk当中的
,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector
,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类
,然后进行管理,但是DriverManager由启动类加载器加载, 只能记载JAVA_HOME的lib下文件
,而其实现是由服务商提供的,由系统类加载器加载
,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派。
, 类似这样的情况就需要打破双亲委派。打破双亲委派的意思其实就是不委派、向下委派
7.2 自定义类加载器
7.3 SPI
一种特殊的类加载器,可以通过Thread获取,基于此可实现逆向委托加载, 为了解决双亲委派的缺陷而生
//获取 Thread.currentThread().getContextClassLoader() // 设置 Thread.currentThread().setContextClassLoader(new Classloader_4());
9. 沙箱安全
跟Linux的权限机制有点像
openjdk源码会看到有这样的判断AccessController.doPrivileged
比如自定义了一个类名为String所在包为java.lang,因为这个类本来是属于jdk的,如果没有沙箱安全机制
,这个类将会污染到所有的String类, 但是由于沙箱安全机制,所以就委托顶层的bootstrap加载器查找这个类
,如果没有的话就委托extsion,extsion没有就到aapclassloader,但是由于String就是jdk的源代码
,所以在bootstrap那里就加载到了,先找到先使用,所以就使用bootstrap里面的String
, 后面的一概不能使用,这就保证了不被恶意代码污染