本文最后更新于 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 应用程序类加载器就是通过线程上下文类加载器实现的。