Java类的加载

 

类的加载分为如下几个阶段:

 

我们首先看一下JVM预定义的三种类加载器,当JVM启动的时候,Java缺省开始使用如下三种类型的类加载器:

(1)启动(Bootstrap)类加载器:引导类加载器是用C++代码实现的类加载器,它负责将 <JAVA_HOME>/lib下面的核心类库 或 -Xbootclasspath选项指定的jar包等 虚拟机识别的类库 加载到内存中。 

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

输出如下:   

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指定位置中的类库 加载到内存中。

(3)系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,它负责将 用户类路径(java -classpath或-Djava.class.path变量所指的目录,即当前类所在路径及其引用的第三方类库的路径,如第四节中的问题6所述)下的类库 加载到内存中。

AppClassLoader与ExtClassLoader的继承体系如下: 

 在sun.misc.Launcher类(rt.jar包)中定义了这两个类加载器,可以详细的了解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);
        }

        ...
}

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包)可以详细了解双亲委派模式。

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方法中找到了。

双亲委派模型有以下几个优点:

  • 安全,父加载器优先于子加载器,核心的类库只能由jvm自己去加载。
  • 共享,子加载器可以直接使用父加载器加载过的类,使得系统不会多次加载一个类。

除了根类加载器、扩展类加载器和系统类加载器外,还可以定义应用类加载器,其工作流程如下图所示。

 

 从实现方式来看,可以分为两大类。

1、顶级类加载器bootStrapClassLoader加载jdk核心类库

2、用户代码由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 

 

 

 

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/mazhimazhi/p/11414861.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值