JVM系列-类加载

76 阅读4分钟

大家好,我是小趴菜,今天我们来复习一下JVM相关的知识

我们从开发一个程序到部署运行,代码的整体会经历如下流程

屏幕截图 2024-11-12 204115.png

类加载

我们把一个java程序一般会打包成jar或者是war包放到对应的操作系统上运行,那么要能执行到我们程序主要还是依靠JVM虚拟机,但是JVM在执行我们的程序之前首先肯定是要加载我们的程序文件

所以在整个过程中又会经历如下几个阶段

加载阶段

我们将java程序编译后生成一个class文件,但是本质上它还是一个个的文件,所以会通过IO将class文件读取到内存当中

  • 1:通过类的全限定名获取二进制字节流,就是读取整个文件的操作
  • 2:将整个字节流的数据转化为内存结构
  • 3:在内存中生成一个代表整个类的java.lang.Class对象

以上就是加载阶段要做的事情

链接

链接阶段又会分为三个阶段来执行

  • 1:验证

    因为JVM从外部读取文件,但是并不是任何文件都符合的,所以会有一个验证阶段,验证整个字节码文件是否符合

  • 2:准备

    1:为类的静态变量分配内存并设置默认值,即零值

    2:不包含final的静态变量,其在编译的时候就会分配好默认值

    这里的默认值并不是用户赋予的值,而是类型的默认值,比如int类型的默认值就是0,字符串类型的就是空

  • 解析

    主要将常量池内的符号引用转为直接引用的过程

初始化

主要是为类的静态变量赋予正确的初始值,也就是用户在程序中赋予的值,对类的变量进行初始化

类加载器

类加载器有以下几种类型

  • 1:启动类加载器 null,因为底层是c语言实现,所以为null
  • 2:扩展类加载器 sun.misc.Launcher$ExtClassLoader@7a46a697
  • 3:系统类加载器 sun.misc.Launcher$AppClassLoader@18b4aac2
  • 4:自定义类加载器

屏幕截图-2.png

那么为什么需要这么多的类加载器,其实每个类加载器加载的类的路径是不一样的

启动类加载器加载的类路径如下

屏幕截图 3.png

扩展类加载器加载的类路径如下

屏幕截图4.png

各个启动类加载器的关系

public Launcher() {
    ExtClassLoader var1;
    try {
        //扩展类加载器
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
        //系统类加载器
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
}

从源码中我们各个加载器之间是没有关系的,只不过加载系统类加载器的时候将扩展类加载器作为参数传递进去,但是并没有父子类相关的关系

双亲委派机制

屏幕截图 5.png

现在需要初始化一个类,这时候系统类加载器会从扩展类加载器查询,看下能否加载这个类,而扩展类加载器并不会直接初始化,而是继续向启动类加载器委托,如果启动类加载器没有,那么再回到扩展类加载器加载,如果扩展类加载器没有,就要由系统类加载器进行加载了

其实整个过程就是个递归过程,类加载的源码如下

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    //往上委托
                    c = parent.loadClass(name, false);
                } else {
                    //从启动类加载器获取
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
               
            }

            if (c == null) {
               
                c = findClass(name);
            }
        }
        return c;
    }
}

双亲委派机制有以下好处

  • 1:安全,可以避免外部程序篡改
  • 2:按需加载,比如String这个类,全程只需要加载一次就可以了

如何破坏双亲委派机制

  • 1:自定义类加载器

Tomcat就实现了自定义类加载器,来保证多个服务之间类的加载不会相互影响,一个Tomcat是可以部署多个WEB应用的,如果这个两个应用出现了相同限定名的类,那么就需要都加载,并且两个是不一样的类

  • 2:线程上下文类加载器

利用线程上下文类加载器,就比如JDBC