jvm_类加载器

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);
        }

总结:

  1. jdk源码中的LoadMainClass方法中,先通过GetLauncherHelperClass获取启动类加载器BootStrapClass。
  2. 调用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,后面的一概不能使用,这就保证了不被恶意代码污染

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值