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的另一个核心知识点——垃圾回收算法.这一部分其实并不太难,如果对操作系统的内存处理算法有所了解,那么这部分算法其实只看名字就能明白,两者在原 ...
随机推荐
- JVM学习笔记之认识JDK(一)
1. HotSpot VM: HotSpot VM是Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机. 什么是HotSpot VM & 深入理解Java虚拟机 ...
- C#.net winform skin 皮肤大全
C#.net winform skin 皮肤大全 1. 东日IrisSkin IrisSkin 共有两个版本,一个是IrisSkin.dll 用于.Net Framework1.0/1.1 和Iris ...
- Linux安装配置JDK1.8
JDK1.8 链接:http://pan.baidu.com/s/1nvGBzdR 密码:ziqb 1 在/usr/local 文件夹下新建一个文件夹software ,将JDK放到此文件夹中 ...
- Spring Boot启动的报错 Stopping service [Tomcat]
我遇到的问题是项目中使用java_websocket的WebSocketClient,由于性能要求,需要再Controller直接继承WebSocketClient, 在项目启动过程中调试进入spri ...
- 关键字 using语句 大神的神扯
using 是非托管资源: 解析:在C#应用托管到.NET Framework.但是他可以释放非托管资源. using 关键字有两个作用: 1:作为关键字,using可以导入命名空间 2:座位C#语句 ...
- git学习教程二之远程仓库学习
首先你需要注册一个github用户名,我的github账户是:1654218052@qq.com 由于本地的git仓库和github的仓库是通过SSH加密的,所以我们还需要设置一点东西哦 第1步:创建 ...
- Java基础知识常见面试题汇总第一篇
[Java面试题系列]:Java基础知识常见面试题汇总 第一篇 文中面试题从茫茫网海中精心筛选,如有错误,欢迎指正! 1.前言 参加过社招的同学都了解,进入一家公司面试开发岗位时,填写完个人信息后 ...
- OpenResty + Lua访问Redis,实现高并发访问时的毫秒级响应打回
一.lua中redis的配置依赖: 1.OpenResty的lua访问redis的插件:https://github.com/openresty/lua-resty-redis 二.下载后,导入对应的 ...
- *【Python】【demo实验26】【练习实例】【递归方法的使用】
原题: 利用递归方法求5! 原题给出的解答: #!/usr/bin/python # encoding=utf-8 # -*- coding: UTF-8 -*- # 利用递归方法求5! def fa ...
- CentOS7之yum仓库配置
操作系统版本:CentOS Linux release 7.2.1511 (Core) Yum软件版本:yum-3.4.3-132.el7.centos.0.1.noarch Yum主配置文件:/ ...