JVM:垃圾回收
概述
上一篇文章我们已经了解了 Java 的这几块内存区域。对于垃圾回收来说,针对或者关注的是 Java 堆这块区域。因为对于程序计数器、栈、本地方法栈来说,他们随线程而生,随线程而灭,所以这个区域的内存分配和回收可以看作具备确定性。对于方法区来说,分配完类相关信息后内存大小也基本确定了,加上在 JAVA8 中引入的元空间,所以这个部分也不用关注。
目的
对于堆中存储的那些不用的或者死掉的对象进行清理。
如何判断对象已死?
引用计数器
每当有一个地方引用它时,计数器的值就加一,如果引用失效时,计数器值减一。简单高效,但是没办法解决循环引用的问题。
可达性分析算法
这个算法的基本思路是通过一系列名为 GC ROOTS 的对象作为起始点,从这些节点开始向下搜索。当一个对象到 GC ROOTS 没有任何引用链时,则证明此对象不可用。可以作为 GC ROOTS 的对象包括下面几种?
- 方法里面引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法中引用的对象。
更为简单的理解方式为:
- 当前各线程执行方法中的局部变量(包括形参)引用的对象
- 已被加载的类的 static 域引用的对象
- 方法区中常量引用的对象
- JNI 引用
如何回收
当前的商业虚拟机的垃圾收集都采用分代垃圾回收的算法,这种算法并没有什么新的思想。只是根据对象的存活周期将不同的内存划分为几块。一般是把 Java 堆分为新生代
和老年代
,根据新生代和老年代存活时间的不同采取不同的算法,使虚拟机的 GC 效率提高了很多。新生代采用复制算法,老年代采用标记-清除或者标记-整理算法。
回收算法
标记-清除
算法分为
标记
和清除
两个阶段,首先要标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。缺点:效率问题,标记和清除过程的效率都不高,另外会有不连续的内存碎片。
复制
为了解决效率问题,复制算法出现了,将内存按容量划分为大小相等的两块,每次只使用其中的一块。清除后将活着的对象复制到另外一块上面。简单高效。现在的商业虚拟机都采用这种收集算法来回收
新生代
。因为新生代
中的对象98%都是朝生夕死的,所以并不需要按1:1划分内存,而是按8:1:1分为 Eden,survivor,survivor。每次只使用 Eden 和其中一块 Survivor。当回收时,将 Eden 和 Survivor 中还存活的对象一次性拷贝到另外一块 Survivor 上。8:1:1 是在复制算法的基础上改良而来的。
当 Survivor 空间不够用时,需要依赖
老年代
进行分配担保。比较适合需要清除的对象比较多的情况。
(图片来源于网络)
标记-整理
标记-整理算法和标记-清除算法的标记过程一样,后序有一个对内存进行整理的动作。和标记-整理算法一样,比较适合要清除对象不多的情况。复制算法在对象存活率较高时就要执行较多的复制操作,效率会变的很低。而且如果不想浪费 50% 的空间,就需要有额外的空间进行分配担保,以应对对象 100% 存活的极端情况,所以
老年代
一般不选复制算法,而选择标记-清除或者标记-整理算法。
新生代为什么按 8:1:1 分
因为新建出来的对象 98% 都是朝生夕死的,真正能在一轮 GC 之后留下的非常少,所以按照复制算法最初的 5:5 分是非常浪费空间的。所以将新生代分为 8:1:1 的 Eden survivor survivor。对象优先在 Eden 中分配,大多数情况下,对象在新生代 Eden 中分配,当 Eden 没有足够的空间进行分配时,虚拟机将发起一次 Minor GC
。
在 GC 开始的时候,对象只会存在于 Eden 区和名为 From 的 Survivor 区,名为 To 的 Survivor 区是空的。紧接着进行 GC,Eden 区中所有存活的对象都会被复制到 To,而在 From 区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过 -XX:MaxTenuringThreshold 来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到 To 区域。
经过这次 GC 后,Eden 区和 From 区已经被清空。这个时候,From 和 To 会交换他们的角色,也就是新的 To 就是上次 GC 前的 From ,新的 From 就是上次 GC 前的 To 。
另外还有两条对象分配策略是:
- 大对象直接进入老年代,大对象指的是那些需要连续内存空间的 Java 对象,最典型的大对象就是那种很长的字符串以及数组。直接进入老年代避免了大对象在 Eden 区和 Survivor 区之间发生大量的内存拷贝。
- 长期存活的对象将进入老年代,虚拟机给每个对象定义了一个对象年龄计数器,如果对象在 Eden 出生并经过一次 Minor GC 后仍然存活,并且能被 Survivor 容纳就会被移动到 Survivor 中,并且年龄增加 1。当年龄达到某个阙值(默认为 15)时,就会晋升到老年代。
对于新生代,需要选择速度比较快的垃圾回收算法,因为新生代的垃圾回收是频繁的。所以选择复制算法。
对于老年代,需要考虑的是空间,因为老年代占用了大部分堆内存,而且针对该部分的垃圾回收算法,需要考虑到这个区域的垃圾密度比较低。所以选择标记清除和标记整理算法。
看完了上面的知识点那么我们对什么时候进行 Minor GC,什么时候进行 Full GC 也就明白了。
Eden 满了进行 Minor GC,升到老年代的对象大于老年代剩余空间进行 Full GC。
在讲 Minor GC 和 Full GC
大家在通过这张图来了解一下堆内存的划分,堆内存分为 Eden,Survivor 和 Old 空间嘛,如下图所示:
在年轻代进行的内存回收称为 Minor GC,对老年代进行的内存回收称为 Major GC,而 Full GC 是对整个堆进行的。Major GC的速度一般会比Minor GC慢10倍以上。
下面列出几种进行 Full GC 的条件。
System.gc()
这个方法的调用是建议 JVM 进行 Full GC。
老年代空间不足
老年代只有新生代对象转入或者新建大对象的时候才会出现不足的情况,如果执行 Full GC 后空间仍然不足,那么则会抛出 OOM error 了。
永久代空间不足
方法区中存放的是一些 class 的信息,常量,静态变量等数据,当系统中要加载的类,反射的类或调用的方法较多时,永久代可能会被填满。在为配置为采用 CMS GC 的情况下也会执行 Full GC。
CMS GC promotion failed 和 concurrent mode failure
对于采用 CMS 进行老年代 GC 的程序而言,尤其要注意 GC 日志中是否有 promotion failed 和 concurrent mode failure 两种状态,这两种状态可能会触发 Full GC。
HandlePromotionFailure
在发生 Minor GC 之前,虚拟机会先检查老年代的最大可用连续空间是否大于新生代所有对象总空间,如果条件成立,那么 Minor GC 时可以确保安全的,否则不成立。虚拟机会查看 HandlePromotionFailure 设置是否允许担保。如果允许,会检察老年代的连续可用空间是否大于历次晋升的平均大小,如果大雨,尝试着进行一次 Minor GC,尽管有风险,如果小于或者 HandlePromotionFailure 设置为不允许冒险,则要进行一次 Full GC。
垃圾收集器
如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。下面介绍基于 HotSpot 虚拟机中的垃圾收集器。对于垃圾收集器,大家有个概念就可以了,没有必要去深究垃圾收集器的底层原理,当然如果有余力,了解底层原理当然是最好的。
Serial 收集器
最早的垃圾收集器,回收新生代,单线程。这里的单线程不仅仅说明它只会使用一个 CPU 或者一条收集线程去完成垃圾收集工作,重要的是,在进行垃圾收集时,必须暂停其他所有工作线程(Stop The World)。
ParNew 收集器
新生代垃圾回收,ParNew 收集器其实就是 Serial 收集器的多线程版本,在收集算法,Stop The World 和对象分配规则,回收策略上都与 Serial 相同。ParNew 在单核甚至双核 CPU 上的表现不如 Serial,更多的 CPU 才能体现他的优点。
Parallel Scanvnge 收集器
新生代垃圾回收,采用复制算法,关注吞吐量,不关注停顿时间。停顿时间越短就越适合需要于用户交互的程序,良好的响应速度能提升用户的体验。高吞吐量则可以最高效率地利用 CPU 时间,尽快完成运算任务,适合在后台运算而不需要太多交互的任务。
Serial Old 收集器
Serial 的老年代版本,单线程,使用标记-整理算法。
Parallel Old 收集器
Parallel New 的老年代版本,使用标记-整理算法。
CMS 收集器
CMS 是一种以获取最短回收停顿时间为目标的收集器,注重响应速度。基于标记-清除算法实现的。不同于其他收集器的全程 Stop The World,CMS 会有两次短暂的 Stop The World,垃圾收集和工作线程并发执行。整个过程分为 4 个步骤:
- 初始标记(Stop The World),标记 GC Roots 能关联到的对象。
- 并发标记
- 重新标记(Stop The World)
- 并发清除
G1 收集器
基于
标记-整理
实现。可以实现在基本不牺牲吞吐量的前提下完成低停顿的内存回收,新生代和老年代都可以回收。
CMS 收集器
目前 CMS 时最常用的收集器(JDK8 的应用一般都切换到了 G1 收集器了)。这个收集器和其他收集器的区别是不会全程 Stop-The-World,可以做到垃圾回收线程和应用程序线程同时运行。
对于许多程序来说,吞吐量不如响应时间来的重要。通常年轻代的垃圾收集不会停顿多长时间,但是老年代的垃圾回收,虽然不频繁,但是可能会导致长时间的停顿,尤其是当堆内存比较大的时候。为了解决这个问题,HotSpot 虚拟机提供了 CMS 收集器,也叫做低延时收集器。
新生代使用 CMS 收集器
和其他新生代并行收集器一样,并行清除-> stop-the-world->复制。
老年代使用 CMS 收集器
在老年代的垃圾收集过程中,大部分收集任务和应用是并发执行的。
CMS 会有一段小停顿 stop-the-world,叫做初始标记阶段,用于确定 GC Roots。然后是并发标记阶段,标记 GC Roots 可达的存活对象,由于这个阶段应用也在运行,所以并发标记结束后,并不能标记所有的存活对象,所以需要在此停顿,再次标记阶段,遍历在并发标记阶段应用程序修改的对象,这次停顿会比较长,会使用多线程并行执行来增加效率。
再次标记结束后,接下来进入并发清理阶段。
CMS 是唯一不进行压缩的收集器,就是它使用标记清除算法。
JVM:垃圾回收的更多相关文章
- JVM垃圾回收机制总结:调优方法
转载: JVM垃圾回收机制总结:调优方法 JVM 优化经验总结 JVM 垃圾回收器工作原理及使用实例介绍
- JVM内存管理和JVM垃圾回收机制
JVM内存管理和JVM垃圾回收机制(1) 这里向大家描述一下JVM学习笔记之JVM内存管理和JVM垃圾回收的概念,JVM内存结构由堆.栈.本地方法栈.方法区等部分组成,另外JVM分别对新生代和旧生代采 ...
- JDK分析工具&JVM垃圾回收(转)
转自:http://blog.163.com/itjin45@126/blog/static/10510751320144201519454/ 官方手册:http://docs.oracle.com/ ...
- 老李分享:jvm垃圾回收
老李分享:jvm垃圾回收 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq:908821478 ...
- jvm - 垃圾回收
jvm - 垃圾回收 注意 : 本系列文章为学习系列,部分内容会取自相关书籍或者网络资源,在文章中间和末尾处会有标注 垃圾回收的意义 它使得java程序员不再时时刻刻的关注内存管理方面的工作. 垃圾回 ...
- JVM垃圾回收机制概述
JVM垃圾回收机制概述 1.定义 是指JVM用于释放那些不再使用的对象所占用的内存. 2.方式 2.1引用计数(早期) 当引用程序创建引用以及引用超出范围时,JVM必须适当增减引用数.当某个对象的引用 ...
- Java虚拟机学习笔记——JVM垃圾回收机制
Java虚拟机学习笔记——JVM垃圾回收机制 Java垃圾回收基于虚拟机的自动内存管理机制,我们不需要为每一个对象进行释放内存,不容易发生内存泄漏和内存溢出问题. 但是自动内存管理机制不是万能药,我们 ...
- JVM基础系列第8讲:JVM 垃圾回收机制
在第 6 讲中我们说到 Java 虚拟机的内存结构,提到了这部分的规范其实是由<Java 虚拟机规范>指定的,每个 Java 虚拟机可能都有不同的实现.其实涉及到 Java 虚拟机的内存, ...
- Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法
在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...
- JVM垃圾回收算法解析
JVM垃圾回收算法解析 标记-清除算法 该算法为最基础的算法.它分为标记和清除两个阶段,首先标记出需要回收的对象,在标记结束后,统一回收.该算法存在两个问题:一是效率问题,标记和清除过程效率都不太高, ...
随机推荐
- ipmitool命令
1.remote access control powerIpmitool -I lanplus -H 192.168.0.10 -U username -P Password chassis pow ...
- vs2013中配置SQLite数据库
转载:https://maplefan.com/index.php/2019/08/14/visual-studio-2013%e9%85%8d%e7%bd%aesqlite3%e7%9a%84%e6 ...
- [CEOI 2004]锯木厂选址
Description 题库链接 从山顶上到山底下沿着一条直线种植了 \(n\) 棵老树.当地的政府决定把他们砍下来.为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂. 木材只能朝山下运.山脚下有一个 ...
- Day11 - I - 取石子游戏 HDU - 2516
1堆石子有n个,两人轮流取.先取者第1次可以取任意多个,但不能全部取完.以后每次取的石子数不能超过上次取子数的2倍.取完者胜.先取者负输出"Second win".先取者胜输出&q ...
- 3.使用Selenium模拟浏览器抓取淘宝商品美食信息
# 使用selenium+phantomJS模拟浏览器爬取淘宝商品信息 # 思路: # 第一步:利用selenium驱动浏览器,搜索商品信息,得到商品列表 # 第二步:分析商品页数,驱动浏览器翻页,并 ...
- ubuntu 解压命令全览
.tar解包:tar xvf FileName.tar打包:tar cvf FileName.tar DirName(注:tar是打包,不是压缩!)-------------------------- ...
- js——form表单验证
用js实现一个简易的表单验证 效果: 代码: <html> <head> <title>js校验form表单</title> <meta char ...
- div背景图片自适应
对背景图片设置属性:background-size:cover;可以实现背景图片适应div的大小. background-size有3个属性: auto:当使用该属性的时候,背景图片将保持100% 的 ...
- ASP.NET MVC Web项目中使用Log4Net记录日志,并按照日志类型分文件存储
1.创建MvcLog4Net项目 2.创建 空的MVC项目 3.项目创建完成的效果 4.选择项目,点击鼠标右键,在弹出菜单中选择“管理解决方案的 NuGet 程序包” 5. 在NuGet浏览界面: 点 ...
- 获取进程对应的UID登陆用户
getlogin获取登陆用户,守护进程调用出错 一个用户UID可以对应多个登陆名吗?貌似可以