Java垃圾回收
垃圾收集算法
引用计数
堆中的每个对象都有一个引用计数,当对象被引用时引用计数加1,当对象的引用被重新赋值或超出有效区域时引用计数减1,当一个对象被回收后,它所引用的对象的引用计算减1。当一个对象的引用计数变为0时就被回收。
引用计数的优点:
垃圾收集器可以很快地执行,当一个对象的引用数为0时就可以回收这个对象,垃圾收集交织在程序的正常执行过程中,不用长时间中断程序的正常执行。
引用计数的缺点:
- 每次引用计数的增加和减少会带来额外的开销
- 无法检测出循环引用
根搜索算法
垃圾检测通过建立一个根对象的集合(局部变量、栈桢中的操作数,在本地方法中引用的对象,常量池等)并检查从这些根对象开始的可触及性来实现。根对象总是可访问的,如果存在根对象到一个对象的引用路径,那么称这个对象是可触及的或活动对象,否则是不可触及的,不可触及的对象就是垃圾对象。
标记清除
分为标记和清除两个阶段,在标记阶段,垃圾收集器跟踪从根对象的引用,在追踪的过程中对遇到的对象打一个标记,最终未被标记的对象就是垃圾对象,在清除阶段,回收垃圾对象占用的内存。可以在对象本身添加跟踪标记,也可以用一个独立的位图来设置标记。
标记清除法是基础的收集算法,其他算法大多时针对这个算法缺点的改进。
有两个缺点:
- 效率
- 存在内存碎片
复制算法
将内存划分为大小相等的两个区域,每次只使用其中的一个区域,当这个区域的内存用完了,就将可触及的对象直接复制到新的区域并连续存放以消除内存碎片,当可触及对象复制完后,清除旧内存区域,修改引用的值。
这种算法的缺点很明显,可使用内存变为了原来的一半,太过浪费。
一般情况下,新生代中的对象大多生命周期很短,也就是说当进行垃圾收集时,大部分对象都是垃圾,只有一小部分对象会存活下来,所以只要保留一小部分内存保存存活下来的对象就行了,用不着使用一半的内存。在新生代中一般将内存划分为三个部分:一个较大的Eden空间和两个较小的Survior空间(一样大小),每次使用Eden和一个Survior的内存,进行垃圾收集时将Eden和使用的Survior中的存活的对象复制到另一个Survior空间中,然后清除这两个空间的内存,下次使用Eden和另一个Survior,HotSpot中默认将这三个空间的比例划分为8:1:1,这样被浪费掉的空间就只有总内存的1/10了。
这样的内存空间划分是基于这样一种假设,即每次垃圾收集时大部分对象都是垃圾,只有少部分对象存活。如果遇到例外的情况怎么办,在某次垃圾收集时存活下来的对象超过了预留的那个Survior空间的总大小,这就需要依赖其他的内存进行分配担保了(参考分代收集,前面的描述中也说了这是新生代中的方法)
标记整理
普通的标记清除会在内存中留下内存碎片,复制算法如果不想浪费掉50%内存就需要有内存分配担保,一般是内存分代,但总有一代是没有其他代为它担保的。标记整理算法中标记的过程同标记清理一样,但整理部分不是直接清除掉垃圾对象,而是将活动对象统一移动一内存的一端,然后清除边界外的内存区域,这样就避免了内存碎片。也不会浪费内存,不需要其他内存进行担保
分代收集
大多数程序中创建的大部分对象生命周期都很短,而且会有一小部分生命周期长的对象,为了克服复制收集器中每次垃圾收集都要拷贝所有的活动对象的缺点,将内存划分为不同的区域,更多地收集短生命周期所在的内存区域,当对象经历一定次数的垃圾收集存活时,提升它的存在的区域。一般是划分为新生代和老年代。新生代又划分为Eden区,From Survior区和To Survior区。
自适应收集器
监听堆中的情形,并且对应地调用合适的垃圾收集技术。
垃圾收集器
Serial
一个单线程的收集器,在进行垃圾收集时会暂停其他线程的工作,不适合用到Server端的虚拟机,但Client模式的模拟机还是可以用的,因为Client模式下的应用分配到的系统内存一般不大,垃圾收集可以很快完成。优点就是简单高效,没有线程交互开销,可以获得最高的单线程收集效率。
ParNew
Seria的多线程版本,可以多个线程收集垃圾,但如果CPU只有一核且没有超线程,效果就不一定比Serial好了,如果是多核或有超线程,可以保证效果好于Serial,除Seria之外,这是唯一能与CMS收集器配合的垃圾收集器
Parallel Scavenge
使用复制算法的新生代多线程垃圾收集器,Parallel Scavenge收集器的关注点和其他收集器不同,其他收集器的关注点是尽可能缩短垃圾收集时用户线程等待的时间,而Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(Throughput),即CPU用于运行用户代码的时间与CPU总消耗时间的比值。以缩短用户线程等待时间的收集器适合用于需要与用户交互的程序,而以吞吐量为目标的收集器适合用于不需要和用户太多的交互,以后台运算为目标的任务。
Parallel Scavenge可以通过参数设置每次垃圾收集需要停顿的时间和吞吐量目标,但停顿时间并不是越小越好,这是以牺牲吞吐量和新生代空间为代价的,因为要使垃圾收集停顿时间缩小,只能进行少量多次收集,或减小需要收集的空间大小。
还有一个-XX:UseAdaptiveSizePolicy参数,指定这个参数后,就不需要手工指定新生代的大小、Eden区和Survior区的比例大小和晋升老年代对象年龄等细节参数了,虚拟机会根据收集到的信息动态调整这些参数,这称为自适应策略。
Serial Old
Serial的老年代版本,单线程收集器,使用"标记-整理"算法,主要被Client模式下的虚拟机使用,当被使用在Server模式时主要有两个用途:
- 与Parallel Scavenge配合使用
- 作为CMS收集失败时的备选方案。
Parallel Old
Parallel Scavenge的老年代版本,使用"标记-整理"算法,JDK1.6后提供的,在此之前,如果新生代选择了Parallel Scavenge,老年代只能选择Serial Old,由于Serial Old是单线程的垃圾收集器,可能会影响收集性能。Parallel Old出现后,就可以分别在新生代和老年代选择Parallel Scavenge和Parallel Old组合了。
CMS(Concurrent Mark Sweep)
以获取最短回收停顿时间为目标的收集器,使用“标记-清除”算法,整个回收过程分为以下4步:
- 初始标记(CMS Initial Mark)
- 并发标记(CMS Current Mark)
- 重新标记(CMS Remark)
- 并发清楚(CMS Concurrent Sweep)
初始标记与重新标记阶段仍会暂停用户线程的运行。
初始标记只是记录下GC Root能直接关联到的对象,速度很快。
并发标记就是GC Roots Tracing了,速度较慢,但可以和用户线程同时运行。
重新标记是修正并发标记时由于用户线程运行导致的标记记录变动,这个阶段会使用户线程停顿,停顿时间比初始标记略长,但仍小于重新标记。
并发清除就是清除垃圾对象了,耗时较长,但可与用户线程同时工作。
CMS的缺点
- 对CPU资源敏感,并发阶段和用户线程同时运行,影响服务器的响应速度,尤其是CPU核心数少时
- 无法处理浮动垃圾,由于并发阶段用户线程同时在运行,可能会在垃圾收集过程中产生新的垃圾,CMS无法处理这部分浮动垃圾,由于在进行垃圾收集时用户线程同时在运行,需要额外的内存空间,所以不能等到内存满时再进行GC,需要预留一部分空间,如果预留的这部分空间不够GC时用户线程创建新对象使用,就会使用预备方法,使用Serial Old进行一次Full GC。
- CMS基于“标记-清除”算法,进行垃圾回收后会存在内存碎片,当申请大的连续内存时可能内存不足,此时需要进行一次Full GC,可以通过参数指定进行Full GC后或进行多少次Full GC后进行一次内存压缩来整理内存碎片。
G1(Garbage First)
基于"标记-整理"算法,避免了内存碎片的问题,并可精确地控制垃圾回收时的停顿。
G1收集器可以实现基本不牺牲吞吐量的前提下完成低停顿的内存回收,不同于之前的垃圾回收器,G1收集器的回收区域不是整个新生代或老年代,而是将整个Java堆划分为多个固定大小的区域,并跟踪这些区域里的垃圾堆积程度,在后台维护一个优先列表,优先回收垃圾最多的区域。区域的划分使每次回收时间变短,而优先级的划分使得每次回收的区域可以回收最多的垃圾,这就使用G1收集器可以在有限的时间内获取最高的收集效率。
内存分配与回收策略
对像优先在新生代Eden区分配,当Eden区没有足够的内存时会发生一次Minor GC(新生代GC,Major GC或Full GC是老年代GC)
大对象可以直接在老年代分配内存,可以通过参数指定一个大小,大于这个大小的对象直接在老年代中分配内存。
进行Minor GC时,Eden区和一个Survior区中存活的对象会被复制到另一个Survior区,一个对象每在一次Minor GC中存活下来一次后这个对象的年龄就加1,当这个对象的年龄大于一定值(默认15)就会进入老年代。
如果Survior中相同年龄的对象占用的空间大于Survior空间的一半,那么年龄大于或等于这个年龄的对象会直接进入老年代,而不用等到达到特定年龄
当进行Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果小于,判断是否开启了HandlerPromotionFailure允许担保失败,如果开启了就只进行Minor GC,否则进行Full GC。由于使用的之前Minor GC时的平均大小,如果某一次突然大小变大,导致老年代剩余空间不够,即担保失败,会再进行一次Full GC。
finalize
GC时会对活动对象进行标记,没有被标记的对象就是垃圾对象,但垃圾对象不会直接被清除,垃圾收集器还会判断是否需要执行对象的finalize方法,如果对象没有覆写finalize方法或它的finalize已经被执行过一次,那么是没有必要执行的,否则就认为是有必要执行的,当被判断为有必要执行时,这个对象会被放入一个F-Queue队列中,由一个后台的低优先级的Finalizer线程执行队列中的对象的finalize方法,对象可以在这个方法中中复活自己,即重新被其他对象引用,但这个函数只会被垃圾收集器运行一下,第二次回收这个对象时这个函数不会再被调用。稍后GC会对F-Queue队列中的对象执行第二次标记。
Java垃圾回收的更多相关文章
- 【转载】Java垃圾回收机制
原文地址:http://www.importnew.com/19085.html Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联 ...
- 【转】深入理解 Java 垃圾回收机制
深入理解 Java 垃圾回收机制 一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...
- 深入理解java垃圾回收机制
深入理解java垃圾回收机制---- 一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...
- Java GC系列(2):Java垃圾回收是如何工作的?
本文由 ImportNew - 伍翀 翻译自 javapapers. 目录 垃圾回收介绍 垃圾回收是如何工作的? 垃圾回收的类别 垃圾回收监视和分析 本教程是为了理解基本的Java垃圾回收以及它是如何 ...
- Java GC系列(1):Java垃圾回收简介
本文由 ImportNew - 好好先生 翻译自 javapapers. Java的内存分配与回收全部由JVM垃圾回收进程自动完成.与C语言不同,Java开发者不需要自己编写代码实现垃圾回收.这是Ja ...
- Java垃圾回收介绍(译)
在Java中,对象内存空间的分配与回收是由JVM中的垃圾回收进程自动完成的.与C语言不同的是,在Java中开发者不需要专门为垃圾回收写代码.这是使Java流行的众多特征之一,也帮助了程序员写出了更好的 ...
- [牛感悟系列]JAVA(1)理解JAVA垃圾回收
理解JAVA垃圾回收的好处是什么?满足求知欲是一方面,编写更好的JAVA应用是另外一方面. 如果一个人对垃圾回收过程感兴趣,那表明他在应用程序开发领域有相当程度的经验.如果一个人在思考如何选择正确的垃 ...
- [译]GC专家系列2:Java 垃圾回收的监控
原文链接:http://www.cubrid.org/blog/dev-platform/how-to-monitor-java-garbage-collection/ 这是"成为GC专家系 ...
- java 垃圾回收(堆内存)、以及栈内存的释放
一.Java的垃圾回收机制———解疑 Java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间. 需要注意的是:垃圾回收回收的是无任何引用的 ...
- Java垃圾回收机制_(转载)
Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联系起来.在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给 ...
随机推荐
- Shell特殊变量
$ 表示当前Shell进程的ID,即pid $echo $$ 运行结果 特殊变量列表 变量 含义 $0 当前脚本的文件名 $n 传递给脚本或函数的参数.n 是一个数字,表示第几个参数.例如,第一个参数 ...
- Android权限管理之Permission权限机制及使用
前言: 最近突然喜欢上一句诗:"宠辱不惊,看庭前花开花落:去留无意,望天空云卷云舒." 哈哈~,这个和今天的主题无关,最近只要不学习总觉得生活中少了点什么,所以想着围绕着最近面试过 ...
- 如何远程关闭一个ASP.NET Core应用?
在<历数依赖注入的N种玩法>演示系统自动注册服务的实例中,我们会发现输出的列表包含两个特殊的服务,它们的对应的服务接口分别是IApplicationLifetime和IHostingEnv ...
- Angular企业级开发(4)-ngResource和REST介绍
一.RESTful介绍 RESTful维基百科 REST(表征性状态传输,Representational State Transfer)是Roy Fielding博士在2000年他的博士论文中提出来 ...
- C#制作简易屏保
前言:前段时间,有个网友问我C#制作屏保的问题,我瞬间懵逼了(C#还可以制作屏保!).于是我去查阅相关资料,下面把C#如何制作屏保的过程及我学习过程的心得也记录下来,希望对需要的人能有帮助. 基本思路 ...
- 防线修建 bzoj 2300
防线修建(1s 512MB)defense [问题描述] 近来A国和B国的矛盾激化,为了预防不测,A国准备修建一条长长的防线,当然修建防线的话,肯定要把需要保护的城市修在防线内部了.可是A国上层现在还 ...
- SNMP简单网络管理协议
声明:以下内容是学习谌玺老师视频整理出来(http://edu.51cto.com/course/course_id-861.html) SNMP(Simple Network Management ...
- Quartz2D总结
天了噜,脑子完全懵了,最起码说出来个上下文啊,连这个都给忘了,特此总结一下,并以此缅怀这次面试 Quartz2D的API来自于Core Graphics(这就是为什么CGContextRef是以CG开 ...
- GCC学习(1)之MinGW使用
GCC学习(1)之MinGW使用 因为后续打算分享一些有关GCC的使用心得的文章,就把此篇当作一个小预热,依此来了解下使用GNU工具链(gcc.gdb.make等)在脱离IDE的情况下如何开发以及涉及 ...
- vue2.0构建淘票票webapp
项目描述 之前一直用vue1.x写项目,最近为了过渡到vue2.0,特易用vue2.0栈仿写了淘票票页面,而且加入了express作为后台服务. 前端技术栈:vue2.0 + vue-router + ...