复习博客:JVM
复习博客:JVM
今日复习内容
今天学习Java虚拟机(JVM),它是Java程序运行的基石。理解JVM的工作原理对于优化Java应用性能和排查问题至关重要。主要复习了以下内容:
JVM内存模型
JVM内存模型(也称为运行时数据区域)主要分为以下几个部分:
-
程序计数器 (Program Counter Register):
- 一块较小的内存空间,是当前线程所执行的字节码的行号指示器。
- 每个线程都有一个独立的程序计数器,互不影响。
- 此区域是JVM内存中唯一一个不会出现
OutOfMemoryError
的区域。
-
Java虚拟机栈 (Java Virtual Machine Stacks):
- 每个线程在创建时都会创建一个虚拟机栈,用于存储栈帧(Stack Frame)。
- 栈帧中存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 生命周期与线程相同。
- 可能出现
StackOverflowError
(栈深度超过允许的深度)和OutOfMemoryError
(栈扩展时无法申请到足够的内存)。
-
本地方法栈 (Native Method Stacks):
- 与虚拟机栈类似,但为JVM执行Native方法服务。
- 也可能出现
StackOverflowError
和OutOfMemoryError
。
-
Java堆 (Java Heap):
- 所有线程共享的一块内存区域,在JVM启动时创建。
- 用于存放对象实例和数组。
- 是垃圾收集器管理的主要区域(GC堆)。
- 是
OutOfMemoryError
最常出现的区域。 - 通常分为新生代(Eden区、From Survivor区、To Survivor区)和老年代。
-
方法区 (Method Area):
- 所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 在JDK 1.8及以后,方法区被元空间(Metaspace)取代,元空间使用本地内存,而不是JVM内存。
- 可能出现
OutOfMemoryError
。
垃圾回收机制
垃圾回收(Garbage Collection, GC)是JVM自动管理内存的机制,主要目标是回收堆中不再使用的对象。
-
判断对象是否可回收:
- 引用计数法:每个对象维护一个引用计数器,当计数器为0时表示可回收。缺点是无法解决循环引用问题。
- 可达性分析算法:通过GC Roots(如虚拟机栈中引用的对象、方法区中静态属性引用的对象、本地方法栈中JNI引用的对象等)作为起始点,从这些节点向下搜索,搜索不到的对象即为可回收对象。
-
常见的垃圾收集算法:
- 标记-清除 (Mark-Sweep):先标记出所有可回收对象,然后统一回收。缺点是会产生大量内存碎片。
- 复制 (Copying):将可用内存分为大小相等的两块,每次只使用其中一块。当一块用完时,将存活对象复制到另一块,然后清空已使用块。适用于新生代,缺点是内存利用率低。
- 标记-整理 (Mark-Compact):先标记出所有可回收对象,然后将所有存活对象向一端移动,最后清理掉边界以外的内存。解决了内存碎片问题,适用于老年代。
- 分代收集 (Generational Collection):根据对象存活周期的不同将内存划分为几块(新生代、老年代),然后根据各代的特点采用不同的GC算法。这是目前主流的GC策略。
-
常见的垃圾收集器:
- Serial:单线程收集器,适用于小型应用。
- ParNew:Serial的多线程版本,新生代收集器。
- Parallel Scavenge:吞吐量优先的收集器,新生代收集器。
- Serial Old:Serial的老年代版本。
- Parallel Old:Parallel Scavenge的老年代版本。
- CMS (Concurrent Mark Sweep):并发、低延迟收集器,适用于老年代,但会产生浮动垃圾和内存碎片。
- G1 (Garbage First):面向服务端应用的垃圾收集器,兼顾吞吐量和停顿时间,将堆划分为多个区域(Region)。
类加载机制
类加载机制是指JVM将Class
文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型的过程。包括以下阶段:
-
加载 (Loading):
- 通过类的完全限定名获取定义该类的二进制字节流。
- 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
- 在内存中生成一个代表该类的
java.lang.Class
对象。
-
验证 (Verification):
- 确保
Class
文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 - 包括文件格式验证、元数据验证、字节码验证、符号引用验证。
- 确保
-
准备 (Preparation):
- 为类的静态变量分配内存并设置类变量的初始值(通常是零值)。
- 不包括实例变量的内存分配和初始化。
-
解析 (Resolution):
- 将常量池内的符号引用替换为直接引用。
- 符号引用:一组符号来描述所引用的目标。
- 直接引用:直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
-
初始化 (Initialization):
- 执行类构造器
<clinit>()
方法的过程。 <clinit>()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static {}
块)中的语句合并产生的。- 在多线程环境下,JVM会保证一个类的
<clinit>()
方法在多线程中被正确地加锁、同步,只有一个线程去执行。
- 执行类构造器
双亲委派模型 (Parent-Delegation Model):
- 类加载器收到类加载请求时,会先将请求委派给父类加载器处理。
- 如果父类加载器无法加载,子类加载器才会尝试自己加载。
- 优点:避免类的重复加载;保证Java核心API的类型安全。
今日心得
JVM是Java程序员进阶的必经之路。今天的复习让我对JVM的内存结构、垃圾回收机制以及类加载过程有了更系统和深入的理解。特别是各种GC算法和收集器的特点,以及双亲委派模型的重要性,这些都是面试中考察深度和广度的关键点。理解这些底层原理,对于编写高性能、高可用的Java应用非常有帮助。