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指定

1
2
3
4
5
6
7
8
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 应用类加载器

        默认加载用户程序的类加载器,  查看类加载器加载的路径

1
2
3
4
5
6
7
8
9
10
11
12
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

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

 

 

 

 

      

 

 

 

 

 

 

 

posted @   剑阁丶神灯  阅读(84)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示