jvm知识点

240 阅读11分钟

JVM.png

1. jvm内存结构

JVM内存结构.png

线程共享: 堆和方法区
线程隔离: 程序计数器、虚拟机栈、本地方法栈

1. 程序计数器: java是多线程的,当线程数大于CPU数的时候,就会进行线程切换,切换就意味着中断和恢复。自然需要一块内存存放当前线程的执行信息。程序计数器就是用于记录各个线程执行的字节码的地址,循环、跳转、异常、线程恢复等都依赖于计数器。每个线程都有一个独立的程序计数器。

2. 虚拟机栈: 每个线程在创建的时候就会创建一个虚拟机栈,每个方法在调用的时候会创建一个栈帧,栈帧包括局部变量表、操作数栈、方法返回值、动态链接等。虚拟机栈伴随着线程的整个生命周期。

虚拟机栈.png

3. 本地方法栈: 虚拟机栈用户管理java函数的调用,本地方法栈用于管理本地方法的调用。这里的本地方法是指非java方法,一般是C语言实现。

4. 堆: jvm里最大的一块内存区域,它是线程共享的,所有的对象都是在堆里分配的

5. 方法区: 存储了类信息、常量池。类信息包括类的版本、方法、字段、接口等。常量池又包括静态常量和运行时常量。

String s1 = "abc" new String("abc") 内存分配问题

image.png

image.png

2. 什么是内存溢出?什么是内存泄漏

内存溢出: OOM是指内存不足。程序运行需要使用的内存超过了最大可用值,如果不进行处理就会影响其他进程,所以操作系统处理办法就是立即报错。举例子,500ml的被子倒进入600ml水,谁就会溢出来

内存泄漏: 本来无用的对象却继续占用内存,没有再恰当的时机释放占用内存。

juejin.cn/post/707862…

怎么排查内存泄漏问题

juejin.cn/post/709613…

3. 垃圾对象是怎么被找到的

1. 引用计数

给对象增加一个计数器,每当一个地方引用它的时候加1,每当一个引用失效时候减1,当计数等于0时,被当成垃圾。缺点是会有循环引用

2. 根可达

jvm默认的垃圾寻找方法。原理是定义了一些列的根,称为GC Roots。从GC Roots往下找。走过的地方叫做引用链,当一个对象与GC Roots没有任务引用链连接,称之为垃圾。

有哪些: 比如 虚拟机栈中所引用的对象 方法区中的常量池的常量,方法区中引用静态变量、虚拟机栈中引用的对象

image.png

4. java四种引用类型

强引用
Object o = new Object() 只要强引用关系还在,就不能回收被引用的对象。

软引用
那些有用但是没必要的对象,当内存空间不足时就会回收。

弱引用
比软引用更多,只要垃圾回收器一工作就会被回收。

虚引用
唯一作用是作为一种通知,比如零拷贝

5. 垃圾回收算法

1. 标记拷贝算法

原理: 把程序运行的堆分成大小相同的两半,一半称为from空间,一半称为to空间。利用from空间进行分配,当空间不足以分配新的对象的时候,就会触发GC。GC会把存活的对象全部复制到to空间。当复制完成以后,会把from和to互换

基于拷贝的算法--基础版V2.png

优点: 简单高效,不会产生内存碎片

缺点: 空间利用率低,存活对象较多的时候,效率低。

2. 标记清除算法

原理: 标记需要回收的对象,统一回收

Mark-Sweep.png

优点: 空间利用率高 缺点: 有内存碎片,效率不高

3. 标记整理算法

原理: 首先标记需要回收的对象,标记完成后,不是直接清理,而是将所有存活的对象都向同一端移动,然后清理

优点: 空间利用率高,没有内存碎片

缺点: 标记和回收都需要操作链表,效率不高

6. 分代收集理论

分代收集算法.png

新生代怎么进入老年代的

juejin.cn/post/684490…

7. 垃圾回收器

1. 垃圾回收器概述

新生代.png

老年代.png

2. 回收器配合使用

配合使用的收集器.png

3. CMS

CMS可以说是垃圾回收器的创举,实现了与用户线程一起工作,并发完成。

回收过程:

CMS回收流程.png

1. 初始标记:只标记与GC Roots关联的对象,速度很快,需要STW

2. 并发标记:根据第一步关联的对象找到所有的引用关系,与用户线程并发运行,耗时较长,但是不会有什么影响

3. 重复标记:修复并发期产生变动的对象,需要STW

4. 并发清理:真正的回收,不需要STW,与用户线程并发运行

优点: 耗时较长的并发标记与并发清除阶段与用户线程一起工作

缺点:

  1. 并发阶段,占用CPU资源
  2. 并发清除阶段,用户线程还在运行,可能产生浮动垃圾
  3. 产生内存碎片(标记清除算法本身)
  4. 退化为Serial Old收集器进行收集,单线程,停顿时间长。

image.png

4. G1

G1 顾名思义垃圾回收第一。它的回收对象不仅局限于分代,而是面向堆内存的所有对象。

G1 会把堆内存分为多个大小相等的分块,代表不同含义Eden区、Survivor区、Old区、Humongous区(G1用于分配大对象的区域)

分块GC简介.png

G1对于CMS的改进:

  • 使用标记整理算法,不会产生内存碎片
  • 停顿时间可控,可以设置停顿时间来控制垃圾回收时间。

执行步骤:

1. 初始标记: 标记GC Roots关联的对象,STW

2. 并发标记:

3. 最终标记: 处理第二步遗留下来的少量记录,需要STW,但是可以并发运行

4. 筛选回收:

  • 对每个分块的回收价值和成本排序,形成优先级列表
  • 根据优先级列表和设置的停顿时间来回收
  • 需要回收的分块的存活对象复制到不需要回收的分块中,然后回收需要回收的(复制+标记整体算法)

CMS与G1如何选择

image.png

image.png

8. 说说三色标记

1. 三色标记算法思想

可以让jvm不发生或者短时间发生STW,从而达到清除内存垃圾的目的 三色标记法将对象的颜色标记为黑、灰、白三种颜色 
黑色:  该对象已经被标记,并且该对象的引用也全部被标记
灰色:  对象被扫描过了,但是对象下还存在没被扫描的引用
白色:  对象没有被扫描过,表示不可达

并发标记出现误标的原因(两者同时出现)

  1. 新增一条或者多条黑色到白色的引用(CMS)

  2. 删除了灰色到白色的直接或者间接引用 (G1)

2.CMS解决方法:增量更新

描述:当一个未标记的白色对象被黑色对象重新引用时,黑色变成灰色

CMS的两个缺点:内存碎片和浮动垃圾

CMS采用标记清理的算法,会产生内存碎片,当到达一定数量时,CMS无法清理这边碎片,CMS会让serial old来清理,因为是单线程,效率低。

所以CMS会出现一种情况,硬件升级了,却越来越卡,问题就出在这。

解决1: mark-sweep-compact算法,启动压缩,减少内存碎片

-XX:+UseCMSCompactAtFullCollection 开启 CMS 的压缩

-XX:CMSFullGCsBeforeCompaction 默认为0,指经过多少次 CMS FullGC 才进行压缩

解决2: 降低出发CMS GC的阈值,减少浮动垃圾

-XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让老年代占用率达到该值就进行 CMS GC

3. G1解决方法:SATB

snapshot at the beging

  • 在开始的时候生成一个快照记录存活对象
  • 在一个引用断开后,要将此引用推到GC的堆栈里,保证白色对象(垃圾)还能被GC线程扫描到

9. 排查OOM问题

  • 1.增加两个参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof,当 OOM 发生时自动 dump 堆内存信息到指定目录;
  • 2.同时jstat查看监控JVM的内存和GC情况,先观察问题大概出在什么区域;
  • 3.使用工具载入到dump文件,分析大对象的占用情况

juejin.cn/post/708532…

image.png

image.png

10. jvm启动参数

# 设置堆内存
-Xms(初始) -Xmx(最大) 一般相等

# 指定GC算法
-XX:UseG1GC

# 指定GC线程数
‐XX:ParallelGCThreads=4

# 打印GC日志
-XX:PrintGCDetails

# 指定日志文件
‐Xloggc:gc.log

# 单个线程栈大小
-Xss

#指定堆内存溢出时自动进行Dump

-XX:+HeapDumpOnOutOfMememoryError

-XX:HeapDumpPath=""

11. jvm问题排查与调优实战

小工具: gceasy.io/

开胃菜

GC停顿造成的影响

  • 增加用户线程执行时间,最直接是增加了用户响应时长
  • 降低服务的吞吐量
  • 响应时间过长导致其他问题

理想的GC核心指标

  1. YGC频率5秒一次?或者10秒1次?
  2. YGC平均时间20ms,不超过50ms
  3. CMS GC不超过1天一次
  4. FullGC执行时间不到1s
  5. FullGC执行不算频繁,10分钟1次?

GC调优原则

  • 多数的Java应用是不需要在服务器上进行GC优化的
  • 多数导致GC问题,都不是因为我们参数设置错误,而是代码问题
  • 实际使用中,分析GC情况优化代码比优化GC参数要多得多
  • 减少创建对象,减少使用全局变量和大对象

常见的GC停顿过长原因

jvm本身

  • Y区过小
  • GC线程数过小
  • 堆内存过大
  • GC任务分配不均
  • 选择合适的回收器

怎么判断是不是GC引发的问题

在GC问题处理过程中,如何判断是GC导致的故障还是系统本身引发的GC。这个是很重要的。我们把表象分为四类case:GC耗时增大、线程Block增多、慢查询增多、CPU负载高

  • 时序分析: 先发生的是根因的概率更大,通过监控分析各个指标的异常时间点,还原时间线。如先观察到CPU负载高,那么整体问题链路可能是CPU导致的
  • 概率分析: 根据历史经验,比如经常有慢查询报警。
  • 反证分析: 比如CPU、慢查都正常,那很可能是GC导致的。

最后个指标证明都没问题,那就要分析GC日志了。

GC问题分类

分代/分区GC

YGC: YGC频繁:YGC设置过小
OldGC: OldGC频繁:老年代设置较小;触发老年代回收的阈值设置较低;OldGC不频繁但是单次耗时大,根据日志查看哪个过程耗时大
Full GC: 全量收集的GC,对整个堆进行回收,STW时间较长,一旦发生,影响较大。

CMS常见问题

  • 最终标记阶段停留时间长

CMS的GC停顿时间80%都在最终标记阶段。若该阶段停顿时间长,常见原因是新生代堆老年代的无效引用,在上一阶段的并发标记阶段未完成。

设置参数 CMSScavengeBeforeRemark。在执行最终操作之前先出发YGC,从而减少新生代对老年代的无效引用,降低最终标记阶段的停顿

  • 并发模式失败&晋升失败

并发模式失败: concurrent mode failure。当CMS在执行回收时,新生代发生垃圾回收,同时老年代又没有足够的空间晋升的对象时,CMS垃圾回收就会退化成单线程的FullGC。

晋升失败:promotion failed 当新生代发生垃圾回收,老年代有足够的空间可以容纳晋升的对象,但是由于空闲空间的碎片化,导致晋升失败,此时会触发单线程且带有压缩动作的FullGC

image.png

问题1: GC停顿3s------YGC空间太小

排查过程

  1. 查看jvm参数配置

  2. 查看监控指标 GC耗时/GC频次

  3. 收集GC日志

  4. 使用gceasy平台分析日志

  5. YGC promotion fail

Survivor Space放不下,对象只能放入老年代,而此时老年代也放不下造成的。老年代CMS还没有机会进行回收,又放不下转移到老年代的对象,因此会触发CMS收集器, 进而产生较长停顿

通常新生代与堆内存的比例要保持为 1:4

问题2: 频繁fullGC问题

juejin.cn/post/706407…

image.png

image.png

问题3: 应用启动时Full GC频繁

image.png