1. JVM内存结构概述

1.类加载器子系统 - 图3

2. 类加载子系统

2.1 类加载过程

Class文件需要加载到虚拟机中之后才能运行,那JVM是如何加载这些Class文件的呢 ?

1.类加载器子系统 - 图4

2.1.1 加载

2.1.1.1 步骤

    1. 通过一个类的全限定名获取定义此类的二进制字节流。
    1. 将这个字节流所代表的 静态存储结构 转化为方法区的运行时数据结构。

      (JDK<=1.7,方法区 的实现方式为:永久代。JDK>=1.8, 方法区的实现方式为:元空间)

    1. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法去这个类的各种数据的访问入口。(此步骤生成大的Class实例,在内存中)

2.1.1.2 加载.class文件的方式

image.png

2.1.2 链接

1.类加载器子系统 - 图6

2.1.2.1 验证

验证class文件是否符合jvm规范。

2.1.2.1.1 文件格式验证

2.1.2.1.2 元数据验证

2.1.2.1.3 字节码验证

2.1.2.1.4 符号引用验证

2.1.2.2 准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。这些内存都将在方法区中分配。

  • 这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中。
  • 这里所设置的初始值”通常情况”下是数据类型默认的零值(如0、0L、null、false等),比如我们定义了public static int value=111 ,那么 value 变量在准备阶段的初始值就是 0 而不是111(初始化阶段才会赋值)。

    特殊情况:比如给 value 变量加上了 fianl 关键字public static final int value=111 ,那么准备阶段 value 的值就被赋值为 111。(因为final修饰的变量在编译器即被赋值)

2.1.2.3 解析

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

2.1.3 初始化

初始化是类加载的最后一步,也是真正执行类中定义的 Java 程序代码(字节码),初始化阶段是执行初始化方法 <clinit> ()方法的过程 ;
此方法不需要定义,是javac编译器自动收集 类中的所有类变量的赋值动作和静态代码块中的语句合并而来。

  • 也就是说,当我们的代码中包含 static 变量的时候,就会有()方法。

1.类加载器子系统 - 图7

重点 : 只有当类中包含 类变量 和静态代码块 时 才会有这个 方法生成,否则不会有
静态代码块在此步骤执行。类变量在此环节被显示赋值。

2.1.3.1 触发初始化的情景

对于初始化阶段,虚拟机严格规范了有且只有5种情况下,必须对类进行初始化(只有主动去使用类才会初始化类):

  1. 当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
    • 当jvm执行new指令时会初始化类。即当程序创建一个类的实例对象。
    • 当jvm执行getstatic指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。
    • 当jvm执行putstatic指令时会初始化类。即程序给类的静态变量赋值。
    • 当jvm执行invokestatic指令时会初始化类。即程序调用类的静态方法。
  2. 使用 java.lang.reflect 包的方法对类进行反射调用时如Class.forname(“…”),newInstance()等等。 ,如果类没初始化,需要触发其初始化。
  3. 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
  4. 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
  5. MethodHandle和VarHandle可以看作是轻量级的反射调用机制,而要想使用这2个调用, 就必须先使用findStaticVarHandle来初始化要调用的类。

    2.2 类加载器分类

    1.类加载器子系统 - 图8

    2.2.1 启动类加载器

  • 这个类加载使用C/C++语言实现的,嵌套在JVM内部
  • 它用来加载Java的核心类库 (JAVA_HOME/jre/lib/rt.jarresource.jar或sum.boot.class.path路径下的内容),用于提供JVM自身需要的类(JDK最核心的源代码都在rt.jar (runtime) 中
  • 并不继承自java.lang.ClassLoader,没有父加载器
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器

(这里的父类,不是继承的关系。加载的路径明显表示 是相互独立的);

  • 由于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类

    2.2.1.1 加载路径

    ``` @Test public void test() {
    1. URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
    2. for (URL urL : urLs) {
    3. System.out.println(urL.toExternalForm());
    4. }
    }

file:/D:/dev/JDK/jre/lib/resources.jar file:/D:/dev/JDK/jre/lib/rt.jar file:/D:/dev/JDK/jre/lib/sunrsasign.jar file:/D:/dev/JDK/jre/lib/jsse.jar file:/D:/dev/JDK/jre/lib/jce.jar file:/D:/dev/JDK/jre/lib/charsets.jar file:/D:/dev/JDK/jre/lib/jfr.jar file:/D:/dev/JDK/jre/classes

  1. <a name="HF4Id"></a>
  2. ### 2.2.2 扩展类加载器
  3. - [x] Java语言编写,由sum.music.Launcher$ExtClassLoader实现
  4. - [x] 派生于ClassLoader类
  5. - [x] 父类加载器为启动类加载器
  6. - [x] 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的**jre/lib/ext子目录(扩展目录)下加载类库**。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载.
  7. <a name="tlyJV"></a>
  8. #### 2.2.2.1 加载路径

@Test public void test() { String property = System.getProperty(“java.ext.dirs”); for(String path:property.split(“;”)){ System.out.println(path); } }

D:\dev\JDK\jre\lib\ext C:\Windows\Sun\Java\lib\ext
```

2.2.3 应用程序类加载器

  • java语言编写,由sum.misc.Launcher$AppClassLoader实现
  • 派生于ClassLoader类
  • 父类加载器为扩展类加载器
  • 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
  • **该类加载是程序中默认的类加载器**,一 般来说,Java应用的类都是由它来完成加载
  • 通过ClassLoader.getSystemClassLoader()方法可以获取到该类加载器

    2.3 双亲委派机制

    2.3.1 工作原理

1.类加载器子系统 - 图9

2.3.2 双亲委派机制的优势

  • 避免类的重复加载
  • 保护程序安全,防止核心API被恶意篡改。(沙箱安全机制)

    image.png

    在自定义的java.lang下新建类时,也是不被允许的。

    2.3.3 破坏双亲委派机制

    2.3.3.1 为何要破坏双亲委派机制

    首先要明确的是 双亲委派机制存在缺陷,

    2.3.3.2 如何破坏双亲委派机制

    https://blog.csdn.net/awake_lqh/article/details/106171219

    2.4 沙箱安全机制

    举例说明:
    image.png
    我们自定义一个String类,包名设置为:java.lang.String
    运行main方法发现直接报错。
    原因:自定义String类加载时,根据双亲委派机制,最后时候启动类加载器加载rt.jar下的String类,并非我们自定义的类,所以直接报错提示无main方法。

2.5 如何判断两个Class对象是否相同

在JVM中表示2个Class对象是否为同一个类存在2个必要条件:
类的完整类名必须一致,包括包名。
加载这个类的ClassLoader(指ClassLoader实例对象)必须相同。

image.png

2.6 对类加载器的引用(具体需要在动态链接中理解)

image.png

2.7 类的主动引用和被动引用

1.类加载器子系统 - 图14

引用

https://blog.csdn.net/sj15814963053/article/details/109851454 类加载器子系统写的很全面
推荐IDEA插件:jclasslib ByteCode Viewer 可以查看类的字节码