JVM就是Java虚拟机,Java虚拟机就是JVM
运行时数据区
类加载器
Java的类加载器遵循双亲委派模型,主要分为三个层次:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader)。启动类加载器是最顶层的,由C++实现,负责加载Java核心库如rt.jar。扩展类加载器负责加载扩展目录下的类,而应用程序类加载器则负责加载classpath下的类。用户自定义的类加载器通常继承自应用程序类加载器。
- 启动类(根)加载器(Bootstrap ClassLoader)
- 扩展类加载器(Extension ClassLoader)
- 应用程序(系统类)加载器(Application ClassLoader)
- 虚拟机自带的加载器
public class Student { public static void main(String[] args) { Student student1 = new Student(); Student student2 = new Student(); Student student3 = new Student(); System.out.println(student1.hashCode()); System.out.println(student2.hashCode()); System.out.println(student3.hashCode()); System.out.println(student1.getClass()); System.out.println(student2.getClass()); System.out.println(student3.getClass()); } }
类是模板,对象是具体的
三个实例的
hashCode()
值通常不同,因为它们是独立的对象,内存地址不同。
Student
类被同一个类加载器加载,三个实例的getClass()
返回的Class
对象是同一个。如果通过不同的类加载器加载Student
类(例如自定义类加载器),即使类名相同,JVM 也会认为它们是不同的类,导致getClass()
返回不同的Class
对象。
public class Student { public static void main(String[] args) { Student student1 = new Student(); Student student2 = new Student(); Student student3 = new Student(); System.out.println(student1.hashCode()); System.out.println(student2.hashCode()); System.out.println(student3.hashCode()); System.out.println(student1.getClass()); System.out.println(student2.getClass()); System.out.println(student3.getClass()); Class<? extends Student> class1 = student1.getClass(); ClassLoader classLoader = class1.getClassLoader(); System.out.println(classLoader);//Application ClassLoader System.out.println(classLoader.getParent());//Extension ClassLoader System.out.println(classLoader.getParent().getParent());//null,1.找不到2.java程序获取不到 } }
我们可以看到
Student
类由应用程序类加载器(Application ClassLoader)加载- 应用程序类加载器的父加载器是扩展类加载器(Extension ClassLoader)
- 扩展类加载器的父加载器是启动类加载器(Bootstrap ClassLoader),但由于它是 JVM 内部用 C++ 实现的,Java 中无法直接获取其引用,因此返回
null
。而在 Java 9 及更高版本中,由于模块化系统(JPMS)的引入,类加载器的结构和职责被重新设计:
- Bootstrap ClassLoader:仍为顶层加载器,但不再直接暴露给开发者。
- PlatformClassLoader(平台类加载器):取代了
ExtClassLoader
,负责加载平台模块(如java.base
、java.xml
等核心模块)。- AppClassLoader(应用程序类加载器):加载用户自定义的模块和
classpath
下的类。
双亲委托机制
主要目的:解决类加载冲突,即安全。
双亲委托流程
用户代码请求加载类:例如,
AppClassLoader
收到加载com.example.MyClass
的请求。向上委派:
AppClassLoader
先委派给父加载器PlatformClassLoader
。
PlatformClassLoader
进一步委派给Bootstrap ClassLoader
。逐级尝试加载:
Bootstrap ClassLoader
尝试加载核心类(如java.lang.String
),若找到则返回。若未找到,
PlatformClassLoader
尝试加载平台模块中的类。若仍失败,
AppClassLoader
最终在用户模块或classpath
下加载类。
Native
Native 方法是 Java 中通过 JNI(Java Native Interface) 调用本地代码(如 C/C++ 编写的函数)的桥梁。即java的作用范围达不到,才调用底层C语言的库
JNI:扩展java的使用,融合不同的编程语言为java所用,在内存区域中开辟了一块标记区域(本地方法栈)登记native方法
程序计数器
程序计数器(PC寄存器)是 JVM运行时数据区 中一块线程私有的小内存空间,用于存储当前线程正在执行的字节码指令地址(或Native方法的执行状态)。
方法区
方法区是 JVM运行时数据区 的一部分,用于存储已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 存储类的元数据(如类名、字段描述、方法字节码)。
- 维护运行时常量池(包含字面量、符号引用)。
- 存放静态变量(
static
修饰的变量)和JIT编译后的本地代码。
栈
栈就是管理程序的运行、生命周期和线程同步,每个线程都独立拥有栈,每个栈由栈帧组成 ,每个栈帧由局部变量表、操作数栈、动态链接和返回地址组成,栈满了会报错StackOberflowError,栈中没有垃圾回收算法
堆
堆”(Heap)是 Java 虚拟机中最核心的运行时内存区域之一,主要用于存储对象实例,几乎所有的对象和数组都在这里分配内存。所有线程共享一个堆,堆的大小可变,是GC的主要工作区域。99%的对象都是临时对象在新生代中发生
堆又分为
- 新生代:存放新创建的对象
- Eden区:对象初次分配内存的位置
- Survivor区:经过多次Minor GC后存活的对象,分为from区和to区,这两个区会轮流交换,谁空谁是to区,当
- 老年代:存放长期存活的对象,当一个对象经历了默认的15次Minor GC后仍存活,就会放入养老区
-
元空间(Metaspace)永久存储区:取代永久代(PermGen),存储类JCK自身携带的class对象。
垃圾回收
- Minor GC(轻量化垃圾回收):
- 对象优先在Eden区分配。
- Eden区满时触发Minor GC,存活对象被复制到Survivor区(From/To交替使用)。
- 对象每熬过一次Minor GC,年龄+1,达到阈值(默认15)后晋升到老年代。
- FUll GC(重量化垃圾回收)
- 老年代空间不足时会产生OOM错误,并且触发Full GC
标记清除法:对于可以活着的对象进行标记,未标记的对象进行清除,虚线代表清除的对象,由内存碎片
- 优点:实现简单,无额外内存开销。适合老年代
- 缺点:内存碎片:回收后产生不连续内存,两次频繁扫描,浪费时间
标记整理法:与标记-清除相同,标记存活对象。将存活对象向内存一端移动,清理边界外内存。
- 优点:无内存碎片:内存连续分配。适合老年代。
- 缺点:停顿时间长:整理阶段需移动对象。
复制算法:将内存分为两块(如Eden和Survivor),每次只使用其中一块。GC时将存活对象复制到另一块内存,清空原内存。适合新生代回收
- 好处:无内存碎片
- 坏:浪费空间
引用计数器