JVM 讲解 (主要类加载其以及流程和机制(双亲委派))

发布于:2024-04-20 ⋅ 阅读:(27) ⋅ 点赞:(0)

JVM有什么用?

说白了,就是我们编写 Java 代码,编译 Java 代码,目的不是让它在 Linux、Windows 或者 MacOS 上跑,而是在 JVM 上跑。(保证只要有JVM这个东西,就可以跨平台使用Java) 可以把JVM想象成一个小型平台。

 JVM的组织架构

JVM 大致可以划分为三个部门,分别是类加载器(Class Loader)、运行时数据区(Runtime Data Areas)和执行引擎(Excution Engine)

 类加载器

类加载器的作用是将类文件加载到内存中,主要分为加载,连接,实例化三个阶段。如果加载到内存中失败,那么运行时数据区或者执行引擎什么都干不了了。

类加载的过程

第一步 加载

通过类的全限定名(包名 + 类名),获取到该类的.class文件的二进制字节流

将二进制字节流所代表的静态存储结构,转化为方法区运行时的数据结构

内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

 总结加载二进制数据到内存 —> 映射成jvm能识别的结构 —> 在内存中生成class文件

第二步 连接

连接是让内存中生成好的类对象,合并到JVM中,然后让这个类可以在JVM中可以被正常执行。可分为验证准备解析三个阶段。

  • 验证:确保class文件中的字节流信息符合JVM的要求,保证这个被加载的class类的正确性,不会危害到虚拟机的安全。

  • 准备:为类中的静态字段 设置初始值,例如int 类型的初始值就是 0。但是要注意的是final 修饰的遍历不用设置初始值,因为final在编译的时候就分配了。

  • 解析:解析阶段的目的,是将常量池内的符号引用转换为直接引用的过程(将常量池内的符号引用解析成为实际引用),意思就是将符号的引用提前给他加载了,不用再去符号引用,直接把引用整到本地。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化。)

  • 解析动作主要针对类、接口、字段、类方法、接口方法、方法类型等。对应常量池中的 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。

第三步 initialization初始化

初始化就是执行类的构造器方法init()的过程。 

若该类具有父类,jvm会保证父类的init先执行,然后在执行子类的init

 类加载器的分类

  • 第一个:启动类/引导类:Bootstrap ClassLoader

这个类加载器使用C/C++语言实现的,嵌套在JVM内部,java程序无法直接操作这个类。

它用来加载Java核心类库,如:JAVA_HOME/jre/lib/rt.jarresources.jarsun.boot.class.path路径下的包,用于提供jvm运行所需的包。

并不是继承自java.lang.ClassLoader,它没有父类加载器

它加载扩展类加载器应用程序类加载器,并成为他们的父类加载器。

出于安全考虑,启动类只加载包名为:java、javax、sun开头的类

  • 第二个:扩展类加载器:Extension ClassLoader

Java语言编写,由sun.misc.Launcher$ExtClassLoader实现,我们可以用Java程序操作这个加载器。

派生继承自java.lang.ClassLoader,父类加载器为启动类加载器

从系统属性:java.ext.dirs目录中加载类库,或者从JDK安装目录:jre/lib/ext目录下加载类库。我们就可以将我们自己的包放在以上目录下,就会自动加载进来了。

  • 第三个:应用程序类加载器:Application Classloader

Java语言编写,由sun.misc.Launcher$AppClassLoader实现。

派生继承自java.lang.ClassLoader,父类加载器为启动类加载器。

它负责加载环境变量classpath或者系统属性java.class.path指定路径下的类库

它是程序中默认的类加载器,我们Java程序中的类,都是由它加载完成的。

我们可以通过ClassLoader#getSystemClassLoader()获取并操作这个加载器

若以上的类加载器还不能满足需求,我们可以自定义类加载器。

简单来说:

Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。

ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。

AppClassLoader:主要负责加载应用程序的主函数类

自定义加载器实现步骤

继承java.lang.ClassLoader类,重写findClass()方法

如果没有太复杂的需求,可以直接继承URLClassLoader类,重写loadClass方法,具体可参考AppClassLoaderExtClassLoader

ClassLoader

这是一个抽象的类,除了启动类加载器,剩下都继承于它。

获取ClassLoader 的几种方式:

// 方式一:获取当前类的 ClassLoader
clazz.getClassLoader()
// 方式二:获取当前线程上下文的 ClassLoader
Thread.currentThread().getContextClassLoader()
// 方式三:获取系统的 ClassLoader
ClassLoader.getSystemClassLoader()
// 方式四:获取调用者的 ClassLoader
DriverManager.getCallerClassLoader()
类加载机制--双亲委派

这段代码是ClassLoader中的loadClass方法。

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    //              -----??-----
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 首先,检查是否已经被类加载器加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    // 存在父加载器,递归的交由父加载器
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 直到最上面的Bootstrap类加载器
                        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.
                    c = findClass(name);
                }
            }
            return c;
    }

从上面的代码中可以看到:

当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。

如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。

直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。
                        

 双亲委派的作用

这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader,会一层一层向上传递加载的动作,不会自己加载,知道Bootstrap),已经加载完了,所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。

 

 作用以及图片 来源于 https://blog.csdn.net/codeyanbao/article/details/82875064 这位大佬