聊聊面试中常问的GC机制
GC
中文直译垃圾回收,是一种回收内存空间避免内存泄漏的机制。当 JVM
内存紧张,通过执行 GC
有效回收内存,转而分配给新对象从而实现内存的再利用。 JVM
GC
机制虽然无需开发主动参与,减轻不少工作量,但是某些情况下,自动 GC
将会导致系统性能下降,响应变慢,所以这就需要我们提前了解掌握 GC
机制。当面对这种情况时,才能从容不迫的解决问题。另外 GC
机制也是 Java
面试高频考题,了解掌握 GC 是一项必备技能。
学习 GC
,首先我们解决三个问题:
- 什么是垃圾
- 在哪里回收垃圾
- 怎么回收垃圾
什么是垃圾#
我们先来看一段简单的代码。
上面代码通过将字符串对象转化成字节数组,然后写入本地文件。方法一旦开始执行,就将会在分配一定内存给新建的对象,然后将引用告诉了 str
, bytes
变量。等到方法执行完毕,方法内部局部变量紧接将就会被销毁。但是这样仅仅销毁了局部变量,却没有带走内存上这些实际的对象。这类不再起作用,没有被引用的对象,将其归类为垃圾。
在偌大的内存上存活着无数对象, GC
之前需要准确将这些对象标记出来,分为存活对象与垃圾对象。这个过程一旦少标记,那就只能等待下次 GC
标记,再回收,这样将会影响 GC 效率。另外决不能错标记,将正常存活对象标记为垃圾。一旦回收正常存活的对象,可能就会引起程序各种崩溃。
目前有两种算法可以用来标记:
- 引用计数法
- 可达性分析法
引用计数法#
引用计数法通过在对象头分配一个字段,用来存储该对象引用计数。一旦该对象被其他对象引用,计数加 1。如果这个引用失效,计数减 1。当引用计数值为 0 时,代表这个对象已不再被引用,可以被回收。
引用计数法
如上图所示,当 str
引用堆中对象时,计数值增加为 1。当 str
变为 null
时,既不再引用该对象,计数值减 1。此时该对象就可以被 GC
回收。
引用计数法只需要判断计数值,所以实现比较简单,这个过程也比较高效。但是存在一个很严重的问题,无法解决对象循环引用问题。
引用计数法-1
从上图可以看到, a
, b
不再引用堆中对象,导致计数减一。此时两个对象内部还存在互相引用,计数值不为 0,此时 GC
没办法回收该对象。
可达性分析法#
这个算法首先需要按照规则查找当前活跃的引用,将其称为 GC Roots
。接着将 GC Roots
作为根节点出发,遍历对象引用关系图,将可以遍历(可达)的对象标记为存活,其余对象当做无用对象。
可达性分析
注意这里是是 引用 ,而不是对象。
从上图可以看到,绿色对象虽然存在循环引用,但是由于这些对象不能被 GC Roots
遍历到,所以将会被回收。
可以被当做 GC Roots
活跃引用包括但不限于以下引用:
- 方法中局部变量
- 静态变量,常量
- JNI handles
- ....
在哪里回收垃圾#
还记得刚开始接触 Java
时,只知道堆栈,对象实例分配在堆中,方法中局部变量位于栈中。实际上 JVM
内存区域划分更加细致,分为:
- 堆
- 方法区
- 虚拟机栈
- 本地方法栈
- 程序计数器
JVM 运行时内存区域划分
如图所示,我们将内存划分为线程私有与线程共享的区域。方法区与堆都是线程共享的区域,这两部分占用 JVM
大部分内存,剩下三个小弟将会跟线程绑定,随着线程消亡,自动将会被 JVM
回收。
堆
堆应该是大家最熟悉的一块区域,几乎所有对象实例都将会在此出生,通常也是虚拟机上占用内存最大一块区域,简直就是 JVM
内存中的大哥大。堆内存内部也不是简简单单一块而已,目前将会根据分代算法,将堆分代,不同对象位于不同区域。这一点我们下文再详细了解。
方法区
方法区将会保存已被虚拟上加载的类信息、常量,静态变量,字节码等信息,堆上的对象正式通过方法区这些信息,才能正确创建出来。
栈
虚拟机栈栈由一系列栈帧组成,每个栈帧其实代表一个方法,栈帧中将会保存一个方法的局部变量表,方法出入口信息,操作栈等。每当调用一个方法,就将会把这个栈帧压入栈中,执行结束,出栈。
本地方法栈与虚拟机栈比较类似,最大区别在于,虚拟机栈执行的 Java
方法,而本地方法栈将会用来执行 Native
方法服务。下面方法就会在本地方法栈中执行。
<pre class="java" style="margin: 10px 0px; padding: 0px; white-space: pre !important; overflow-wrap: break-word; position: relative !important; color: rgb(49, 70, 89); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 300; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
Copy
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
</pre>
程序计数器
程序计算器可以说是这几块区域占用最小的一部分,但是功能却十分重要。Java 源代码通过编译变成字节码,然后被 JVM 载入运行之后,将会变成一条条指令,而程序计数器的工作就是告诉当前线程下一条需要执行指令。这样即使发生了线程切换,等待恢复的时候,当前线程依然知道接下去要执行的指令。
怎么回收#
目前主流 GC 算法主要分为三种:
- 标记-清除算法
- 复制算法
- 标记-整理算法
标记-清除算法#
这是一个最为基础也是最容易实现的算法,主要实现步骤分为两步:标记,清除。
- 标记:通过上述
GC Roots
标记出可达对象。 - 清除:清理 未标记对象 。
ps:这个图着实难画啊。。。。
可以看到经过这个算法回收之后,虽然堆空间被清理出来,但是也产生很多 空间碎片 。这就会导致一个新对象根据堆剩余容量计算,看起来是可以分配,但是实际分配过程,由于没有连续内存,导致虚拟机感知到内存不足,又不得不提前再次触发 GC
。
可能这里你就会有疑惑,为什么对象需要分配一块连续的内存?
这里引用一下 R 神@RednaxelaFX 答案。
另外这个算法还有一个不足:标记与清除效率比较低。这就竟会导致 GC
占用时间过长,影响正常程序使用。
复制算法#
为了解决上述效率问题,诞生复制算法。这个算法将可用内存分为两块,每次只使用其中一块,当这一块内存使用完毕,触发 GC
,将会把存活的对象依次复制到另外一块上,然后再把已使用过的内存一次性清理。
这个算法每次只需要操作一半内存, GC
回收之后也不存在任何空间碎片,新对象内存分配时只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。但是这个算法闲置一半内存空间,空间利用效率不高。
PS:复制算法以空间换时间,两者不可兼得
另外对象存活率也会影响复制算法效率。如果对象大部分都是朝生夕死,只需要移动少量存活对象,就能腾出大部分空间。反而如果对象存活率高,这就需要进行较多的复制操作,回收之后也并没有多余内存,这就可能导致频繁触发 GC
。
针对这种存活时间长的对象,就需要使用标记-整理算法。
标记-整理算法#
标记-整理算法可以说是标记-清除算法的改进版,改进了清除导致的空间碎片问题。这个算法分为两步:
GC Roots
虽然标记-整理算法解决了标记-清除算法空间碎片问题,也完整利用整个内存空间,但是这个算法问题效率并不高。相较于标记-清除算法,标记-整理算法多增加整理这一步,所以该算法效率还低于标记-清除算法。
分代收集算法#
从上面三种 GC
算法可以看到,并没有一种空间与时间效率都是比较完美的算法,所以只能做的是综合利用各种算法特点将其作用到不用的内存区域。
目前商业虚拟机根据对象存活周期不同划分内存区域,一般分为新生代,老年代。新对象一般情况都会优先分配在新生代,新生代对象若存活时间大于一定阈值之后,将会移到至老年代。新生代的对象都是短命鬼,老年代的对象都是长寿先生。
新生代每次 GC
之后都可以回收大批量对象,所以比较适合复制算法,只需要付出少量复制存活对象的成本。这里内存划分并没有按照 1:1 划分,默认将会按照 8:1:1 划分成 Eden
与两块 Survivor
空间。每次使用 Eden
与一块 Survivor
空间,这样我们只是闲置 10% 内存空间。不过我们每次回收并不能保证存活对象小于 10%,在这种情况下就需要依靠老年代的内存分配担保。当 Survivor
空间并不能保存剩余存活对象,就将这些对象通过分配担保进制移动至老年代。
老年代中对象存活率将会特别高,且没有额外空间进行分配担保,所以并不适合复制算法,所以需要使用标记-清除或标记-整理算法。
感谢你看完我的长篇大论,如果觉得对你有帮助的话,可以动动你敲代码的小手帮我点个赞。
或者也可以关注我的公众号【Java技术zhai】,不定期的技术干货内容分享,带你重新定义架构的魅力!
聊聊面试中常问的GC机制的更多相关文章
- 面试中常问的List去重问题,你都答对了吗?
面试中经常被问到的list如何去重,用来考察你对list数据结构,以及相关方法的掌握,体现你的java基础学的是否牢固. 我们大家都知道,set集合的特点就是没有重复的元素.如果集合中的数据类型是基本 ...
- 面试中常问的五种IO模型和BIO,NIO,AIO
一,五种IO模型: 一个IO操作可以分为两个步骤:发起IO请求和实际的IO操作例如:1.操作系统的一次写操作分为两步:第一步,将数据从用户空间拷贝到系统空间:第二步,从系统空间往网卡写.2.一次读操作 ...
- Java面试中常问的数据库方面问题
MySQL 为什么用自增列作为主键 如果我们定义了主键(PRIMARY KEY),那么InnoDB会选择主键作为聚集索引.如果没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的唯一索引作 ...
- Java面试中常问的Spring方面问题(涵盖七大方向共55道题,含答案)
1.一般问题 1.1. 不同版本的 Spring Framework 有哪些主要功能? VersionFeatureSpring 2.5发布于 2007 年.这是第一个支持注解的版本.Spring 3 ...
- 面试中常问的有关随机选取k个数的总结
1.在半径为1的圆中随机选取一点. 2.给定一个未知长度的整数流,如何随机选取一个数 3.给定一个数据流,其中包含无穷尽的搜索关键字(比如,人们在谷歌搜索时不断输入的关键字).如何才能从这个无穷尽的流 ...
- php高级研发或架构师必了解---很多问题面试中常问到!
一.mysql相关知识 1. mysql优化方式 MYSQL 优化常用方法 mysql 性能优化方案 2.如何分库分表 ...
- 【搞定 Java 并发面试】面试最常问的 Java 并发进阶常见面试题总结!
本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.觉得内容不错 ...
- Android相关面试题---面试官常问问题
版权声明:本文为寻梦-finddreams原创文章,请关注: http://blog.csdn.net/finddreams/article/details/44513579 一般的面试流程是笔试完就 ...
- 【搞定 Java 并发面试】面试最常问的 Java 并发基础常见面试题总结!
本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.欢迎 Sta ...
随机推荐
- Mybatis面试问题集锦
1.#{}和${}的区别是什么? 答:mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值: mybatis在处理 $ { } 时,就 ...
- JavaScript DOM–元素操作
获取元素 根据 ID 获取元素 语法: document.getElementById(id) <div id='time'>2020-01-09</div> <scri ...
- DOM盒模型和位置 client offset scroll 和滚动的关系
DOM盒模型和位置 client offset scroll 和滚动的关系 概览 在dom里面有几个描述盒子位置信息的值, pading border margin width height clie ...
- Java上传图片到Ftp,包含上传后文件大小为0的问题和Properties配置文件的读取
准备工作:需要使用coomos-net jar包.下载地址 一. 上传图片到FTP,文件大小为0的问题,解决:将ftp模式修改为Passive模式就可以了. //将ftp模式修改为Passive模式 ...
- SpringBoot 各层之间的关系
SpringBoot 各层之间的关系 SpringBoot 分为四层:controller层.service层.dao层.entity层. entity层:和 model 层一样,存放的是实体类,属 ...
- python夜记
关于多行字符串(multi-line strings)的表现: Python列表是基于0索引的.(zero-indexed). 晌午起床来嘞,再来些笔记: Treasures 1: 列表方法rever ...
- Visual Studio 配置 fftw 库
前提条件: 1.vs 2010 +(我的是2019): 2.下载 fftw. 先将vs 的 msvc 编译器的位置添加到path,一般在下面这个目录下: Microsoft Visual Studio ...
- 如和针对CPU时间百分比,Mem使用bytes,以及Network RecvBytes/SendBytes指标性能压测数据可视化
设计思路:通过jmeter5.1压测获取cpu,Mem,Network的压测指标数据利用pandas+openpyxl进行数据可视化: 涉及添加jar包:下载地址:https://files.cnbl ...
- Yii2 框架下 session跨域共享互通
在项目实施过程中,往往把一个大项目进行分拆成几个独立的项目,项目用完全独立的域名和文件,可以放到不同的服务器上的独立分项目. 几个子项目共用一个登录点. 原理简单来说就是服务端session 共享, ...
- 解决android studio 3.5.3版本的下载安装问题 2.5日
有些好笑,我安装了android studio3.5版本的软件安装了四天,在刚开始的时候,同学们安装软件应该是一趟就下来了,但是我的软件一直卡在了 ERROR: Unable to find vali ...