JVM学习(二):垃圾回收
我刚工作的时候问一个前辈,我们能针对JVM做出什么样的优化。前辈说,我们系统现在的性能并不需要调优,用默认的配置就能满足现在的需求了。我又问,那你为什么要看JVM相关的书呢?前辈微微一笑,悠悠地来了句,为了面试。
玩笑归玩笑,不过事实上确实萌新程序员确实不需要在实际工作中进行JVM调优。一方面Java虚拟机的默认配置足够我们使用,另一方面功能强大的IDE让我们在编写代码的时候基本不需要考虑虚拟机的问题。那么抛开应付面试,我们为什么要学习JVM呢?
在我看来,学习无论从广度还是从深度上都应当超前于工作中的应用。就像科研一样,理论永远比实践走得要远(想想爱因斯坦1916年预测的引力波到2016年才被探测到)。这里不多说,增加知识储备肯定是好的。闲话少说,我们开始聊垃圾回收。
上一篇说到Java程序员不需要手动垃圾回收(GC),这是因为Java虚拟机已经帮我们自动完成了。那么为什么Java虚拟机知道哪个对象需要回收,何时需要回收,回收后如何重新分配呢?
对于程序计数器、虚拟机栈、本地方法栈这样的非线程共享的内存,它们的内存跟线程的生命周期相同,我们不用考虑这些内存的回收。此处垃圾回收指的是线程共享区域(堆和方法区)的内存回收。
哪些内存需要回收?
引用计数法
引用计数法是古老的辨别对象存亡的算法。引用计数法的方法是为每个对象添加一个引用计数器,统计引用对象的次数,如果引用次数为0,那么此对象则判断为死亡。
这样的判断方法有一个最大的弊端是,此种方法没有解决对象循环依赖的问题。例如对象A和对象B,两个相互引用。此时两个对象的引用计数器都不为0,那么这两个对象将永远不会被回收,这样就容易引起内存泄露问题。
可达性分析
Java虚拟机通常采用的是可达性分析算法,这个算法是将一系列GC Roots作为初始的存活对象集合。然后从这个集合出发将所有该集合引用到的对象加入到存活对象集合中。最终,未被加入到该集合的对象将被判定为死亡对象。如果你对判定算法感兴趣,可以参考我之前写的一篇文章如何判断一个图中是否存在环路。
不过此方法也不是完美的解决办法。在多线程的环境下,假如一个在存活对象集合中的对象,在运行过程中其引用被删除,我们认为此对象应该被回收掉。但是可达性分析尚未完成,这时候就会出现漏报。漏报不会出现什么问题,因为下次可达性分析完成时就可以回收掉这个对象的内存。另一种情况是误报,一个对象没有在存活对象集合中,在被回收之前却被其他对象引用到了,这时候我们认为此对象是存活的。然而这时候Java虚拟机可能会回收掉这个对象,这就会引起很严重的问题。因此在合适的时机进行垃圾回收是很重要的。
GC Roots是指堆外指向堆内的引用,一般有如下几种:
- Java方法栈帧中的局部变量
- 已加载类的静态变量
- JNI handles
已启动尚未停止的Java线程
何时进行垃圾回收?
Stop-The-World
为了避免上述漏报误报的问题,在Java虚拟机中,垃圾回收是在某个时刻进行的,在此期间不会出现引用关系的变化,这段时间也叫做 Stop-the-world。在Stop-the-world期间,将会停止其他非垃圾回收线程的工作,直到完成垃圾回收。因此垃圾回收造成了停顿时间(GC Pause)。
在Java虚拟机运行的过程中,程序的引用更新是很难预期到,因此Stop-the-world并不是有均匀间隔时间的,而是通过安全点(Safepoint)检测机制来实现的。安全点检测是为了找出Java虚拟机堆栈不会更新的稳定状态,当所有线程都达到安全点的时候,才会允许Stop-the-world线程进行垃圾回收。
线程的稳定状态一般有:JNI执行本地代码、解释执行字节码、执行即时编译器生成的机器码和线程阻塞。
Java Native方法不会去调用Java对象或者调用Java方法,因此Java虚拟机堆栈不会出现变化,属于安全点。
解释执行的字节码,字节码与字节码之间皆可作为安全点。
即时编译生成的机器码是直接运行在机器上的,这部分的运行不受虚拟机的控制。因此生成机器码的时候要插入安全点检测,避免长时间等待导致的停顿。HotSpot虚拟机的做法是在生成代码的方法出口和非计数循环的循环回边处加入安全点检测。
阻塞的线程还处于Java虚拟机的线程管理之中,因此是安全点。
垃圾回收的方式
清除
最简单粗暴的方式就是清除(Sweep)。也就是把未在存活集合中的对象内存全部回收,记录在一个空闲内存列表。当新建对象的时候就从空闲内里列表中划去所需要的内存。

由于Java堆中对象必须是连续的内存,因此这个方法有两个显而易见的缺点。一个是分配效率低下,每次分配内存时都要遍历空闲内存列表,找到符合大小的空闲内存块。第二个缺点是容易造成内存碎片,存在一种情况是内存空间足够,但是没有足够的连续内存为新的对象分配(这个可以参考操作系统中的内存管理,是相同的道理)。
压缩
压缩指的是对于存活对象的内存,移动到内存的起始点位置。可以想象每次回收的时候都需要移动内存,所以这种方法的性能开销很大。

复制
复制是把内存划分为两等分,分别用两个指针from和to维护。只用from指针指向的内存区域来分配内存。垃圾回收时,把存活的对象复制到to指向的内存区域中,然后交换from和to指向的内容。这种方法解决了内存碎片问题,缺点是堆空间的使用率大大降低(只能用一半)。

总结
本篇讲了垃圾回收的基本原理。先是判断何种对象要回收,主要有引用计数法和可达性分析法。其次,讲了什么时候进行垃圾回收,主要有Stop-the-world以及安全点检测。最后是垃圾回收的方式,分别是清除、压缩和复制。其中清除会产生内存碎片,压缩机制有较大的算法开销,复制机制能解决内存碎片,但是堆空间使用率低下。
参考文章
极客时间——郑雨迪:深入拆解Java虚拟机
JVM学习(二):垃圾回收的更多相关文章
- JVM总括二-垃圾回收:GC Roots、回收算法、回收器
JVM总括二-垃圾回收:GC Roots.回收算法.回收器 目录:JVM总括:目录 一.判断对象是否存活 为了判断对象是否存活引入GC Roots,如果一个对象与GC Roots没有直接或间接的引用关 ...
- JVM学习笔记——垃圾回收篇
JVM学习笔记--垃圾回收篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的垃圾回收部分 我们会分为以下几部分进行介绍: 判断垃圾回收对象 垃圾回收算法 分代垃圾回收 垃圾回收器 ...
- JVM学习--(四)垃圾回收算法
我们都知道java语言与C语言最大的区别就是内存自动回收,那么JVM是怎么控制内存回收的,这篇文章将介绍JVM垃圾回收的几种算法,从而了解内存回收的基本原理. stop the world 在介绍垃圾 ...
- JVM学习记录-垃圾回收算法
简述 因为各个平台的虚拟机的垃圾收集器的实现各有不同,所以只介绍几个常见的垃圾收集算法. JVM中常见的垃圾收集算法有以下四种: 标记-清除算法(Mark-Sweep). 复制算法(Copying). ...
- JVM学习——G1垃圾回收器(学习过程)
JVM学习--G1垃圾回收器 把这个跨时代的垃圾回收器的笔记独立出来. 新生代:适用复制算法 老年代:适用标记清除.标记整理算法 二娃本来看G1的时候觉得比较枯燥,但是后来总结完之后告诉我说,一定要慢 ...
- 【转载】Java性能优化之JVM GC(垃圾回收机制)
文章来源:https://zhuanlan.zhihu.com/p/25539690 Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我 ...
- 浅谈jvm中的垃圾回收策略
下面小编就为大家带来一篇浅谈jvm中的垃圾回收策略.小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧 java和C#中的内存的分配和释放都是由虚拟机自动管理的,此前我已 ...
- Java性能优化之JVM GC(垃圾回收机制)
Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我们需要记住一个单词:stop-the-world .它会在任何一种GC算法中发生.st ...
- JVM——GC(垃圾回收)算法
一.垃圾回收的基本概念 垃圾回收(GC,Garbage Collection),指内存中不会再被使用的对象清理掉. 垃圾回收有很多种算法:如引用计数法.标记压缩法.复制算法.分代/分区的思想 二.垃圾 ...
- JVM学习总结二——垃圾回收算法
昨天总结了JVM内存分区相关的知识,这次我们将来了解下JVM的另一个核心知识点——垃圾回收算法.这一部分其实并不太难,如果对操作系统的内存处理算法有所了解,那么这部分算法其实只看名字就能明白,两者在原 ...
随机推荐
- js五彩小球
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- C#Application:Exit与ExitThread 解释
Application.Exit(); 方法停止在所有线程上运行的所有消息循环,并关闭应用程序的所有窗口 Application.ExitThread 方法 退出当前线程上的消息循环,并关闭该线程上的 ...
- [PySpark] Build R&D environment
开发环境 基本操作 Ref:Spark的环境搭建 一.启动集群 先启动hadoop,再启动spark,查看启动后的状态:http://node-master:8080 start-all.sh sta ...
- 忘记mysql或mariadb数据库的密码之解决方案
一.实验环境 CentOS Linux release 7.5.1804 (Core) mysql Ver 15.1 Distrib 5.5.56-MariaDB, for Linux (x86_6 ...
- docker数据管理(2)
一.docker存储资源类型 docker两种存储资源类型 用户在使用 Docker 的过程中,势必需要查看容器内应用产生的数据,或者需要将容器内数据进行备份,甚至多个容器之间进行数据共享,这必然会涉 ...
- SPRINGMVC 视图介绍
SpringMVC视图解析器 前言 在前一篇博客中讲了SpringMVC的Controller控制器,在这篇博客中将接着介绍一下SpringMVC视图解析器.当我们对SpringMVC控制的资源发起请 ...
- JavaScript之参数传递方式
前言 nodejs项目中遇到此问题了,具体啥需求暂时不说~ 本博文,关于理论部分,主要是摘抄"推荐文献"第一篇:关于实验部分是看该博文之前做的,两者无干系. [结论]对于普通函数, ...
- kali破解wifi密码
开始 1.选择合适的网卡,有些网卡kali识别不了,我用的网卡信息 2.网卡开启监听模式 3.查看监听模式是否开启成功(网卡名称变成wlan0mon说明已经开启成功) 4.输入“airodump-ng ...
- 2019Java常见面试上
一.开场白简单的介绍一下自己的工作经历与职责,在校或者工作中主要的工作内容,主要负责的内容:(你的信息一清二白的写在简历上,能答出来的最好写在上面,模棱两可不是很清楚的最好不要写,否则会被问的很尴尬) ...
- VIM 介绍
gedit a.txt 是一个图形界面的文本编辑器. 需要安装图形界面才会有. nano a.txt 也是一样的 vi 是一种文本界面的编辑器. vim 是 vimsual interfa ...