Java Virtual Machine

本文最后更新于 2 分钟前,文中所描述的信息可能已发生改变。
详情
  • 类加载器(ClassLoader):负责将字节码文件加载到运行时数据区。只负责加载字节码文件,不负责验证字节码文件。
  • 运行时数据区(Runtime Data Area):包括方法区、堆、栈、本地方法栈、程序计数器等。
  • 执行引擎(Execution Engine):负责执行字节码文件。将字节码文件解释为机器码执行。
  • 本地方法接口(Native Interface):调用本地接口。

内存结构

JVM 内存结构分为两大部分:方法区和堆,方法区又分为类加载器、运行时常量池、方法区和本地方法栈。

  • 方法区(Method Area):存储类的结构信息、常量、静态变量等。
  • 堆(Heap):存放对象实例,是垃圾回收的主要区域。
  • 本地方法栈(Native Method Stack):为 JVM 使用到的 native 方法(如调用 C/C++)服务。
  • 虚拟机栈(Java Virtual Machine Stack):每个线程都有一个,存放局部变量、操作数栈、动态链接和方法出口等信息。
  • 程序计数器(Program Counter Register):每条线程都有一个,记录当前线程正在执行的字节码指令地址。
  • 运行时常量池(Runtime Constant Pool):存放编译期生成的各种字面量和符号引用。

内存模型 (JMM)

详见 helltractor blog 中 Java Memory Model

垃圾回收 (GC)

详见 helltractor blog 中 Garbage Collect

详情

JVM 使用可达性分析算法来判断一个对象是否存活。它从一组称为 GC Roots 的对象出发,沿着引用链向下搜索,如果一个对象无法通过任何 GC Root 访问到,就被认为是不可达,即“垃圾”。

常见的 GC Roots 包括:

  • 虚拟机栈中的引用对象(局部变量表);
  • 方法区中类的静态属性;
  • 方法区中常量引用;
  • 本地方法栈中的 JNI 引用;

除了强引用(Strong Reference)外,Java 还提供了三种“引用类型”来配合垃圾回收机制:

  • 软引用:内存不足时会被回收,适合缓存;
  • 弱引用:下一次 GC 一定会被回收;
  • 虚引用:不会影响生命周期,用于对象被回收时收到通知(配合 ReferenceQueue);
详情
  • Serial GC:单线程的垃圾回收器,适用于单核 CPU 或堆内存较小的场景。它的优点是简单、高效,但因为只有一个线程执行回收,停顿时间较长,可能影响响应时间;
  • Parallel GC:多线程的垃圾回收器,专门设计用于多核 CPU 环境。它的目标是通过并行处理来提高吞吐量,适用于对吞吐量要求较高的场景,如批处理系统。缺点是可能会产生较长的停顿时间;
  • CMS(Concurrent Mark-Sweep)GC:旨在最小化停顿时间,采用并发标记和清理阶段,通过“停止世界”阶段最小化影响,适合高并发应用,如 Web 服务。但其缺点是容易导致内存碎片,且回收过程中可能出现长时间的“停止世界”现象;
  • G1 GC:作为默认垃圾回收器,G1 结合了 CMS 和 Parallel GC 的优点,支持混合回收(Mix GC),通过将堆划分为多个小的 Region,进行增量式回收,能更灵活地控制停顿时间。G1 特别适用于大内存应用,解决了 CMS 的内存碎片问题,并且可以在较低的停顿时间内提供较高的吞吐量。

类加载机制

详情
  • 可以自定义包名不为 java.lang 的 String 类,并区别包名正常使用
  • 自定义包名为 java.lang 的 String 类
    • String 类下写 main 方法:由于双亲委派模型,在加载 String 类时,会最终委派给 Bootstrap ClassLoader 去加载,加载的是 rt.jar 包中的那个 java.lang.String,而 rt.jar 包中的 String 类是没有 main 方法的,因此报错误
    • 启动类也在 java.lang 包下:这里与是否用到 String 类无关,会报 Prohibited package name: java.lang 错误。由于双亲委派,java.lang 包肯定早于自定义的 java.lang 包的加载,就会冲突.
    • 调用方法不在 java.lang 包中:此时由于双亲委派模型的存在,并不会加载到自定义的 String 类

  • 来自一线程序员 Seven 的探索与实践,持续学习迭代中~
  • 本文已收录于我的个人博客:https://www.seven97.top

类加载器

详情

类加载器(ClassLoader)是 Java 虚拟机(JVM)的一部分,用于将字节码文件加载到 JVM 运行时数据区中。 现有的类加载器基本都是 java.lang.ClassLoader 的子类,该类用于将指定的类找到或生成对应的字节码文件。 同时,类加载器负责加载程序所需的资源文件。

详情
  • 启动类加载器(Bootstrap ClassLoader):不继承 ClassLoader。用于加载 JVM 运行时必备的核心类,如 JAVA_HOME/jre/lib 目录下的类。
  • 扩展类加载器(Extension ClassLoader):加载 JVM 扩展目录中的类,如 JAVA_HOME/jre/lib/ext 目录中的类。
  • 应用程序类加载器(Application ClassLoader):加载应用程序 classpath 目录中的类。
  • 自定义类加载器:继承 ClassLoader 类,实现自定义类加载规则。

双亲委派模型 (Parent Delegation Model)

详情

每个类加载器在尝试加载类或资源时,会首先检查请求加载的类型是否已经被加载过,若没有将这个任务委托给它的父加载器去完成。 只有当父加载器无法找到并加载请求的类时,子加载器才会尝试自己去加载该类。

详情
  • 避免类的重复加载:当父加载器已经加载了一个类时,子加载器不会再加载一次。
  • 避免恶意代码:防止恶意代码替换 JDK 中的核心类。
详情

在某些场景中,父类加载器加载不到子模块中的类,这时需要打破双亲委派模型。

  • 重写 loadClass()方法:重写 loadClass()方法,让当前 ClassLoader 先尝试加载类。
  • 重写 findClass()方法:findClass() 只会被 loadClass() 调用,不会委派给父类。重写 findClass()方法,自定义加载规则。
  • 使用线程上下文类加载器:通过 Thread.currentThread().setContextClassLoader()设置线程上下文类加载器。临时替换当前线程的类加载器,使得类加载器不再遵循双亲委派模型。如:Tomcat 的 Web 应用程序类加载器就是通过线程上下文类加载器实现的。
Common Resources for Materials Calculation