类的加载分为以下几个阶段:java
咱们首先看一下JVM预约义的三种类加载器,当JVM启动的时候,Java缺省开始使用以下三种类型的类加载器:安全
(1)启动(Bootstrap)类加载器:引导类加载器是用C++代码实现的类加载器,它负责将 <JAVA_HOME>/lib下面的核心类库 或 -Xbootclasspath选项指定的jar包等 虚拟机识别的类库 加载到内存中。 app
package com.test; import java.net.URL; public class ClassLoaderTest { public static void main(String[] args) { URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (URL url : urls) { System.out.println(url.toExternalForm()); } } }
输出以下: jvm
file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/resources.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/rt.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/sunrsasign.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/jsse.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/jce.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/charsets.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/lib/jfr.jar file:/home/mazhi/workspace/jdk1.8.0_192/jre/classes
(2)扩展(Extension)类加载器:扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,它负责将 <JAVA_HOME >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库 加载到内存中。this
(3)系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,它负责将 用户类路径(java -classpath或-Djava.class.path变量所指的目录,即当前类所在路径及其引用的第三方类库的路径,如第四节中的问题6所述)下的类库 加载到内存中。url
AppClassLoader与ExtClassLoader的继承体系以下: spa
在sun.misc.Launcher类(rt.jar包)中定义了这两个类加载器,能够详细的了解Java中相关类加载器的扫描路径等信息,同时也能看到 建立了两个加载器的实例,以下:.net
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); } ... }
ExtClassLoader类的定义以下:线程
/* * The class loader used for loading installed extensions. */ static class ExtClassLoader extends URLClassLoader { static { ClassLoader.registerAsParallelCapable(); } /** * create an ExtClassLoader. The ExtClassLoader is created * within a context that limits which files it can read */ public static ExtClassLoader getExtClassLoader() throws IOException { final File[] dirs = getExtDirs(); try { // Prior implementations of this doPrivileged() block supplied // aa synthesized ACC via a call to the private method // ExtClassLoader.getContext(). 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); } }); } catch (java.security.PrivilegedActionException e) { throw (IOException) e.getException(); } } /* * Creates a new ExtClassLoader for the specified directories. */ public ExtClassLoader(File[] dirs) throws IOException { super(getExtURLs(dirs), null, factory); // parent传递的参数为null,因此并非引导类加载器 } private static File[] getExtDirs() { String s = System.getProperty("java.ext.dirs"); File[] dirs; if (s != null) { StringTokenizer st = new StringTokenizer(s, File.pathSeparator); int count = st.countTokens(); dirs = new File[count]; for (int i = 0; i < count; i++) { dirs[i] = new File(st.nextToken()); } } else { dirs = new File[0]; } return dirs; } ... }
经过java.security.ClassLoader类(rt.jar包)能够详细了解双亲委派模式。code
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(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } 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; } }
先由findLoadedClass
->native方法findLoadedClass0()
,检查是不是已经加载的类,若是没加载过且父加载器不为空,则尝试由父加载器来加载。
若是父加载器为空,那么须要调用findBootstrapClassOrNull
->底层的native方法findBootstrapClass()
来检查是不是顶级类加载器BootstrapClassLoader
加载过的类。
若是不是,那么则调用findClass()由子类本身去加载。咱们看一下 URLClassLoader
是怎么实现逻辑的?
在URLClassLoader#findClass()
中,根据类加载器的加载路径(包括jar),找到对应的文件,最后调用defineClass
->native方法defineClass1()
将java层的类注册到jvm中,那么下次再获取的时候就能够从findLoadedClass
方法中找到了。
双亲委派模型有如下几个优势:
除了根类加载器、扩展类加载器和系统类加载器外,还能够定义应用类加载器,其工做流程以下图所示。
从实现方式来看,能够分为两大类。
一、顶级类加载器bootStrapClassLoader
加载jdk核心类库
二、用户代码由native方法defineClass1
方式加载到jvm中的类,其中jvm中实现类加载的类是SystemDictionary
java在启动时会调用jvm库的create_vm
->init_globals
方法时,完成一些重要的初始化工做,其中重要的有
parse_vm_init_args
解析vm参数,vm参数在里面都可找到。
universe_init
初始化gc策略和,全局堆 和tlab等。tlab是在eden区切出一小块,(每一个线程均有一个,具体是ThreadLocalAllocBuffer::initialize
,没看懂是如何计算的,有人说是256k)。
vmSymbols::initialize
建立基础类型(int,boolean...)的klass句柄。
SystemDictionary::initialize
加载jdk中重要的类
jdk中定义了一些重要的类名为_well_known_klasses
,_well_known_klasses
的组成能够由systemDictionary.hpp#WK_KLASSES_DO
枚举到,涵盖了Object,String,Class,...一系列重要的类,SystemDictionary::initialize
负责将_well_known_klasses
中的类都加载到jvm中,流程以下:
遍历顶级加载器的加载路径,用LazyClassPathEntry::open_stream
寻找到要加载的类的class文件流
调用ClassFileParser::parseClassFile
执行具体的从文件流读取数据,根据class文件格式标准解析class文件,生成对应的instanceKlass对象。
调用SystemDictionary::find_or_define_instance_class
->SystemDictionary::update_dictionary
->Dictionary::add_klass
将生成的Klass对象存起来。Dictionary
是个hash表实现,使用的也是开链法解决hash冲突
除去jvm初始化加载的类,其余类的加载是由java程序触发的,调用native方法defineClass1()
。
流程和初始化加载类流程差很少。由java代码传入文件流句柄和类名,调用底层代码SystemDictionary::resolve_from_stream
。其中主要是两件事:
1.ClassFileParser::parseClassFile
读取文件生成Klass
2.调用SystemDictionary#define_instance_class()
将类加载到Klass字典中
参考文章:
(1)https://blog.csdn.net/qq_26000415/article/category/9289818