搞定面试官:咱们从头到尾再说一次 Java 垃圾回收
接着前几天的两篇文章,继续解析JVM面试问题,送给年后想要跳槽的小伙伴
万万没想到,面试中,连 ClassLoader类加载器 也能问出这么多问题.....
三、GC垃圾回收
1、GC是什么?为什么要GC
GC:垃圾收集,GC能帮助我们释放jvm内存,可以一定程度避免OOM问题,但是也无法完全避免。Java的GC是自动工作的,不像C++需要主动调用。
当new对象的时候,GC就开始监控这个对象的地址大小和使用情况了,通过可达性分析算法寻找不可达的对象然后进行标记看看是否需要GC回收掉释放内存。
2、你能保证GC执行吗?
不能,我只能通过手动执行System.gc()方法通知GC执行,但是他是否执行的未知的。
3、对象的引用类型有哪几种,分别介绍下
强引用
发生GC的时候不会回收强引用所关联的对象。比如new就是强引用。
软引用
有用但非必须的对象,在OOM之前会把这些对象列进回收范围之中进行第二次回收,若第二次回收还没有足够的内存,则会抛出OOM。也就是第一次快要发生OOM的时候不会立马抛出OOM,而是会回收掉这些软引用,然后再看内存是否足够,若还不够才会抛出OOM。
弱引用
有用但非必须的对象,比软引用更弱一些,只要开始GC,不管你内存够不够,都会将 弱引用所关联的对象给回收掉。
虚引用
也叫幽灵引用/幻影引用,无法通过虚引用获得对象,他的意义在于能在这个对象被GC掉时收到一个系统通知,仅此而已。
4、垃圾收集算法有哪些
标记清除
分为两步:标记和清除。
首先需要标记出所有需要回收的对象,然后进行清除回收变为可用内存。
缺点:效率低,会产生垃圾碎片 。

标记清除01

标记清除
复制算法
将可用堆内存按照容量分为大小相等的两块,每次只用一块,当这块内存快用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存一次清理掉。
年轻代from/to(s1/s2)采取的就是此种算法。老年代一般不会采取此种算法,因为老年代都是大对象且存活的久的,空间压缩一半代价略高。
优点:效率较高、不会产生碎片。
缺点:将内存缩小为原来的一半,代价略高。


标记整理
分为两步:标记和整理。
整理其实也是两步:整理+清除。
整理让所有存活的对象都移动到一端,然后清理掉边界以外的内存。
优点:不会产生碎片问题,适合年老代的大对象存储,不像复制算法那样浪费空间。
缺点:效率赶不上复制算法。


分代算法
并不是新算法,而是根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。
5、为什么要分代
因为在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间会相对较长,也有很多对象完全没必要遍历,比如大对象存活的时间更长,遍历下来发现不需要回收,这样更浪费时间。所以才有了分代,分治的思想,进行区域划分,把不同生命周期的对象放在不同的区域,不同的区域采取最适合他的垃圾回收方式进行回收。
6、分代垃圾回收是怎么工作的
分代回收基于这样一个理念:不同的对象的生命周期是不一样的,因此根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。这样来提高回收效率。
新生代执行流程:
- 把 Eden + From Survivor(S1) 存活的对象放入 To Survivor(S2) 区;
- 清空 Eden 和S1 区;
- S1 和 S2 区交换,S1 变 S2,S2变S1。
每次在S1到S2移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老年代。大对象也会直接进入老年代。
老年代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。
7、垃圾回收器有哪些

Serial
采取复制算法,用于新生代,单线程收集器,所以在他工作时会产生StopTheWorld。单线程情况下效率更高,比如用于GUI小程序
ParNew
采取复制算法,用于新生代,是Serial的多线程版本,多个GC线程同时工作,但是也会产生StopTheWorld,因为不能和工作线程并行。
Parallel Scavenge
采取复制算法,用于新生代,和ParNew一样,所以也会产生STW,多线程收集器,他是吞吐量优先的收集器,提供了很多参数来调节吞吐量。
Serial Old
采取标记整理算法,用于老年代,单线程收集器,所以在他工作时会产生StopTheWorld。单线程情况下效率更高,比如用于GUI小程序
Parallel Old
采取标记整理算法,用于老年代,Parallel Scavenge收集器的老年代版本,吞吐量优先。
CMS
采取标记清除算法,老年代并行收集器,号称以最短STW时间为目标的收集器,并发高、停顿低、STW时间短的优点。主流垃圾收集器之一。
G1
采取标记整理算法,并行收集器。对比CMS的好处之一就是不会产生内存碎片,此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。而且他的STW停顿时间是可以手动控制一个长度为M毫秒的时间片段(可以用JVM参数 -XX:MaxGCPauseMillis指定),设置完后垃圾收集的时长不得超过这个(近实时)。
8、详细介绍一下 CMS 垃圾回收器?
采取标记清除算法,老年代并行收集器,号称以最短STW时间为目标的收集器,并发高、停顿低、STW时间短的优点。主流垃圾收集器之一。
主要分为四阶段:
- 初始标记:只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。所以此阶段会STW,但时间很短。
- 并发标记:进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。不会STW。
- 重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。STW时间会比第一阶段稍微长点,但是远比并发标记短,效率也很高。
- 并发清除:清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。

所以CMS的优点是:
- 并发高
- 停顿低
- STW时间短。
缺点:
对cpu资源非常敏感(并发阶段虽然不会影响用户线程,但是会一起占用CPU资源,竞争激烈的话会导致程序变慢)。
无法处理浮动垃圾,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,失败后而导致另一次Full GC的产生,由于CMS并发清除阶段用户线程还在运行,伴随程序的运行自然会有新的垃圾产生,这一部分垃圾是出现在标记过程之后的,CMS无法在本次去处理他们,所以只好留在下一次GC时候将其清理掉。
内存碎片问题(因为是标记清除算法)。当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
9、详细介绍一下 G1 垃圾回收器?
采取标记整理算法,并行收集器。
特点:
- 并行与并发执行:利用多CPU的优势来缩短STW时间,在GC工作的时候,用户线程可以并行执行。
- 分代收集:无需其他收集器配合,自己G1会进行分代收集。
- 空间整合:不会像CMS那样产生内存碎片。
- 可预测的停顿:可以手动控制一个长度为M毫秒的时间片段(可以用JVM参数 -XX:MaxGCPauseMillis指定),设置完后垃圾收集的时长不得超过这个(近实时)。
原理:
G1并不是简单的把堆内存分为新生代和老年代两部分,而是把整个堆划分为多个大小相等的独立区域(Region),新生代和老年代也是一部分不需要连续Region的集合。G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
补充:
Region不是孤立的,也就是说一个对象分配在某个Region中,他并非只能被本Region中的其他对象引用,而是整个堆中任意的对象都可以相互引用,那么在【可达性分析法】来判断对象是否存活的时候也无需扫描整个堆,Region之间的对象引用以及其他手机其中新生代和老年代之间的对象引用虚拟机都是使用Remembered Set来避免全堆扫描的。
步骤:
- 初始标记:仅仅标记GCRoots能直接关联到的对象,且修改TAMS的值让下一阶段用户程序并发运行时能正确可用的Region中创建的新对象。速度很快,会STW。
- 并发标记:进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。不会STW。
- 最终标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。STW时间会比第一阶段稍微长点,但是远比并发标记短,效率也很高。
- 筛选回收:首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。

10、GC日志分析

11、Minor GC与Full GC分别在什么时候发生
新生代内存(Eden区)不够用时候发生Minor GC也叫YGC。
Full GC发生情况:
- 老年代被写满
- 持久代被写满
- System.gc()被显示调用(只是会告诉需要GC,什么时候发生并不知道)
12、新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?
- 新生代回收器:Serial、ParNew、Parallel Scavenge
- 老年代回收器:Serial Old、Parallel Old、CMS
- 整堆回收器:G1
新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。标记整理很适合大对象,不会产生空间碎片。
13、栈上分配是什么意思
JVM允许将线程私有的对象分配在栈上,而不是分配在堆上。分配在栈上的好处是栈上分配不需要考虑垃圾回收,因为出栈的时候对象就顺带着一起出去了,没了,而不需要垃圾回收器的介入,从而提高系统性能。
补充1:对象逃逸。
逃逸的目的是判断对象的作用域是否有可能逃出函数体。例如下面的代码就显示了一个逃逸的对象:
private User user;
private void hello(){
user = new User();
}
对象实例 user 是类的成员变量,可以被任何线程访问,因此它属于逃逸对象。但如果我们将代码稍微改动一下,该对象就可以线程非逃逸的了。
private void hello(){
User user = new User();
}
可以看到 user 实例作用域只在 hello 函数中,不会被其他线程访问到,也不会访问。所以该 user 实例对象的作用域只在该函数中,因此它并未发生逃逸。对于这样的情况,虚拟机就有可能将其分配在栈上,而不在堆上。
补充2:TLAB,自行Google。
简单点说,就是将本来应该分配在堆中的对象,让其分配在线程私有的栈上。通过这种方式,减少垃圾回收的压力,提高虚拟机的运行效率。
14、简述下对象的分配规则
- 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次YGC。并将还活着的对象放到from/to区,若本次YGC后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。
- 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
- 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次YGC那么对象会进入Survivor区,之后每经过一次YGC那么对象的年龄加1,直到达到阀值对象进入老年区。默认阈值是15。可以通过
-XX:MaxTenuringThreshold参数来设置。 - 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。无需等到
-XX:MaxTenuringThreshold参数要求的年龄。
动态年龄判断是有歧义的,要想面试加分,必看这个
https://www.jianshu.com/p/989d3b06a49d
- 空间分配担保。每次进行YGC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行YGC,如果false则进行Full GC。

推荐好文
强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!
分享一套基于SpringBoot和Vue的企业级中后台开源项目,代码很规范!
能挣钱的,开源 SpringBoot 商城系统,功能超全,超漂亮!
搞定面试官:咱们从头到尾再说一次 Java 垃圾回收的更多相关文章
- 搞定面试官 - MySQL 中你知道如何计算一个索引的长度嘛?
大家好,我是程序员啊粥. 今天给大家分享一个我遇到过的比较少见的面试题,那就是 MySQL 中如何计算一个索引的长度. 说实话,我第一次遇到这个问题的时候想当然的以为索引长度就是我们建表时定义的字段长 ...
- 搞定面试官 - 你可以介绍一下在 MySQL 中,哪些情况下 索引会失效嘛?
大家好,我是程序员啊粥,前边给大家分享了 *MySQL InnoDB 索引模型 在 MySQL InnoDB 中,为什么 delete 删除数据之后表数据文件大小没有变 如何计算一个索引的长度 如何查 ...
- 从头到尾说一次 Java 垃圾回收,写得非常好!
Java技术栈 www.javastack.cn 优秀的Java技术公众号 作者:聂晓龙(花名:率鸽),阿里巴巴高级开发工程 ⬆️ 图片来源于网络 之前上学的时候有这个一个梗,说在食堂里吃饭,吃完把餐 ...
- 金三银四,2018最新iOS面试题,由它可以搞定面试官?
序言 这些资料,你一定会用到!我相信很多人都在说,iOS行业不好了,iOS现在行情越来越难了,失业的人比找工作的人还要多.失业即相当于转行,跳槽即相当于降低自己的身价.那么做iOS开发的你,你是否在时 ...
- 【搞定面试官】try中有return,finally还会执行吗?
本篇文章我们主要探讨 一下如果try {}语句中有return,这种情况下finally语句还会执行吗?其实JVM规范是对这种情况有特殊规定的,那我就先上代码吧! public class Final ...
- 【搞定面试官】- Synchronized如何实现同步?锁优化?(1)
前言 说起Java面试中最高频的知识点非多线程莫属.每每提起多线程都绕不过一个Java关键字--synchronized.我们都知道该关键字可以保证在同一时刻,只有一个线程可以执行某个方法或者某个代码 ...
- RabbitMQ:从入门到搞定面试官
安装 使用docker安装,注意要安装tag后缀为management的镜像(包含web管理插件),我这里使用的是rabbitmq:3.8-management 1. 拉取镜像 shell docke ...
- 【搞定面试官】谈谈你对JDK中Executor的理解?
## 前言 随着当今处理器计算能力愈发强大,可用的核心数量越来越多,各个应用对其实现更高吞吐量的需求的不断增长,多线程 API 变得非常流行.在此背景下,Java自JDK1.5 提供了自己的多线程框架 ...
- 【搞定面试官】你还在用Executors来创建线程池?会有什么问题呢?
前言 上文我们介绍了JDK中的线程池框架Executor.我们知道,只要需要创建线程的情况下,即使是在单线程模式下,我们也要尽量使用Executor.即: ExecutorService fixedT ...
随机推荐
- I am zhoukangyang!
我是 \(\texttt{zhoukangyang}\),一名来自浙江省,杭州市的初二菜鸡 \(\texttt{oier}\) . 洛谷zhoukangyang 很多东西因为太垃圾所以 了,要开 请洛 ...
- Codeforces Edu Round 58 A-E
A. Minimum Integer 如果\(d < l\),则\(d\)满足条件 否则,输出\(d * (r / d + 1)\)即可. #include <cstdio> #in ...
- 数组问题:a[i][j] 和 a[j][i] 有什么区别?
本文以一个简单的程序开头--数组赋值: int LEN = 10000; int[][] arr = new int[LEN][LEN]; for (int i = 0; i < LEN; i+ ...
- React中对render进行的小优化
react中state和props变化会造成render的重新渲染,有时候我们会在render函数中进行一些稍微复杂的逻辑运算 比如说,像下边这种 在props中将 industries引入,然后对其 ...
- gnuplot名词缩写
http://blog.163.com/yucheng_xiao/blog/static/7660019220141017114630822/ with 缩写成 w lt 是 linetype 的缩 ...
- Kubernetes 最佳安全实践指南
原文链接:https://fuckcloudnative.io/posts/security-best-practices-for-kubernetes-pods/ 对于大部分 Kubernetes ...
- js 传输数据 加密
一.js函数加密 escape()和unescape(); escape() 函数可对字符串进行编码,这样就可以在所有的计算机上读取该字符串. 加密 escape(string) unescape() ...
- 【Go语言绘图】图片的旋转
在上一篇中,我们了解了gg库的基本使用,包括调整大小.调整圆形参数.设置颜色.保存图片.加载图片和裁剪.这一篇我们来学习一下图片的旋转. 加载图片 首先,我们先来一张黄图. func TestRota ...
- 深入LUA脚本语言,让你彻底明白调试原理
这是道哥的第008篇原创 一.前言 上篇文章我们聊了gdb的底层调试机制,明白了gdb是利用操作系统提供的系统信号来调试目标程序的.很多朋友私下留言了,看到能帮助到大家,我心里还是很开心的,其实这也是 ...
- 第三章 Nacos Discovery--服务治理
之前我讲过 Nacos文章 的内容,想要深入了解的 朋友的话,可以去看看 ,我们继续承接上篇讲下去 --> 第二章 : 微服务环境搭建 3.1 服务治理介绍 先来思考一个问题 通过上一章的操作, ...