HotSpot8源码-类加载器的实现

3 篇文章 0 订阅

1.类加载的基本流程

JVM进行LoadClass的类加载器分三种:BootStrapClassLoader,ExtClassLoader,AppClassLoader。在不同的场景下使用相应的ClassLoader将.class字节码文件转换成JavaClass类对象,转换源码如下所示:

在这里插入图片描述
Klass对象为C++实现的对象,在JVM读取转化.class类文件中的数据后得到的结构体,即.class类文件的元数据对象。当类加载器执行loadClass方法进行类加载,SystemDictionary会读取相应的类路径下对应的.class类将其转为Klass对象,同时存入SystemDictionary系统字典哈希表结构中,key为类加载器+类名,value存的就是Klass对象。
在这里插入图片描述
读取字节码文件,返回Klass对象
在这里插入图片描述

Klass将按JVM8手册定义的.class文件格式进行读取,前4个字节为.class文件的标识符,对类文件的格式有兴趣的可以去JVM8手册查询,手册定义如下所示:
在这里插入图片描述
JVM对类文件读取,使用Klass对象进行存储,将Klass转为Java的Class对象返回,对象的模板——类,加载完毕了。
简而言之,类加载的流程:字节码文件=>Klass对象=>Java Class对象

2.双亲委派的实现

Hotspot8类加载机制的一大特点为双亲委派,具体实现需要从ClassLoader的定义的结构进行分析,实则就是面向对对象的灵活应用。下述介绍三种类加载器以及父类ClassLoader。

ClassLoader

下述为ClassLoader的源代码,为了突出本文的核心,对代码进行简略表示:

public abstract class ClassLoader {

 	//双亲委派机制,所有的类加载器必定会通过parent父类往上传
 	//为类加载器的基本属性
    private final ClassLoader parent;
    ...
	
	private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;	//指定父类加载器
       		...
        }
    }

	protected ClassLoader(ClassLoader parent) {
		//执行private ClassLoader(Void unused, ClassLoader parent)方法
        this(checkCreateClassLoader(), parent);
    }

	...
        protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException{
          try {
                    if (parent != null) {
						//递归调用,优先父类加载
                        c = parent.loadClass(name, false);
                    } else {
                    	//顶级父类为BootStrapClassLoader
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
					//自定义的findClass,默认该方法为空,由子类实现
                    c = findClass(name);
                }
		}

}

Launcher(ExtClassLoader和AppClassLoader)

Launcher作为启动器类,JVM使用该类启动主应用程序,ExtClassLoader和AppClassLoader均为它的内部类

public class Launcher {

    private ClassLoader loader;

    public Launcher() {
        // Create the extension class loader
        ClassLoader extcl;
        extcl = ExtClassLoader.getExtClassLoader();
        loader = AppClassLoader.getAppClassLoader(extcl);
    }

    /*
     * Returns the class loader used to launch the main application.
     */
    public ClassLoader getClassLoader() {
        return loader;
    }
	static class ExtClassLoader extends URLClassLoader {
         public static ExtClassLoader getExtClassLoader() throws IOException
        {
            final File[] dirs = getExtDirs();	//获取ExtClassLoader的加载路径java.ext.dirs
            try {
                return AccessController.doPrivileged(
                    new PrivilegedExceptionAction<ExtClassLoader>() {
                        public ExtClassLoader run() throws IOException {
                            int len = dirs.length;
                            for (int i = 0; i < len; i++) {
                                MetaIndex.registerDirectory(dirs[i]);
                            }
                            return new ExtClassLoader(dirs);
                        }
                    });
            }
        }
         /*
         * 该类加载器只加载指定路径的类
         * Creates a new ExtClassLoader for the specified directories.
         */
        public ExtClassLoader(File[] dirs) throws IOException {
            super(getExtURLs(dirs), null, factory);
        }
		private static File[] getExtDirs() {
			//获取ExtClassLoader的加载路径,在系统变量中传入。
            String s = System.getProperty("java.ext.dirs");
            ...
     	}
     }
     
    static class AppClassLoader extends URLClassLoader {
  	 	public static ClassLoader getAppClassLoader(final ClassLoader extcl)
            throws IOException
        {
        	//获取AppClassLoader的加载路径
            final String s = System.getProperty("java.class.path");	
            final File[] path = (s == null) ? new File[0] : getClassPath(s);
            return AccessController.doPrivileged(
                new PrivilegedAction<AppClassLoader>() {
                    public AppClassLoader run() {
                    URL[] urls =
                        (s == null) ? new URL[0] : pathToURLs(path);
                    return new AppClassLoader(urls, extcl);
                }
            });
        }

        /*
         * urls:AppClassLoader的加载路径
         * parent:AppClassLoader的父类加载器
         * Creates a new AppClassLoader
         */
        AppClassLoader(URL[] urls, ClassLoader parent) {
        	//指定父类加载器
            super(urls, parent, factory);
        }
    }
  }

从Launcher对象构造方法可以发现Java的ExtClassLoader和AppClassLoader类加载器对象就在方法中进行实例化的。
先创建ExtClassLoader对象,将它的引用传入AppClassLoader对象中,指定extcl为AppClassLoader的parent即父类加载器。同时使用System.getProperty(“java.ext.dirs”)获取ExtClassLoader类加载器的加载路径以及System.getProperty(“java.class.path”)获取AppClassLoader类加载器的加载路径,以此确认类加载器的加载权限。到此,ExtClassLoader作为AppClassLoader的父类加载器关系已经确认。想要创建这两个类加载器对象必须先加载Launcher启动器对象,那么对象又是在哪加载的呢?

Launcher对象的加载

这里从源头开始分析,假设在Linux下运行Java程序,首先走JVM的mian.c下的main方法即程序入口方法:

int
main(int argc, char **argv)
{
    int margc;
    char** margv;
    const jboolean const_javaw = JNI_FALSE;
#endif /* JAVAW */
#ifdef _WIN32
 ...
#else /* *NIXES */
    margc = argc;
    margv = argv;
#endif /* WIN32 */      //Linux下的主线程入口
    return JLI_Launch(margc, margv,
                   sizeof(const_jargs) / sizeof(char *), const_jargs,
                   sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
                   FULL_VERSION,
                   DOT_VERSION,
                   (const_progname != NULL) ? const_progname : *margv,
                   (const_launcher != NULL) ? const_launcher : *margv,
                   (const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
                   const_cpwildcard, const_javaw, const_ergo_class);
}

JLI_Launch执行JVMInit方法初始化JVM并创建JavaMain线程,然后调用ContinueInNewThread=>ContinueInNewThread0(创建线程)=>JavaMain方法执行=>LoadMainClass(加载Main函数的类信息)


/*
 * 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;
    //先用BootClassloader加载LauncherHelper类   
    //LauncherHelper,Java虚拟机还没有启动,主类没有加载完成,由它提供了所有的功能
    jclass cls = GetLauncherHelperClass(env);   //映射获取LauncherHelper javaClass实例
    NULL_CHECK0(cls);
    if (JLI_IsTraceLauncher()) {
        start = CounterGet();
    }
    NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
                "checkAndLoadMain",
                "(ZILjava/lang/String;)Ljava/lang/Class;"));
    //将C的char数组表示的字符串转为java的String对象,让Java对象能够识别该name
    str = NewPlatformString(env, name); 

    //然后使用LauncherHelper类调checkAndLoadMain方法加载str字符串代表的Class类
    result = (*env)->CallStaticObjectMethod(env, cls, mid, USE_STDERR, mode, str);
	...

    return (jclass)result;
}

BootStrapClassLoader先加载LauncherHelper,再由LauncherHelper对Launcher进行加载,以此完成App/Ext ClassLoader的加载。如何完成加载的呢?请继续看LauncherHelper的CheckAndLoadMian方法

public static Class<?> checkAndLoadMain(boolean printToStderr,
                                            int mode,
                                            String what) {
        initOutput(printToStderr);
        // get the class name
        String cn = null;
        switch (mode) {
            case LM_CLASS:				//类比静态链接库.o文件
                cn = what;
                break;
            case LM_JAR:				//jar包类比.a文件(.o文件的集合)
                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;

     	
    }

关键在 mainClass = scloader.loadClass(cn) ,scloader为LauncherHelper的静态变量:

private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();	//加载appClassLoader,初始化Laucher启动器

且看它的AppClassLoader的初始化

 public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();	//初始化类加载器
        if (scl == null) {
            return null;
        }
    ...
        return scl;
    }

initSystemClassLoader:

private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
        ...
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();	//获取Launcher启动器
      	...
    }

Launcher类:

public class Launcher {
	    private static URLStreamHandlerFactory factory = new Factory();
    private static Launcher launcher = new Launcher();	//Launcher对象的创建
    private static String bootClassPath = System.getProperty("sun.boot.class.path");	//JDK系统库函数的路径

    public static Launcher getLauncher() {
        return launcher;
    }
    ...
}

Launcher的类加载完成。由类构造器对Launcher对象进行实例化。到此Launcher(ext/app ClassLoader)启动器对象创建完毕。

BootStrap ClassLoader

BootStrapClassLoader是顶级父加载器,用于加载JDK系统库函数的类.以此可以推断出,创建Laucher类的LaucherHelper类必定是由它来加载的。接下来回到LoadMainClass方法:

static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{
    jmethodID mid;
    jstring str;
    jobject result;
    jlong start, end;
    //先用BootClassloader加载LauncherHelper类 
    //LauncherHelper,Java虚拟机还没有启动,主类没有加载完成,由它提供了所有的功能
    jclass cls = GetLauncherHelperClass(env);   //映射获取LauncherHelper javaClass实例
 	...

    return (jclass)result;
}

GetLauncherHelperClass方法使用BootStrapClass去加载LauncherHelper类,获取它的JavaClass类信息:

jclass
GetLauncherHelperClass(JNIEnv *env)
{
    if (helperClass == NULL) {
        //顶层类加载器 BootStrapClass,去加载系统库函数 LauncherHelper
        NULL_CHECK0(helperClass = FindBootStrapClass(env,
                "sun/launcher/LauncherHelper"));
    }
    return helperClass;
}

FindBootStrapClass:

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;
       }
   }
   //函数指针,走jvm.cpp中JVM_FindClassFromBootLoader函数
   return findBootClass(env, classname);
}

jvm.cpp

//JVM_FindClassFromBootLoader
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); //根据key查系统字典获取Klass对象
  if (k == NULL) {
    return NULL;
  }

  if (TraceClassResolution) {
    trace_class_resolution(k);
  }
  return (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END

查SystemDictionary获取LauncherHelper的Klass对象,然后转为JavaClass对象进行返回,LauncherHelper的类加载就完成了。
在整个流程中,并没有发现BootStrapClassLoader的实例Java对象,而是调用了
JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv env,const char name))**
进行类加载,所以我们其实可以推测出它就是BootStrapClassLoader顶级类加载器,由C++代码实现,而不是Java实例对象,所以我们在Java程序中使用ClassLoader.getSystemClassLoader().getParent().getParent()获取的BootStrap类加载器为NULL。
到此,我们可以总结出三种类加载器的关系了:
在这里插入图片描述

loadClass

三种类加载器的关系和它们被加载的时机已经明确了,接下来我们可以对LoadClass进行探讨了:

   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) {
                try {
                    if (parent != null) {
						//优先父类加载
                        c = parent.loadClass(name, false);
                    } else {
                    	//顶级父类为BootStrapClassLoader
                        c = findBootstrapClassOrNull(name);
                    }
                } 
                if (c == null) {
					//自定义的findClass,默认该方法为空
                    c = findClass(name);
            		...
                }
            }
            return c;
        }
    }

当一个自定义类需要被加载,AppClassLoader调用LoadClass,然后由它的parent(ps:也就是ExtClaassLoader)执行LoadClass,直到BootStrapClassLoader,即顶级父类优先加载当前类。即递归调用。
当BootStrapClassLoader检查自己的加载路径(由System.getProperty(“sun.boot.class.path”)确认),检查后发现当前类不属于自己加载就会返回给ExtClassLoader,它检查自己的加载路径(由System.getProperty(“java.ext.dirs”)确认),检查后发现也不属于自己加载就返回给AppClassLoader,它检查自己的加载路径(由System.getProperty(“java.class.path”)确认),发现属于自己加载,然后对指定路径进行类加载。
接下来请看类加载的具体过程:findClass

   /**找到并加载这个类
     * Finds and loads the class with the specified name from the URL search
	...
     */
    protected Class<?> findClass(final String name)
         throws ClassNotFoundException
    {
        try {
			//访问权限检查
            return AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        //获取绝对路径
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            }... 
                        } 
                    }
                }, acc);
        } ...
    }

根据注解,findClass的作用是找到并加载这个类,我们传入类路径,如com.lwl.hello会将其解析为E:/projName/com/lwl/control.hello.class,因为类路径最终还是要落地到文件系统上进行类文件的索引。AppClassLoader确定了当前类是由自己加载的后,开始加载类,进行defineClass方法的执行。

 private Class<?> defineClass(String name, Resource res) throws IOException {
        long t0 = System.nanoTime();
        int i = name.lastIndexOf('.');
        URL url = res.getCodeSourceURL();
       ...
        // Now read the class bytes and define the class
        java.nio.ByteBuffer bb = res.getByteBuffer();
        if (bb != null) {
            // Use (direct) ByteBuffer:
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
			//执行ClassLoader.c类的defineClass(byte[] b, int off, int len)方法
			return defineClass(name, bb, cs);
        } else {
            byte[] b = res.getBytes();
            // must read certificates AFTER reading bytes.
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, b, 0, b.length, cs);
        }
    }

defineClass(name, bb, cs)的执行:

	protected final Class<?> defineClass(byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(null, b, off, len, null);
    }
    ...
	protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
		//调用的是ClassLoader.c文件下的Java_java_lang_ClassLoader_defineClass1方法
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

Class<?> c = defineClass1(name, b, off, len, protectionDomain, source)的执行:

JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass1(JNIEnv *env,
                                        jobject loader,
                                        jstring name,
                                        jbyteArray data,
                                        jint offset,
                                        jint length,
                                        jobject pd,
                                        jstring source)
{    
	...
  jclass result = 0;
	...
 //执行的是jvm.cpp中的JVM_ENTRY(jclass, JVM_DefineClassWithSource..)方法
    result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);
    ...
    return result;

jvm.cpp的内建函数JVM_DefineClassWithSource

JVM_ENTRY(jclass, JVM_DefineClassWithSource(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source))
  JVMWrapper2("JVM_DefineClassWithSource %s", name);
  return jvm_define_class_common(env, name, loader, buf, len, pd, source, true, THREAD);
JVM_END

jvm_define_class_common(env, name, loader, buf, len, pd, source, true, THREAD)的执行:

static jclass jvm_define_class_common(JNIEnv *env, const char *name,
                                      jobject loader, const jbyte *buf,
                                      jsize len, jobject pd, const char *source,
                                      jboolean verify, TRAPS) {
    ...
    //在SystemDictionary中根据class_name和class_loader获取Klass类对象实例
  Klass* k = SystemDictionary::resolve_from_stream(class_name, class_loader,
                                                     protection_domain, &st,
                                                     verify != 0,
                                                     CHECK_NULL);

  if (TraceClassResolution && k != NULL) {
    trace_class_resolution(k);
  }
  //将Klass类对象转为JavaClass对象返回
  return (jclass) JNIHandles::make_local(env, k->java_mirror());
}

resolve_from_stream读取类文件转为Klass对象:

Klass* SystemDictionary::resolve_from_stream(Symbol* class_name,
                                             Handle class_loader,
                                             Handle protection_domain,
                                             ClassFileStream* st,
                                             bool verify,
                                             TRAPS) {                                            
  	...
  //就是在这里对类文件进行读取的
  instanceKlassHandle k = ClassFileParser(st).parseClassFile(class_name,
                                                             loader_data,
                                                             protection_domain,
                                                             parsed_name,
                                                             verify,
                                                             THREAD);
   ...
    return k();
 }

又回到了1.类加载的基本流程类文件的转换上了,无非就是字节码文件=>Klass(C++对象)=>Java Class对象的转换。
至此类加载的全部流程源码解析结束。

3.后言

这里对类加载的流程做个比喻:在情人节这天,两个20岁的情侣去买钻戒结婚,男方跟他爸爸说我要买钻戒(LoadClass),他爸爸知道后跟他爷爷说我儿子要买钻戒(LoadClass),他爷爷检查下自己的钱包(FindClass)发现自己的钱给孙子买房了,于是返回给他爸爸说我只负责买房,我没钱了。于是,他爸爸检查自己的钱包(FindClass)发现自己的钱给儿子买车了,于是返回给他儿子说我只负责买车,我没钱了。然后他儿子检查自己的钱包(FindClass)发现自己还有点钱,便他儿子自己掏钱买了钻戒(defindClass)。最后,钻戒到手顺利结婚了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值