我所理解的JVM(六):内存回收
2019独角兽企业重金招聘Python工程师标准>>>
Java的一大特点就是虚拟机本身拥有垃圾回收机制,用户在编程过程中不再需要考虑垃圾回收的事情。所有的对象的创建都是在堆空间中,垃圾回收主要针对堆空间。
Java是面向对象的,程序运行中不断有对象被创建和使用。绝大部分对象在使用后,就完成了历史使命,等待GC回收。
虚拟机进行垃圾回收的第一步是需要确定堆空间中哪些对象可以被回收,第二步是对对象所在的空间进行清理。根据不同的算法会考虑在清理完成后进行(压缩)整理,防止内存碎片。
判断对象是否可以被回收的依据:
- 引用计数法
引用计数不好解决对象之间互相引用的场景,主流的虚拟机都没有采用这种方式定位。 - 可达性分析
可达性分析是指从“GC Roots”的对象作为起始点,向下搜索所有引用的对象。当一个对象的根引用不是GC Roots的时候,就说明该对象可以被回收了。
Java虚拟机中,GC Roots中的对象包括下面几种:
- 虚拟机栈中的引用;
- 方法去中的类静态变量的引用;
- 方法去中常量的引用;
- 本地方法栈中的引用
这里提到了引用的概念。java 1.2之后增丰富了引用的类别:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。
- 强引用:Object a = new Object() 属于强引用。
- 软引用:用来描述一些有用但并非必须存在的对象。对应java中的SoftReference类。
- 弱引用:非必须存在,比软引用更弱,只要发生GC必然回收。对应java中的WeakReference类。
- 虚引用:最弱的引用,无法通过虚引用获取对象。虚引用的唯一作用实在这个对象被垃圾回收时收到一个系统通知。对应java中的PhantomReference类。
GC回收对象时,如果对象覆盖了finalize()方法并且还没执行,则将该对象进行一次标记,放到一个叫F-Queue中。没有覆盖finalize()或者已经执行过了finalize(),则该对象直接回收。虚拟机在售后会启动单独的线程在执行F-Queue中的对象的finalize(),目的是防止finalize()过于耗时影响GC时间。如果在finalize()期间该对象重新有了引用,则该对象逃脱GC,否则进行二次标记等待回收。
采用可达性分析确定哪些对象可以被回收的过程,HosPot虚拟机采用三色标记算法,被标记的对象组成的路径成为引用链。标记之后就是对对象所处空间进行回收。
HosPot虚拟机中的垃圾收集的算法:
- 标记-清除:速度块。缺点是内存变成了不连续空间
- 标记-整理:标记清除后对不连续的空间进行整理,使得空间变得连续。缺点是整理空间很耗时
- 复制:将内存空间1:1分为两部分,只是用其中1部分,标记清除后将剩余对象转移到另一部分。缺点是比较浪费空间。
- 分代收集:将内存分为不同区域,针对不同区域的特点来确定采用的上述算法中的哪一个。
- 增量收集:在应用进行的同时进行垃圾回收,其实就是并发。
HosPot将堆空间分为年轻代(YongGeneration)和年老代(Old/TenuredGeneration)。年轻代又分为1个Eden区和2个Survivor区,默认是8:1:1。对象在堆空间内的流转过程:
- 新建对象优先在Eden区。当Eden区空间不足的时候,进行MinorGC,把存活对象放在SurvivorA区,Eden区清空。
- 当Eden区再次空间不足时,Eden区和SurivivorA区同时进行MinorGC,把存活对象放在SurvivorB区,Eden区清空。
- 重复上述过程
- 当MinorGC后Eden区和Survivor区存活的对象空间比另外一个Survivor空间还要大时,原Survivor中的对象进入Old区,Eden中的对象进入Survivor区。
- 如果MinorGC后Survivor区中存活的对象已经反复存活超过一定次数了,直接接入Old区。默认15次。
- 当Old区也出现空间不足时,进行Major GC,又称Full GC,对Old区中的对象进行回收。
由于处理器有多核多线程和单核单线程之分,用来执行垃圾清除的垃圾收集器也有多种:
- 年轻代可以选择的有: Serial(串行)、ParNew、ParallelScavenge
- 年老代可以选择的有:CMS、Serial Old、Parallel Old
- 1.7之后可以选择G1收集器应用于整个堆空间
几个概念:
- 并行(Parallel):多个垃圾收集线程同时执行,用户线程仍然处于等待状态
- 并发(Concurrent):指用户线程与垃圾收集线程同时执行
- 串行(Serial):单线程执行
由于年轻代和年老代的特点,除G1外,年轻代收集器均采用复制算法,年老代收集器均采用标记整理算法。(注:CMS单次是标记清除,一定次数后或者空间不足时触发(压缩)整理)
Serial收集器:
单线程执行,STW的时间比较长,一般应用与Client模式
设置参数: "-XX:+UseSerialGC":添加该参数来显式的使用串行垃圾收集器;
ParNew收集器:
多线程执行,行为、特点和Serial收集器一样。因为除Serial外,目前只有它能与CMS收集器配合工作。
设置参数:
"-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器;
"-XX:+UseParNewGC":强制指定使用ParNew;
"-XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
Parallel Scavenge收集器:
Parallel Scavenge垃圾收集器因为与吞吐量关系密切,也称为吞吐量收集器(Throughput Collector)。CMS、Parallel等收集器的目的是尽可能的减少用户线程等待时间,即使一段时间内发生多次GC。而Parallel Scavenge收集器的目的是尽可能提高虚拟机的吞吐量,尽可能少发生GC,哪怕一个GC的时间比较长。一般应用于需要后台计算的场景,比如批量处理,科学计算等。
设置参数:
"-XX:MaxGCPauseMillis"控制最大垃圾收集停顿时间,大于0的毫秒数;
"-XX:GCTimeRatio"设置垃圾收集时间占总时间的比率,0<n<100的整数;相当于设置吞吐量大小;
"-XX:+UseAdptiveSizePolicy"开启这个参数后,JVM会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomiscs);
Serial Old收集器:
Serial收集器的年老代版,主要应用与Client模式。
Serial Old收集器在Server模式可以作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
Parallel Old收集器:
ParNew收集器的年老代版。二者均为多线程并行。
设置参数: "-XX:+UseParallelOldGC":指定使用Parallel Old收集器;
CMS收集器(Concurrent Mark Sweep)
并发标记清理(Concurrent Mark Sweep,CMS)收集器也称为并发低停顿收集器(Concurrent Low Pause Collector)或低延迟(low-latency)垃圾收集器;并发收集器,用户等待时间短,应用于WEB等Server端。标记整理会产生内存碎片。
设置参数:
"-XX:+UseConcMarkSweepGC":指定使用CMS收集器
"-XX:CMSInitiatingOccupancyFraction":设置CMS预留内存空间;JDK1.5默认值为68%;JDK1.6默认值为92%;
运作过程:
- 初始标记(CMS initial mark):只标记GC Roots中直接应用的对象(需要STW)
- 并发标记(CMS concurrent mark):找出堆中所有初始标记的对象的间接引用链上的对象(与用户线程并发)
- 重新标记(CMS remark):修正并发标记期间由用户线程引起的那一部分对象的标记记录(需要STW)
- 并发清除(CMS concurrent sweep)
CMS的缺点:
- 对CPU资源敏感:CMS的默认收集线程数量是=(CPU数量+3)/4,即CPU线程数较少是不推荐使用。
- 预留空间问题:需要为并发清除时用户线程新产生的垃圾预留一部分空间,导致出发FullGC的阀值比其他收集器要小。如果CMS预留内存空间无法满足程序需要,JVM启用后备预案:临时启用Serail Old收集器,而导致另一次Full GC的产生; 即当老年代的空间使用率达到68%时,会执行一次CMS回收。如果应用程序的内存使用率增长很快,在CMS的执行过程中,已经出现了内存不足的情况,此时,CMS回收将会失败,JVM将启动老年代串行收集器进行垃圾回收。如果这样,应用程序将完全中断,直到垃圾收集完成,这是,应用程序的停顿时间可能很长。
- 产生内存碎片:CMS基于"标记-清除"算法,清除后不进行压缩操作;当虚拟机无法找打足够连续的内存分配大对象时,会提前出发一次FullGC。解决办法:1,"-XX:+UseCMSCompactAtFullCollection",发生上述场景时不进行FullGC,而是开启内存碎片的合并整理过程;合并整理过程无法并发,停顿时间会变长;默认是开启的。2,"-XX:+CMSFullGCsBeforeCompaction"设置执行多少次不压缩的Full GC后,来一次压缩整理;默认是0。
- 由于空间不再连续,CMS需要使用可用"空闲列表"内存分配方式,这比简单实用"碰撞指针"分配内存消耗大;
G1收集器
特点:
- 并行与并发,目的是充分利用多CPU多核多线程环境下的硬件优势,缩短STW的时间。
- 管理整个GC堆(整个堆划分为多个大小相等的独立区域(Region),仍然保留分代概念)
- 空间整合:整体上看采用标记-整理算法,从每个区域看是复制算法,不产生内存碎片
- 可预测的停顿:除了追求低停顿外,还会建立可预测的停顿时间模型。开发者可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒;
应用场景:服务端,有大内存和多处理多核多线程,要求低GC延迟,堆的内存分配数量大 的场景
设置参数
"-XX:+UseG1GC":指定使用G1收集器;
"-XX:InitiatingHeapOccupancyPercent":当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45;
"-XX:MaxGCPauseMillis":为G1设置暂停时间目标,默认值为200毫秒;
"-XX:G1HeapRegionSize":设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个
可预测停顿的原理:
G1收集器之所以能建立可预测的停顿模型,是因为它可以有计划的避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(每次回收所获得的空间大小以及回收所需要的时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的由来)之中使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在游侠时间诶可以获取尽可能高的手机效率。
一个对象被不同区域引用的问题:
由于有众多的region区域,当一个对象被其他区域饮用时,在做可达性判断确定对象是否存活的时候,岂不是要扫描整个Java堆?这个问题并非只有G1有,只是G1有众多的区域显得问题比较突出。 解决办法:
无论是G1还是其他分代收集器,JVM都采用RememberedSet来避免全堆扫描。G1中每个Region都有一个与之对应的RememberedSet,当虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个WriteBarrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中(在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象)。如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的RememberedSet中。当进行内存回首时,在GC根节点的枚举范围中加入RememberedSet即可保证不对全堆扫描也不会有遗漏。
如果不计算维护RememberedSet的操作,G1收集器的运作过程:
- 初始标记(initial marking)
- 并发标记(concurrent marking)
- 最终标记(final marking)
- 筛选回收(live data conuting and evacuation)
G1收集器要求堆空间在6G以上才使用。
方法区的回收针对类型的卸载。类型的卸载需要满足3个条件:
- 该类的类加载器已经被回收;
- 该类没有任何实例对象;
- 该类的clss对象没有被任何地方引用。
动态代理,动态生成JSP等频繁自定义ClassLoader生成class字节码的场景使得虚拟机必须具备类卸载的功能。何时对该空间进行回收由GC判断。
Jdk1.8之后字符串常量池位于堆空间单独一块,字符串常量池空间也是可以被回收的,只需要判断该字符串是否仍有引用就可以确定是否可以回收。何时对该空间进行回收由GC判断。
用户线程状态
只要发生GC,就会导致Stop The World。当发生Stop The World的时候,虚拟机中的用户线程是什么状态呢?两种:
1. 安全点(针对处于running状态的线程)
因为程序在运行时不是所有的时候停下来都是安全的(比如运算进行到一半,数据值是一个脏数据),安全点是所有线程在”Stop the world”时到达的一个安全的点。由于堆中的对象庞大,若为每个对象都生成OopMap数据结构将占用大量空间,所以HotSpot只在”安全点“上生成这些数据结构。同时程序并非在所有位置都可以停止,而是只能在安全点才会停止。所以”安全点“还会影响到GC的及时性。”安全点“选取时不能太多,以造成空间上的支出,也不能太少,以让GC等待较长时间。 对于”安全点“还有另外一个需要考虑的问题是,当GC发生时如何让所有线程都跑到最近的”安全点“,一般都两种方案。抢先式中断,抢先式中断是虚拟机将所有线程停下来,然后一一检查是否已达安全点,若没有到达安全点则恢复线程让其到达最近的”安全点”,此种方式几乎没有虚拟机使用。主动式中断的思路是中断发生时,虚拟机在所有的线程上设置一个标志,线程自行检查该标志,然后进入“安全点”。检查标志的地方和安全点是重合的。
2. 安全区域(针对处于非running状态的线程)
上面没有解决的问题是当一个线程处于休眠,或未分配CPU时钟,比如sleep或blocked状态时,他就无法走到安全点去挂起自己。对于这种情况,就需要通过安全区域来解决,安全区域是一个程序不会更改自己引用的区间,在这个区域的任何地方开始GC都是安全的,可以被认为是扩展了的安全点。当线程执行到SafeRegion的代码时,他就会标记自己已经进入Safe Region,此时发生GC,将不会管这些线程。快要离开安全区域的时候他就会去检查当前的状态,如果是在GC中,那边他就会挂起等待可以离开Safe Region的信号,否则他就可以继续运行。
参考资料:
- 《深入理解Java虚拟机》
- 解析JDK 7的Garbage-First收集器
- 相对全面的GC总结
- HotSpot算法及垃圾收集器简介
- JVM三色标记
- 7种垃圾收集器主要特点
- 说说GC标记阶段的一些事
- 理解进入safepoint时如何让Java线程全部阻塞
- Java垃圾回收精粹
转载于:https://my.oschina.net/u/3466682/blog/1573617
我所理解的JVM(六):内存回收相关推荐
- Java技术专题之JVM逻辑内存回收机制研究图解版
一.引言 JVM虚拟机内存回收机曾迷惑了不少人,文本从JVM实现机制的角度揭示JVM内存回收的原理和机制. 一.Java平台逻辑架构 二.JVM物理结构 通过从JVM物理结构图我们可以看到: 1.JV ...
- jvm垃圾内存回收问题
CrashReport系统在游戏内测当天出现了异常情况JVM僵死,通过top -p <PID> -H 结合jstack(jstack -m -l pid)查看,发现是VM Thread线程 ...
- java垃圾回收菜鸟_java程序员不懂JVM内存回收,两年后也是个菜鸟
java程序员不懂JVM内存回收,两年后也是个菜鸟 在学java程序员的时候,如果你还不懂JVM内存回收,那么你就只能是个很一般的程序员菜鸟了,那么什么是JVM内存回收呢?今天我们就来学习,都还不深入 ...
- 怎么把虚拟机清空内存_深入理解java虚拟机1——内存管理机制与回收机制
文中涉及JVM底层知识大多来自<深入理解Java虚拟机>第2版,内容枯燥乏味,如果看,认真看.跟着撸一遍也可以受益良多. 1.JVM:是运行在操作系统之上的,它与硬件没有直接的交互. 运行 ...
- jvm垃圾回收机制_深入理解JVM的垃圾回收机制
如何判断对象已"死" Java堆中存放着几乎所有的对象实例,垃圾回收器在堆进行垃圾回收前,首先要判断这些对象那些还存活,那些已经"死去".判断对象是否已&qu ...
- 第三章 JVM内存回收区域+对象存活的判断+引用类型+垃圾回收线程
注意:本文主要参考自<深入理解Java虚拟机(第二版)> 说明:查看本文之前,推荐先知道JVM内存结构,见<第一章 JVM内存结构> 1.内存回收的区域 堆:这是GC的主要区域 ...
- JVM学习四:垃圾收集器与内存回收策略
一.经典垃圾收集器 如果垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的实践者.<Java虚拟机规范>对于垃圾收集器的实现没有任何规定. 这里介绍的经典垃圾收集器," ...
- 【JVM学习笔记】内存回收与内存回收算法 就哪些地方需要回收、什么时候回收、如何回收三个问题进行分析和说明
目录 一.相关名词解释 垃圾收集常用名词 二.哪些地方需要回收 本地方法栈.虚拟机栈.程序计数器 方法区 Java堆 三.什么时候回收 1. 内存能否被回收 内存中的引用类型 引用计数算法 可达性分析 ...
- JVM内存回收机制及回收器-一目了然
一.概述 内存回收,分析出所以然,为什么如此设计,内存回收,如整理屋子.用户就是系统,其实和普通的系统没有大区别. 需求: 正确 高效(不能对用户线程有较大的影响) 二.设计 我来设计的话,一些基本的 ...
- JVM的GC回收算法、GC收集器以及内存分配策略
目录 •写在前面 •标记-清除算法 •复制算法 •标记-整理算法 •HotSpot上的算法实现保障 •GC收集器 •内存分配策略 •写在前面 JVM的垃圾回收算法.收集器以及内存分配策略放在一起了解和 ...
最新文章
- ISME:高手开杠-‘1%的微生物可培养’到底为哪般?
- 通过C#代码 压缩/解压文件
- 【错误记录】Windows 系统 bat 脚本报错 ( 使用 pause 拦截窗口自动关闭 | 方便查看错误 )
- zabbix 3.2.1 升级3.4.1
- sql语句多个表补齐四位_SQL学习笔记 - CTE通用表表达式和WITH用法
- How is html text displayed in Assignment block
- % 在C语言中的用法
- cs硕士妹子找工作经历【阿里人搜等互联网】
- linux交叉编译aix_mips-linux-gcc交叉编译工具链搭建小结【转】
- 《学习之道》第六章习惯的部分-反应程序
- 在pascal环境下学习record
- mysql group commit_MySQL5.7 核心技术揭秘:MySQL Group Commit-阿里云开发者社区
- Skin hierachy
- 为什么买域名必须实名认证?这样做什么原因?
- 【论文简读】Diffusion Kernel Attention Network for Brain Disorder Classification用于脑疾病分类的扩散核注意力网络
- POSCMS 后台自定义链接友情链接增加搜索功能
- ARToolKit在visual studio2013(win10)的环境配置
- 关于编码问题的复制黏贴
- 计算机word表格计算教程F9,Word表格数据计算与域操作
- 2023临沂大学计算机考研信息汇总
热门文章
- 英语语言学u c,06422英语语言学 — 新编简明英语语言学教程, 戴炜栋
- Angular - - $animate
- 使用JFreeChart做成柱状图写入word的总结
- Js与jQuery的相互转换
- Ajax是个什么玩意儿
- ubuntu下访问支付宝官网,安装安全控件
- linux中probe函数的pm管理,linux中 probe函数的何时调用的
- cad lisp程序大集,超经典CAD lisp程序集锦、CAD快捷键大全
- 达梦8数据库 静默安装_centOS7上静默安装达梦数据库
- AtCoder Beginner Contest 128