JVM的内存模型和垃圾回收机制

发布时间:2021-09-17 15:56:16 作者:chen
来源:亿速云 阅读:101
# JVM的内存模型和垃圾回收机制

## 一、JVM内存模型概述

Java虚拟机(JVM)在执行Java程序时会将其管理的内存划分为若干个不同的数据区域,这些区域各有用途,并随着程序的启动和终止而创建和销毁。JVM内存模型主要分为以下几个部分:

### 1. 程序计数器(Program Counter Register)

程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。每个线程都有自己独立的程序计数器,用于记录线程执行的当前位置。这是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

### 2. Java虚拟机栈(Java Virtual Machine Stacks)

与程序计数器一样,Java虚拟机栈也是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

- **局部变量表**:存放编译期可知的各种基本数据类型、对象引用和returnAddress类型
- **可能抛出的异常**:StackOverflowError(线程请求的栈深度大于虚拟机所允许的深度)、OutOfMemoryError(扩展时无法申请到足够内存)

### 3. 本地方法栈(Native Method Stack)

与虚拟机栈作用相似,区别在于本地方法栈为虚拟机使用到的Native方法服务。HotSpot虚拟机直接就把本地方法栈和虚拟机栈合二为一。

### 4. Java堆(Java Heap)

Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

特点:
- 是垃圾收集器管理的主要区域("GC堆")
- 可以处于物理上不连续的内存空间中
- 可通过`-Xmx`和`-Xms`参数设定大小
- 抛出OutOfMemoryError异常当堆无法扩展时

### 5. 方法区(Method Area)

方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

JDK 8的显著变化:
- 永久代(PermGen)被元空间(Metaspace)取代
- 元空间使用本地内存而非JVM内存
- 默认情况下大小仅受本地内存限制

### 6. 运行时常量池(Runtime Constant Pool)

运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。具备动态性,运行期间也可以将新的常量放入池中(如String的intern()方法)。

## 二、对象访问方式

Java程序需要通过栈上的reference数据来操作堆上的具体对象。主流的访问方式有两种:

1. **句柄访问**:Java堆中划分出一块内存作为句柄池,reference中存储对象的句柄地址
   - 优点:对象移动时(如GC时)只需改变句柄中的实例数据指针
   
2. **直接指针访问**:reference中直接存储对象地址
   - 优点:速度快(HotSpot采用此方式)

## 三、垃圾回收机制

### 1. 判断对象是否存活

#### 引用计数算法
给对象添加一个引用计数器,当有地方引用它时计数器加1,引用失效时减1。简单但无法解决循环引用问题。

#### 可达性分析算法
通过一系列称为"GC Roots"的对象作为起始点,从这些节点向下搜索,所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象不可用。

可作为GC Roots的对象包括:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象

### 2. 引用类型

JDK 1.2后,Java将引用分为四种:

1. **强引用(Strong Reference)**:普遍存在的引用,只要强引用存在,垃圾收集器永远不会回收被引用的对象
2. **软引用(Soft Reference)**:描述还有用但非必需的对象,在系统将要发生内存溢出异常前会被回收
3. **弱引用(Weak Reference)**:只能生存到下一次垃圾收集发生之前
4. **虚引用(Phantom Reference)**:最弱的引用,无法通过虚引用获取对象实例,唯一目的是对象被回收时收到系统通知

### 3. 垃圾收集算法

#### 标记-清除算法(Mark-Sweep)
分为"标记"和"清除"两个阶段:
- 标记所有需要回收的对象
- 统一回收被标记对象

缺点:
- 效率问题(标记和清除两个过程效率都不高)
- 空间问题(会产生大量不连续的内存碎片)

#### 复制算法(Copying)
将可用内存按容量分为大小相等的两块,每次只使用其中一块。当这一块用完时,将存活的对象复制到另一块上,然后清理已使用的内存空间。

优点:
- 实现简单,运行高效
- 解决了内存碎片问题

缺点:
- 内存缩小为原来的一半
- 在对象存活率较高时需要进行较多的复制操作

#### 标记-整理算法(Mark-Compact)
标记过程与"标记-清除"算法一样,但后续步骤不是直接清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。

#### 分代收集算法(Generational Collection)
当前商业虚拟机的垃圾收集都采用此算法,根据对象存活周期的不同将内存划分为几块:
- 新生代(Young Generation):使用复制算法
- 老年代(Tenured Generation):使用标记-清除或标记-整理算法

### 4. HotSpot的算法实现

#### 枚举根节点
在可达性分析中,GC停顿所有Java执行线程("Stop The World")来枚举根节点。HotSpot使用一组称为OopMap的数据结构来直接得知哪些地方存放着对象引用。

#### 安全点(Safepoint)
程序执行时并非在所有地方都能停顿开始GC,只有在到达安全点时才能暂停。安全点的选定基本上以"是否具有让程序长时间执行的特征"为标准进行选定。

#### 安全区域(Safe Region)
安全区域是指在一段代码片段中,引用关系不会发生变化。线程执行到安全区域时,会标识自己已进入安全区域,这样GC时就不用管这些线程了。

### 5. 垃圾收集器

#### Serial收集器
单线程收集器,进行垃圾收集时必须暂停其他所有工作线程(Stop The World)。简单高效,对于限定单个CPU的环境来说没有线程交互开销。

#### ParNew收集器
Serial收集器的多线程版本,是许多运行在Server模式下的虚拟机中首选的新生代收集器。

#### Parallel Scavenge收集器
新生代收集器,使用复制算法,目标是达到可控制的吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))。

#### Serial Old收集器
Serial收集器的老年代版本,使用标记-整理算法。

#### Parallel Old收集器
Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。

#### CMS收集器(Concurrent Mark Sweep)
以获取最短回收停顿时间为目标的收集器,基于标记-清除算法实现,运作过程分为四个步骤:
1. 初始标记(CMS initial mark)
2. 并发标记(CMS concurrent mark)
3. 重新标记(CMS remark)
4. 并发清除(CMS concurrent sweep)

优点:并发收集、低停顿
缺点:对CPU资源敏感、无法处理浮动垃圾、产生空间碎片

#### G1收集器(Garbage-First)
面向服务端应用的垃圾收集器,特点:
- 并行与并发
- 分代收集
- 空间整合(整体基于标记-整理,局部基于复制算法)
- 可预测的停顿

将整个Java堆划分为多个大小相等的独立区域(Region),跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表。

## 四、内存分配与回收策略

### 对象优先在Eden分配
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间时,虚拟机将发起一次Minor GC。

### 大对象直接进入老年代
需要大量连续内存空间的Java对象(如长字符串及数组)会直接进入老年代,避免在Eden区及两个Survivor区之间发生大量的内存复制。

### 长期存活的对象将进入老年代
虚拟机给每个对象定义了一个对象年龄计数器。对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间,年龄设为1。对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当年龄增加到一定程度(默认15岁)时,就会被晋升到老年代中。

### 动态对象年龄判定
如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。

### 空间分配担保
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间。如果成立,则Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。如果大于,将尝试进行一次Minor GC;如果小于,或者HandlePromotionFailure设置不允许冒险,则要进行一次Full GC。

## 五、总结

JVM的内存模型和垃圾回收机制是Java高效运行的基础保障。理解这些底层原理对于编写高性能Java应用、进行JVM调优和故障排查都具有重要意义。随着Java技术的不断发展,JVM的内存管理和垃圾回收技术也在持续演进,如JDK 11引入的ZGC和JDK 12的Shenandoah等新垃圾收集器,都致力于在更大堆内存情况下实现更低的停顿时间。

这篇文章约2250字,采用Markdown格式编写,包含了JVM内存模型的详细说明、垃圾回收机制的核心原理和实现细节,以及内存分配策略等内容。文章结构清晰,层次分明,适合作为技术参考文档使用。

推荐阅读:
  1. jvm垃圾回收机制
  2. jvm内存模型及分配参数

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

jvm

上一篇:FastDFS的架构及特点介绍

下一篇:Redis高效实现点赞、取消点赞的步骤

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》