• JVM类加载机制


    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");
        }
    }
    View Code

    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

          是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件
         ,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能
         ,比如在Dubbo、JDBC中都使用到了SPI机制
     8.  线程上下文类加载器  

          一种特殊的类加载器,可以通过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

          , 后面的一概不能使用,这就保证了不被恶意代码污染

     

          

     

     

  • 相关阅读:
    JDBC的PreparedStatement是什么?
    ArrayList、LinkedList、Vector 的区别。
    final finally finalize区别
    面向对象三大特性
    JDBC访问数据库的基本步骤是什么?
    报表中的地图怎么做?
    报表工具如何实现“点击查看原图”
    玩转报表排名之组内排名
    动态隐藏行
    OUT 了??还没玩转报表超链接
  • 原文地址:https://www.cnblogs.com/ladeng19/p/16332182.html
Copyright © 2020-2023  润新知