java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java类加载器

Java中类加载器举例详解

作者:小白qzx

Java类加载器是Java语言的核心组件之一,负责从文件系统或网络中加载Class文件,Class文件在文件开头有特定的文件标识,这篇文章主要介绍了Java中类加载器的相关资料,需要的朋友可以参考下

类加载器简介

Java程序被编译器编译之后成为字节码文件(.class文件),当程序需要某个类时,虚拟机便会将对应的class文件进行加载,创建出对应的Class对象。而这个将class文件加载到虚拟机内存的过程,便是类加载。

类加载器负责在运行时将Java类动态加载到JVM(Java虚拟机),是JRE(Java运行时环境)的一部分。由于类加载器的存在,JVM无需了解底层文件或文件系统即可运行Java程序。并且Java类不会一次全部加载到内存中,而是在需要时才会加载到内存中。

类加载的过程

类的生命周期通常包括:加载、链接、初始化、使用和卸载。上图中包含了类加载的三个阶段:加载阶段、链接阶段和初始化阶段。如果将这三个阶段再拆分细化包括:加载、验证、准备、解析和初始化。

这几个阶段的作用简单概况一下:

在上述类加载的过程中,虚拟机提供了三种类加载器:启动(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器),此外还可以自定义类加载器。

启动(Bootstrap)类加载器:

系统中最顶层、最核心的加载器,是 JVM 与生俱来的一部分,负责加载 JDK 核心类库,奠定 Java 程序运行的基础,由 JVM 的底层代码(C/C++,如 HotSpot 虚拟机)实现,无法在 Java 代码中直接获取其对象(调用 getClassLoader() 返回 null)。

加载范围:加载 JDK 核心类库

扩展(Extension)类加载器:

扩展类加载器是启动类加载器的子类,Java语言编写,由sun.misc.Launcher$ExtClassLoader实现,父类加载器为启动类加载器,负责加载标准核心Java类的扩展。

扩展类加载器从JDK扩展目录(通常是$JAVA_HOME/lib/ext目录)或java.ext.dirs系统属性中指定的任何其他目录进行自动加载。

系统/应用(System)类加载器:

系统类加载器负责将所有应用程序级类加载到JVM中。它加载在类路径环境变量,-classpath或-cp命令行选项中找到的文件。它是扩展类加载器的子类。

自定义类加载器:

在大多数情况下,内置的三种类加载器就足够了。但是,在需要从本地硬盘驱动器或网络中加载类的情况下,需要打破双亲委派机制,则可能需要使用自定义类加载器。

下面介绍类加载器的加载机制--双亲委派机制。

双亲委派机制:

从图中可以清晰的看到双亲委派机制的具体过程:加载某个类时会先层层委托给父加载器寻找目标类加载,如果父加载器在自己的加载类路径下都找不到目标类,尝试向下加载。比如我们自己随便写的User类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托启动类加载器,顶层启动类加载器在自己的类加载路径里找了半天没找到User类,则向下退回加载User类的请求,扩展类加载器收到回复就自己加载,在自己的类加载路径里找了半天也没找到User类,又向下退回User类的加载请求给应用程序类加载器,应用程序类加载器于是在自己的类加载路径里找User类,结果找到了就自己加载了。双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载。

我们来看一下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) {
                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
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

此方法负责加载给定名称参数的类。name参数为类的全限定名。先查找是否已加载该类,若无,则判断有无父类加载器parent,尝试给父类加载器去loadclass,其实就是一个递归的顺序。

为什么要双亲委派机制:

沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止
核心API库被随意篡改( 可以自己写一个String 类 会发现报错 )

避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加
载一次,保证被加载类的唯一性

打破双亲委派机制示例:

1、tomcat:tomcat就是属于一个web 程序,可能部署俩个应用程序,不同的应用程序可能会依赖一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离,就需要打破双亲委派机制。

2、热部署:热部署要求 “修改类 / JSP 后,无需重启应用即可生效”,而双亲委派下,类加载器加载类后,该类会被 JVM 缓存,类加载器不销毁,类就无法重新加载。因此需要自定义类加载器 + 销毁重建。

总结

到此这篇关于Java中类加载器的文章就介绍到这了,更多相关Java类加载器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文