首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Java进阶】大白话详解Java内存模型,纯手撸

【Java进阶】大白话详解Java内存模型,纯手撸

原创
作者头像
全栈开发日记
发布2025-09-09 14:58:02
发布2025-09-09 14:58:02
16000
代码可运行
举报
文章被收录于专栏:全栈开发日记全栈开发日记
运行总次数:0
代码可运行

下面的内容如果我没有专门提及Java版本,则都是以Java8+为例,已经过期的Java7-就不过多研究了。

内容仅为个人观点,如果有不对的请留言互相学习。

1. Java内存模型

在Java8+中内存模型中主要包括:

① 堆内存(年轻代、老年代、字符串常量池)

② 其他的非堆内存,包括了元空间(类的元数据、运行时常量池)、代码缓存、线程栈

Java内存模型
Java内存模型

1.1. 字符串常量池和运行时常量池

这两个都是常量池,为什么一个在堆内存中,另一个在元空间中呢?

运行时常量池中存储的是字符串常量池的引用,可以理解为你把文件存到了D盘,但给这个文件创建了一个快捷方式,把快捷方式存在C盘,这样你就可以直接在C盘快速的访问D盘中的文件了。

基本类型常量(如intfloat)会嵌入代码或存于运行时常量池。

假设我们有以下代码:

代码语言:javascript
代码运行次数:0
运行
复制
String s = "hello";

它的加载方式如下图所示:

加载方式
加载方式

在编译阶段时,会先根据.class文件生成符号引用的记录。

在类加载阶段,把这个生成的符号引用记录加载到运行时常量池中,此时还没有创建实际的字符串。

在运行阶段,JVM才会根据符号引用记录去字符串常量池中创建,就相当于是运行之前,JVM假装已经有了这个字符串(反正也暂时不用),运行的时候才去真的创建。

注意:这里有一个误区,String s = "hello"String s = new String("hello")加载方式不一样。后者因为使用了new,所以在堆中创建了两个对象。

1.2. 年轻代和老年代

年轻代和老年代是堆内存的主要区域,每一个新建对象都会先进入年轻代中的Eden 区,当Eden 区满时会触发Minor GC,GC过后Eden 区幸存下来以及非空闲幸存者区的对象会通过复制算法复制到空闲的幸存者区(S0和S1始终会保持其中一个为空),复制完成后Eden区和原非空闲幸存者区对象会被清空。

年轻代内存分布图
年轻代内存分布图

每一次 Minor GC年轻代中存活的对象年龄都会+1,当幸存者区中的对象年龄≥15时会晋升至老年代中。

在Java8中会动态调整晋升阈值,如果某年龄对象总大小 > Survivor 区 50%,则≥该年龄的对象直接晋升。

对象在对内存中完整生命周期
对象在对内存中完整生命周期

这个是理想情况下是这样的,但有时候就是不太理想,比如创建的对象太大幸存者区放不下的情况都会导致对象直接被放到了老年代中。

而晋升到老年代的对象太多时,就会导致频繁的Full GC,,导致性能下降甚至OOM,如果确实业务需要,可以考虑增加堆空间大小(-Xmx)或者调整老年代的比例(默认老年代和年轻代是2/1)。

1.3. 永久代和元空间

Java8之前的版本中还有一个叫永久代的东西,在Java8中大部分作用被元空间取代了,但也并不是替换这么简单,Java8之前的永久代跟年轻代、老年代一样是在堆内存中,现在的元空间并没有在堆内存中。

由于永久代是堆内存的一部分,所以它的内存空间是固定的。而元空间并不受堆内存的影响,它是动态扩展,你电脑系统的内存有多大它就能用多大,当然你也可以通过配置JVM参数-XX:MaxMetaspaceSize设置上限。

上文说到的字符串常量池在Java8之前是在永久代中的,Java8之后字符串常量池的“老大”永久代被干掉了,字符串常量池翻身当大哥了,直接属于堆内存中的一块空间了。

1.4. GC行为

上文中多次出现Minor GC和Full GC并不是一种具体的垃圾回收算法,而是指一种行为,Minor GC是指局部回收,一般用在年轻代中的回收,所以也被称 Young GC。而Full GC是指全局回收,整个堆内存和元空间都会被回收。

在GC过程中会STW(Stop-The-World),就是说这个期间会把正在运行的线程暂停,是为了对象引用不会在这期间修改,可以类比一个清洁工来办公室清理垃圾,但得让你们腾地方,就会导致此期间无法工作。一般Minor GC行为的STW时间是毫秒级,但Full GC是全堆清理,它的STW时间一般都得秒级,甚至能达到30s以上。

除了上述两种GC行为,还有另一种Java9+才应用的Mixed GC行为,即混合行为。就是为了防止上述说的Full GC时秒级的STW。Mixed GC能让垃圾回收像回收年轻代一样回收老年代,不需要全堆的停顿,一般是将回收过程分多次进行,这样也可以分摊停顿时间。

但被用的最多的Java8当老年代满时,就会触发Full GC,不会进行Mixed GC,要在Java 8中使用Mixed GC需要手动配置,并且不太稳定,建议是直接升级JDK版本到JDK11+。

如果需要在Java8中使用Mixed GC行为,可以通过配置如下参数:

代码语言:javascript
代码运行次数:0
运行
复制
-XX:+UseG1GC # 使用G1垃圾回收器
-XX:InitiatingHeapOccupancyPercent=45 # 触发并发标记的老年代阈值(老年代占堆内存比例)
-XX:G1MixedGCLiveThresholdPercent=85 # Mixed GC回收区域的存活率上限(存活对象 > 85% 的区域不回收)
-XX:G1HeapWastePercent=10 # 碎片空间占堆内存比例,也就是允许10%空间浪费

调优指南

① -XX:G1MixedGCLiveThresholdPercent=85可能会因为太多区域被跳过,导致每次GC堆释放空间太少,从而导致频繁的GC。需要根据自己系统业务,判断是否会存在这个情况来调整比例。

② -XX:G1HeapWastePercent=10可能会导致堆中有大量碎片空间,但还没有达到10%的这个阈值,可以根据自己系统堆空间大小来实际调整。

1.5. GC类型

六大GC类型垃圾收集器对比表

收集器

年轻代算法

老年代算法

优势

适用场景

Serial GC

复制

标记-整理

单线程低开销

客户端/嵌入式(资源受限)

Parallel GC

复制(多线程)

标记-整理(多线程)

高吞吐量

计算密集型后端

CMS

复制

并发标记-清除

低停顿(老年代)

响应敏感的 Web 服务(已废弃)

G1 GC

复制(Region)

复制+标记-整理

平衡吞吐与停顿

大堆内存(6GB~16GB)

ZGC

并发复制

并发复制

亚毫秒停顿

超大堆(TB级)、低延迟要求

Shenandoah GC

并发复制

并发复制

低停顿+高吞吐

大堆内存、混合负载

JDK 8 默认:Parallel GC(吞吐优先)

JDK 9~14 默认:G1 GC(平衡型)

JDK 15+ :ZGC/Shenandoah(极致低延迟)

下面是主要的几个GC收集器的STW停顿时间

STW停顿时间
STW停顿时间

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. Java内存模型
    • 1.1. 字符串常量池和运行时常量池
    • 1.2. 年轻代和老年代
    • 1.3. 永久代和元空间
    • 1.4. GC行为
    • 1.5. GC类型
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档