【后端面经-Java】JVM垃圾回收机制

【后端面经-Java】JVM垃圾回收机制

    • 1. Where:回收哪里的东西?——JVM内存分配
    • 2. Which:内存对象中谁会被回收?——GC分代思想
      • 2.1 年轻代/老年代/永久代
      • 2.2 内存细分
    • 3. When:什么时候回收垃圾?——GC触发条件
    • 4. Why:凭什么说它是垃圾?——垃圾判断算法
      • 4.1 引用计数法
      • 4.2 可达性分析法
    • 5. How:如何对待垃圾?——垃圾回收算法
      • 5.0 垃圾的垂死挣扎
      • 5.1 标记-清除算法
      • 5.2 标记-整理算法
      • 5.3 复制算法
      • 5.4 分代收集算法
    • 6. Who:谁去处理垃圾?——垃圾回收器
      • 6.1 年轻代-Serial收集器
      • 6.2 年轻代-ParNew收集器
      • 6.3 年轻代-Parallel Scavenge收集器
      • 6.4 老年代-SerialOld收集器
      • 6.5 老年代-ParallelOld收集器
      • 6.6 老年代-CMS(Concurrent Mark Sweep)收集器
      • 6.7 年轻代和老年代-G1(Garbage First)收集器
      • 6.8 垃圾回收器对比图
    • 面试模拟
    • 参考资料

1. Where:回收哪里的东西?——JVM内存分配

JVM垃圾回收机制(Garbage Collect,简称GC)主要负责回收JVM内存当中未被及时释放回收的内存区域,JVM垃圾回收机制让程序员摆脱了手动释放内存的操作,降低了程序员疏忽大意导致的风险。
那么,垃圾回收机制到底针对哪一块的内存空间进行处理呢?是整体内存还是某一块内存?
在回答这个问题之前,我们需要先了解一下JVM内存分配机制,JVM内存分配机制主要有如下几个区域:

  • 栈(Stack)
  • 堆(Heap)
  • 方法区(Method Area)
  • 本地方法栈(Native Method Stack)
  • 程序计数器(PC)
    我们需要知道的是,栈、本地方法栈、程序计数器和方法调用有关,是线程私有的,随着方法结束,栈和本地方法栈的栈帧会出栈,释放内存,因此,这三部分并不需要使用垃圾回收机制。
    而堆主要存放对象实例和数组,而方法区存放类的信息、编译信息、常量池等,这两块区域由于是线程共享的,在方法调用结束之后也不会被释放内存(毕竟A用完了,不知道B、C、D会不会用到这部分)需要使用垃圾回收机制进行内存回收。

关于每个区域的具体内容,可参考博客:【后端面经-Java】JVM内存分区详解

因此,内存回收机制主要针对堆和方法区进行处理。

2. Which:内存对象中谁会被回收?——GC分代思想

2.1 年轻代/老年代/永久代

JVM垃圾回收机制中,一般都是基于GC分代思想进行算法设计。
GC机制将内存内容分为三部分:

  • 年轻代(Young Generation):新产生的实例基本上都处于这代,因为新产生的实例大部分都是一次性的,因此这部分内存需要经常进行内存回收;
  • 老年代(Old Generation):在新生代残酷频繁的筛选机制中,多次存活下来的实例会进入老年代,老年代意味着生命周期较长,一般在内存没有满之前不会对这部分内存进行回收;
  • 永久代(Permanent Generation,JCK1.8之后改成元空间(Metaspace)):永久代存放的是JVM程序运行相关的元数据,比如类信息、方法信息、常量池等,内容重要且占空间小,因此基本上不会进行垃圾回收。

如果要类比的话,年轻代就是刚刚步入职场的小青年,不稳定性较高,很容易被裁员(垃圾回收),而熬过这个阶段,成为技术骨干(老年代)之后,基本上不会在正常公司运行过程中被裁员,除非公司倒闭(内存已满),而永久代或者元空间就是公司最高层的管理人员,对公司的运行起着关键作用,一般情况下不会被裁员。

2.2 内存细分

  1. 堆和方法区
    堆中存放年轻代和老年代,而方法区中存放永久代。
  2. 新生代区域细分
    新生代区域又分为Eden区Survivor0区Survivor1区,比例为8:1:1

整体内存细分情况如图所示:
在这里插入图片描述

3. When:什么时候回收垃圾?——GC触发条件

GC按照触发条件,可分为Scavenge GCFull GC

  • Scavenge GC(Minor GC)
    Scavenge GC是指年轻代Eden区的垃圾回收,触发条件为:
    • 年轻代Eden区域内存不足
      • 调用Scavenge GC之后,将未清理的元素放入Survivor0区域。如果Survivor0区域内存不足,则将Survivor0区域内存中的所有元素放入Survivor1区域。清空Survivor0区域,然后将Survivor1中的元素放入Survivor0中;
      • 如果Survivor1区域内存不足,则将Survivor1区域内存中的所有元素放入老年代中;
      • 如果老年代也内存不足,则会考虑触发Full GC。
  • Full GC(Major GC)
    Full GC是整体对内存的垃圾回收,包括年轻代和老年代,触发条件为:
    • 老年代内存不足
    • 永久代内存不足
    • 显性调用system.gc()
    • 上次GC之后堆内存的划分出现变化

对于GC线程,其本身的优先级比较低,因此在CPU空闲的时候,可能会进行GC处理,而在忙时基本上不会进行GC处理,除非此时内存空间不足,需要GC处理之后才能正常运行。
Full GC对于计算资源是一个很大的消耗,应该尽量避免使用Full GC。

4. Why:凭什么说它是垃圾?——垃圾判断算法

前面主要介绍了JVM垃圾回收机制的针对对象,GC分代思想和触发条件。那么,好好的一个对象实例,GC机制空口无凭凭什么说它是垃圾呢?这就需要垃圾判断算法了。
常见的垃圾判断方法有两种:引用计数法可达性分析法

4.1 引用计数法

  • 每个对象实例都有一个引用计数器,当有一个引用指向该对象实例时,引用计数器加1,当引用失效时,引用计数器减1,当引用计数器为0时,该对象实例就是垃圾,需要进行回收。
  • 补充一下JVM的引用类型,如下图所示:
  • 优点:判断逻辑简单;
  • 缺点:无法解决循环引用的问题(从图论角度来说就是环状节点无法识别)。

4.2 可达性分析法

  • 将每个实例看作节点,两个实例之间的引用关系看作路径。从GC Roots开始,对堆内存中的对象进行遍历,如果某个对象实例没有被遍历到,则说明该对象实例不可达,不可达则是垃圾,对其进行垃圾标记,等待后续回收(非连通图查找连通子图的数量)。
  • GC Roots指的是正在运行的程序中一些基本对象,从这些对象往下查找其引用对象,包括如下几种:
    • 虚拟机栈(栈帧中的本地变量表)中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中JNI(Native方法)引用的对象
  • 优点:能有效解决循环引用的问题;
  • 缺点:判断逻辑复杂,需要遍历整个内存空间,效率较低。

5. How:如何对待垃圾?——垃圾回收算法

常见的垃圾回收算法包括:标记-清除算法标记-整理算法复制算法分代收集算法(自适应算法)。

5.0 垃圾的垂死挣扎

在被处决之前,垃圾会进行一次垂死挣扎,实例第一次被标记为垃圾之后,如果可以进行一次有效finalize()方法调用,和其他实例建立引用,那么该实例就会被复活,不会被回收。

5.1 标记-清除算法

在垃圾判断算法执行完成后,已经被明确判断成垃圾的实例,清除法在原地释放其内存空间,将其标记为可用空间,等待后续的内存分配。

  • 优点:操作简单,原地清除不需要复制内存
  • 缺点:会产生内存碎片,长期运行会影响CPU运行效率

5.2 标记-整理算法

标记-整理算法在标记完成之后,将所有存活的实例移动到一端,然后清除掉另一端的内存空间,这样就可以有效解决内存碎片的问题。

  • 优点:无内存碎片;
  • 缺点:需要移动实例,效率较低;

5.3 复制算法

复制算法将内存空间分为两块,每次只使用其中一块,当一块内存空间内存满了之后,将存活的实例复制到另一块内存空间中,然后清除掉之前的内存空间。

  • 优点:无内存碎片,操作简单;标记和复制可并行;
  • 缺点:可用内存空间直接减半,内存利用率较低;

5.4 分代收集算法

针对不同代的数据特点,使用不同的垃圾回收算法。

  • 年轻代:复制算法
    • 存活对象较少,复制算法每次的对象复制不会有太大负担;
    • 操作频繁,复制算法标记和复制可并发处理,效率较高;
  • 老年代:标记-整理算法
    • 存活对象较多,减少内存碎片,提高可用性
  • 永久代(元空间):不作考虑

6. Who:谁去处理垃圾?——垃圾回收器

垃圾回收器是垃圾回收算法的执行者,常见的垃圾回收器如下图所示:
在这里插入图片描述

连线部分说明这两个垃圾回收器能够搭配使用。

6.1 年轻代-Serial收集器

  • 回收算法:复制算法
  • 单线程:执行回收算法的时候是单线程;适用于并发能力较低的系统。
  • Stop the World:一般线程和回收线程无法并行,执行回收算法需要中断其他线程,这个现象称为Stop the World(乱入Dio的“咋瓦鲁多”)。
    • Stop the World现象会导致系统暂停,引出垃圾收集停顿时间这一参数,影响用户体验,因此需要尽量避免;
  • 优点:简单高效,适用于单核CPU;
  • 缺点:无法并行,且单线程处理效率较低;
  • 启用方式:-XX:+UseSerialGC

6.2 年轻代-ParNew收集器

  • 回收算法:复制算法
  • 多线程:执行回收算法的时候是多线程;
  • 也会存在Stop the World现象,除了多线程的改进之外,和Serial收集器没有太大区别;
  • 优点:多线程处理效率高,适用于多核CPU;
  • 缺点:一般线程和回收线程无法并行处理;
  • 启用方式:-XX:+UseParNewGC

6.3 年轻代-Parallel Scavenge收集器

  • 回收算法:复制算法
  • 关注吞吐量
    吞吐量 = 用户线程运行时间 / (用户线程运行时间 + 垃圾回收线程运行时间)
    
  • 相关参数
      1. 垃圾收集停顿时间
      • 设置方式:-XX:MaxGCPauseMillis=一个数值
      • 停顿时间过大将会直接影响每次垃圾回收的用户体验,停顿时间过小则会导致垃圾回收频繁;
      1. 吞吐量大小
      • 设置方式:-XX:GCTimeRatio=一个数值
      • 默认取值为99%,表示只有1%的时间用于垃圾回收;
      1. 自适应模式
      • 设置方式:-XX:+UseAdaptiveSizePolicy
      • 设置自适应模式之后,内存中的新生代分配比例和老年代的时间参数可自行调整,达到吞吐量、停顿时间和内存占用的平衡;
  • 是JDK1.8的默认垃圾回收器
  • 启用方式:-XX:+UseParallelGC

6.4 老年代-SerialOld收集器

  • 回收算法:标记-整理算法
  • 单线程,可类比年轻代的Serial收集器,优缺点同理
  • CMS收集器的后备算法

6.5 老年代-ParallelOld收集器

  • 回收算法:标记-压缩算法
  • 关注吞吐量,可类比年轻代的Parallel Scavenge收集器
  • 多线程,线程数通过-XX:ParallelGCThreads设置,默认为CPU核心数
  • 启用方式:-XX:+UseParallelOldGC

6.6 老年代-CMS(Concurrent Mark Sweep)收集器

  • 回收算法:标记-清除算法
  • 关注停顿时间,期望有较短的垃圾回收停顿时间,从而优化用户体验;
  • 回收步骤:
    • 初始标记:适用可达性分析法的思想,标记GC Roots能直接关联到的对象,速度较快;
    • 并发标记:一般线程和回收线程并行,对此时标记状态出现变化的实例进行统计;
    • 重新标记:根据并发标记的结果,对标记状态发生变化的实例进行重新标记,这一步相对较慢;
    • 并发清除:清理垃圾实例,释放内存空间;这一步可以和一般线程并行;
  • 相关参数:
    • 触发阈值:
      • 设置方式:-XX:CMSInitiatingOccupancyFraction=一个数值
      • 和之前讨论的何时进行垃圾回收的触发机制不同,CMS在处理老年代的时候,不会等到内存完全占满,而是会设置一个阈值,默认数值为68%,占用内存空间超过这个阈值就进行垃圾回收处理。
    • 整理标记
      • 设置方式:-XX:+UseCMSCompactAtFullCollection
      • 如果设置这一标记,则垃圾回收之后会进行一次整理,合并内存碎片。
  • 优点:并发收集,提高执行效率;减少停顿时间,用户体验佳
  • 缺点:无法处理浮动垃圾,对CPU资源敏感,且标记清除算法会产生内存碎片

6.7 年轻代和老年代-G1(Garbage First)收集器

  • 回收算法:整体来看是标记-整理算法,局部来看是复制算法
  • 关注停顿时间,也关注高吞吐量(我全都要.jpg);
  • 分区:不同于之前所讨论的分代划分内存区域的方式,G1回收器将内存划分为一个又一个单元区域,称为Region
    • 设置方式:-XX:G1HeapRegionSize=一个数值
    • 每个分区内部可以存放年轻代或者老年代的数据,根据不同的存放数据,将分区划分为四类:
      • E-Eden区:存放年轻代当中Eden区域的数据;
      • S-Survivor区:存放年轻代当中Survivor区域(survivor0和survivor1)的数据;
      • O-Old:存放老年代的一般数据;
      • H-Humongous:存放老年代当中大对象的数据;当占据整个Region一半以上的时候,就会被划分为Humongous区域;
  • 停顿预测模型:用户可以自行设置垃圾回收停顿时间,而G1回收器会根据历史数据构建预测模型,考虑为了满足用户设置的停顿时间,本次垃圾回收可以处理哪几个Region。
  • Region优先级队列:G1浏览器维护一个Region队列,高价值的Region有更高的优先级,在垃圾回收的时候优先处理,这也是Garbage First名字的由来。
  • 回收步骤(和CMS回收器类似):
    • 初始标记
    • 并发标记
    • 重新标记
    • 并发清除
  • 优点:并发操作,并行收集,提高执行效率;关注停顿时间,可预测停顿时间,优化用户体验;可处理浮动垃圾,不会产生内存碎片;

6.8 垃圾回收器对比图

对上述垃圾回收器的对比如下所示:
在这里插入图片描述

面试模拟

Q:介绍一下JVM和垃圾回收机制。
A:从"where"、“which”、“when”、“why”、“how”、"who"的角度,重点介绍触发机制/判断算法/垃圾回收算法/垃圾回收机制

参考资料

  1. JVM之垃圾回收机制(GC)
  2. JVM的垃圾回收机制 总结(垃圾收集、回收算法、垃圾回收器)
  3. 深入理解 JVM 垃圾回收机制及其实现原理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1661.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】根据MVCC和Read View分析事务的四种隔离级别在读写场景分别是如何体现其隔离性的

目录 一、数据库并发的三种场景 二、读写场景的MVCC 1、3个(4个)记录隐藏列字段 2、undo log(撤销日志) 3、模拟MVCC场景 3.1update场景 3.2delete场景 3.3insert 3.4select场景 4、Read View 5、RR和RC的区别 5.1当…

Windows安装激活注意事项

选择语言、版本(Windows 10指的是专业版本)和体系结构(32位/64位),这里自行根据情况选择(如果机器预装的是Windows 10家庭中文版则选择家庭中文版,如果预装的是专业版则选择Windows 10。这样原先…

Revit 导出明细表的两种方法!

方法一、Revit中怎么灵活运用明细表格式的导出与导入 在做项目的时候,遇到一些项目需要进行工程量统计的时候,经常需要设置明细表里面的格式,例如字体、表格排布样式等,但是项目一旦多起来,这些工作重复性又太高&#…

适合小公司的自动化部署脚本

背景(偷懒) 在小小的公司里面,挖呀挖呀挖。快挖不动了,一件事重复个5次,还在人肉手工,身体和心理就开始不舒服了,并且违背了个人的座右铭:“偷懒”是人类进步的第一推动力。 每次想…

解决MAC IDEA终端每次都要source ~/.zshrc

安装nvm之后,发现每隔一段时间(不清楚是新打开一个终端还是会定时刷新)就要重新执行source ~/zshrc,才能执行nvm命令。找了一圈发现idea默认使用的shell是bash,将默认的shell改成zsh就可以,更改位置&#x…

【运维】shell监控脚本结合钉钉机器人实现服务及服务器监控告警

文章目录 前言一、监控shell脚本和钉钉机器人二、创建钉钉机器人:1.在钉钉群聊里点击设置2.在设置里点击机器人选项3.再点击添加机器人4.再点击选择自定义机器人5.设置机器人名称、是否加密、是否限制ip、以及触发关键字6.获取机器人的Webhook地址 三、编写监控脚本…

[爬虫]解决机票网站文本混淆问题-实战讲解

前言 最近有遇到很多小伙伴私信向我求助,遇到的问题基本上都是关于文本混淆或者是字体反爬的问题。今天给大家带来其中一个小伙伴的实际案例给大家讲讲解决方法 📝个人主页→数据挖掘博主ZTLJQ的主页 ​​ 个人推荐python学习系列: ☄️爬虫J…

架构训练营3:架构设计流程和架构师职责

架构师相关职责: 架构师是业务和技术之间的桥梁,架构师不能只顾技术,不懂业务,架构师很容易两头不讨好 三个核心能力: 判断:1业务理解力2.技术能力3.沟通能力 拆解:1技术深度2.技术宽度3.技术…

基于单片机指纹考勤系统的设计与实现

功能介绍 以51单片机作为主控系统;利用指纹采集模块存储打卡信息;12864显示当前考勤信息,时间 ;如果迟到 语音播报 您已迟到;按键进行注册指纹、删除指纹、设置当前时间和签到时间、查询打卡等;具有掉电保存…

利用Python和Selenium编程,实现定时自动检索特定网页,发现特定网页内容发生变化后,向管理员发送提醒邮件(一)

一、项目需求 要求爬取某单位网站,登录后台查看是否有新增“网友提问”,如果有新的提问,向特定邮箱发出提醒邮件。 二、项目分析 (一)判断是否可用爬虫爬取相关内容 首先查看该网站的robots.txt文件,发现…

JAVA开发(记一次504 gateway timeout错误排查过程)

一、问题与背景: 最近在发布一个web项目,在测试环境都是可以的,发布到生产环境通过IP访问也是可以的,但是通过域名访问就出现504 gateway timeout。通过postman去测试接口也是一样。ip和端口都可以通,域名却不行&…

JVM面试题总结

一.请简述对象的创建过程 对象的new字节码执行指令如下图 首先0指令new出一片内存空间,然后进行对象的初始化,初始化完成前变量都是初始值如m0 然后创建连接,t指向对象。 二.DCL单例要不要加volatile? DCL单例就是要懒汉式从创建…

SignalTap II 软件使用步骤

文章目录 前言一、SignalTap II是什么?二、使用步骤三、总结四、参考资料 前言 环境: 1、Quartus18.1 2、板子型号:原子哥开拓者2(EP4CE10F17C8) 要求: 能够使用SignalTap II进行片上调试。 一、SignalTap II是什么? S…

Docker部署Mysql数据库详解

目录 1. Docker部署Mysql 1.1 Mysql容器 1.1.1 创建Mysql容器 1.1.2 进入Mysql容器并登录Mysql 1.1.3 持久化数据 1.2 远程登录Mysql 1.2.1 修改root加密方式 1.2.2 在容器启动时配置加密方式为mysql_native_password 1.3 Mysql编码 1.3.1 Mysql编码问题 1.3.2 Mysql编码…

大模型开发(六):OpenAI Completions模型详解并实现多轮对话机器人

全文共8500余字,预计阅读时间约17~30分钟 | 满满干货(附代码),建议收藏! 代码下载点这里 一、 Completions与Chat Completions基本概念 经过海量文本数据训练的大模型会在全量语义空间内学习语法关系和表达风格,并通过某些微调过…

java学习路程之篇十、知识点、数组介绍、二维数组介绍、静态初始化、访问元素、遍历元素、动态初始化、内存图、数组常见问题

文章目录 01、数组介绍02、数组静态初始化03、数组元素访问04、数组遍历操作05、数组动态初始化06、数组内存图07、数组常见问题08、二维数组介绍09、二维数组静态初始化10、二维数组遍历11、二维数组动态初始化12、二维数组内存图 01、数组介绍 02、数组静态初始化 03、数组元…

Python爬虫学习笔记(一)————网页基础

目录 1.网页的组成 2.HTML (1)标签 (2)比较重要且常用的标签: ①列表标签 ②超链接标签 (a标签) ③img标签:用于渲染,图片资源的标签 ④div标签和span标签 &…

Dcat-admin使用 Alpine 双向数据绑定

介绍 Alpine.js 这东西真的轻量级,和vue相似,和 livewire 同一个作者,推荐大家使用,可以平替jquery 效果 实现 在 bootstrap.php 引入js Admin::headerJs([https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-y/alpinejs/3.9.0/…

在Windows下安装Anaconda平台

Anaconda介绍 安装Python的方法有很多,其中利用Anaconda来安装,是最为安全和便捷的方法之一。在Python中安装类库,各个类库之间可能存在相互依赖、版本冲突等问题。为了解决这个问题,Python社区提供了方便的软件包管理工具&#…

Swift 中的 Actors 使用以及如何防止数据竞争

文章目录 前言Actors 的基本原理Actor 是引用类型,但与类相比仍然有所不同 为什么会出现数据竞争如何防止数据竞争使用 async/await 访问数据防止不必要的暂停非隔离(nonisolated)访问为什么在使用 Actors 时仍会出现数据竞争?总结 前言 Actors 是 Swif…