你还记得 JVM 和 GC 吗?

初入 Java 的世界,大体有两方面需要学习,一是开发实践,这直接关系到能不能找到一份工作,二是基础知识,这决定了能在这条路上走多远。一天做十个功能的人固然厉害,但我觉得懂底层原理的人更厉害,因为前者是在怎么做的层面熟能生巧,后者能深入理解为什么要这样做,不信看他们解决 bug 的速度就知道了。

这篇文章讲一讲现阶段我对 JVM 和 GC 的理解。

还记得在一次重要面试中,我就因为不懂 GC 露了怯。

面试官:你了解 GC 吗?
我:对的,了解一点。
面试官:那你能简单介绍一下吗?
我:这里面有一个 ZGC,还有一个 G1 GC。。。(前一天晚上看的)
面试官:具体原理呢?
我:还没有深入研究。
面试官:哈哈,今天时候也不早了,回去等通知吧。

如果能对五个月前的自己说些什么,那就以下吧。

一、JVM

JVM 的全称是 Java Virtual Machine,它是一个虚拟计算机,通过仿真模拟真实计算机的处理器、堆栈和指令系统等硬件架构,使得 Java 语言在不同平台上运行时不需要再重新编译,实现了 Java 的平台无关性。在学习内存结构和内存参数的过程中,我画了这样一张图(以 JDK8 为例):

JVM内存结构

参照图片,简单讲解一下每个区域的作用:

堆「Heap」

存储对象、对象成员与类定义、静态变量。为了更好地回收内存,堆内存需要分代。于是堆内存中大大小小的对象便有了岁数「age」的概念。在 JVM 的世界里,每经过一次垃圾回收,便过去了一年,在场的对象都被宣判长大一岁,只要它不是被回收的那一个。年轻代与老年代的分界线默认为 15 岁。

新创建的对象默认进入新生代「Eden」区(也称伊甸园),但如果某个对象生来是个庞然大物(占用大片连续内存),就直接进入老年代,这是出于分配内存时的效率考虑的。

年轻代中除了新生代,还有两个生存区S0和S1,是用于垃圾回收的区域,它们始终总用一个是空的,会在 GC 部分详细讲解。

注意:代和区都是逻辑概念。

栈「Stack」

又称线程栈/方法栈/调用栈,存储方法中使用的原生数据类型和对象引用地址。每启动一个线程,JVM 内存中就多出一块线程栈区域,因此,在逻辑上:

JVM = 1✖️堆 ➕ n✖️栈

那么,各类数据究竟存储在堆还是栈上呢?

存储位置原生数据类型对象引用
局部变量堆:对象本身;栈:对象的引用地址。
成员变量

非堆「Non-Heap」

堆是垃圾回收机制作用的区域,那么作用不到的地方就称为非堆,它非就非在这里

不进行垃圾回收。

非堆分为三个内存池:

  • Metaspace:元空间,存放方法,使用的是直接内存
  • CCS:全称 Compressed Class Space,存放 class 信息(故与 Metaspace 有重叠)
  • Code Cache:存放 JIT 编译器编译后的本地机器代码

堆外「Heap-Except」

广义上讲,在分代算法下,堆内存中的新生代、老生代是连续的虚拟地址,其中的间隙可以认为是堆外内存,这显然不是它的普遍含义;狭义上讲,堆外内存主要是指 java.nio.DirectByteBuffer 在创建的时候分配内存,我们平常说的堆外内存溢出说的就是这个。堆外能直接分配和释放内存,提高效率。对于需要频繁操作的内存,并且仅仅是临时存在一会的,都建议使用堆外内存,并且做成缓冲池,不断循环利用这块内存。

控制参数

  • Xmx: 堆内存上限值,一般为物理内存的1/4,建议设置在系统内存的60%~80%
  • Xms: 堆内存初始值,一般为物理内存的1/64
  • Xmn: 年轻代内存上限值,包括「Eden」区、「S0」区和「S1」区,建议设置-xmx的1/2~1/4
  • Xss: 线程栈,默认1MB

例:-xmx3g -xms3g -xmn3g -xss1m

  • Metaspace: 元空间上限值,默认为无穷大,这意味着它只受系统内存的限制

例:-XX:MaxMetaspaceSize=256m

  • Direct Memory: 堆外上限值

例:-XX:MaxDirectMemorySize=128m

二、GC

GC 的全称是 Garbage Collection,也就是上文提到过的垃圾回收机制,用于管理有限的内存资源。

GC 中的元操作

标记「Mark」

根元素出发,遍历所有可达的活跃对象,并在本地内存中分门别类记下。

根元素「Roots」:局部变量、活动线程、静态域、JNI引用等。

一般标记是 STW 的,但并发标记不是 STW 的,比如在G1的混合回收过程中就有用到并发标记。

清除「Sweep」

不可达对象所占用的内存,在之后进行内存分配时可以重用。

整理「Compact」

实际上,标记-清除算法会产生许多内存碎片,为解决这一问题,引入了整理算法。通过图片,可以直观理解:

image

复制「Copy」

复制算法发生在堆里面的年轻代,也就是新生代「Eden」和两个生存区 「S0」/「S1」 之间。「S0」和「S1」是结构相同的两块区域,它们在任意时刻都有其一是空的,并且每经过一次 GC ,空的会变为非空,非空的会变为空,如此循环往复,其中非空的存活区称为**「From」区,空的那个则称为「To」区。GC 的实质就是清理那些用不到的、在逻辑上不可达的废物资源,而「Eden」区和「From」区存活下来的对象会被标记「Mark」,然后复制**到「To」区,这样就使得每次的内存回收都是对「From」区进⾏回收,同时「Eden」区也并不需要保存对象,它只需要接纳新的对象就可以了。每一轮GC最终过滤出来的精华都在这一轮的「To」区了,要不然就是晋升到老年代了。

停顿「STW」

Stop The World,指的是一种全局暂停现象,所有 Java 线程停止当前工作,native代码虽可以执行,但不能与JVM交互。在大部分 GC 算法中,不管是新生代还是老年代,为了把握对象间瞬时的引用关系,总会触发 STW,影响系统吞吐量,区别仅在于 STW 的时间长短。

GC 算法

理解了 GC 算法中的元操作以后,就可以开始排列组合了。

串行GC「Serial GC」

召唤方式:-XX:+UseSerialGC

年轻代:「Mark」+「Copy」

老年代:「Mark」+「Sweep」+「Compact」

适用范围:堆内存较小的单核CPU

并行GC「Parallel GC」

召唤方式:-XX:ParallelGCThreads=N(N为线程数,默认为CPU核心数)`

年轻代:「Mark」+「Copy」

老年代:「Mark」+「Sweep」+「Compact」

适用范围:对延迟要求低,追求高吞吐量的多核服务器,是 JDK8 的默认GC

CMS GC

全称:Mostly Concurrent Mark and Sweep Garbage Collector,即:最大并发-标记-清除-垃圾收集器

召唤方式:-XX:+UseConcMarkSweepGC

年轻代:「Mark」+「Copy」 ,
此处又称「Parnew」回收器

老年代:「Mark」+「Sweep」+ 空闲列表

适用范围:对吞吐量要求低,追求低延迟的多核服务器,在 JDK13 以后被删除

G1 GC

全称:Garbage First Garbage Collector,即:垃圾优先垃圾回收器

召唤方式:-XX:+UseG1GC

特点:

虽然不需要其他收集器配合就能独⽴管理整个 GC 堆,但还是保留了分代的概念。每个内存分段都可以被标记为「Eden」区,「Survivor」区,「Old」区等。这样属于不同代,不同区的内存分段就可以不必是连续内存空间了;

image

G1收集器在后台维护了⼀个优先列表,每次根据允许的收集时间,优先选择回收价值最⼤的Region,每个Region拥有各自的分代属性,但这些分代不需要连续,可以有效避免内存碎片化问题;

步骤:
1. 初始标记
2. 并发标记
3. 最终标记
4. 筛选回收。

适用范围:大内存,追求低延迟的服务端,是JDK9以后版本的默认GC

ZGC

召唤方式:XX:+UseZGC

通过着色指针和读屏障技术,解决了转移过程中准确访问对象的问题,创造了GC最大停顿时间不超过10ms的奇迹。

总结

对于GC而言,

延迟和吞吐量是此消彼涨的两个指标。

延迟直观对应的是接口响应的速度,一个请求得到响应的速度会被 STW 影响,要想让接口延迟降低,单次STW的时间就要缩短,那就需要减少堆内存或者频繁 GC ,这样一来总体的 STW 时间势必变长,导致吞吐量下降;反之亦然。

「Serial GC」是经典的GC,后来的几代GC以它为基础进行改造、优化:

  1. 「Parallel GC」加入了并行,以高吞吐量为目标;
  2. 「CMS GC」加入了并发,以低延迟为目标;
  3. 「G1 GC」打破了分区的物理界限,让大内存也可以享受低延迟;
  4. 「ZGC」再度降低延迟,并且相较「G1 GC」仅仅损失不到15%的吞吐量。

可见,就目前而言,GC是朝着低延迟的方向发展演进的,我想这是因为延迟大小关系到用户体验的好坏吧。

最后,这篇文章写得贼累,求赞求转发~~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值