jvm特性(3)( 收集算法和收集器的概念)
JVM高级特性与实践(三):垃圾收集算法 与 垃圾收集器实现
大致知识点如下:
- 4种垃圾收集算法概念的学习
- 7种垃圾收集器特征的学习
一. 垃圾收集算法
1. 标记-清除算法(Mark-Sweep)
算法分成“标记”、“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
垃圾收集算法中最基础的算法 ,后续的收集算法都是基于这种思路并对其不足进行改进而产生的。
主要不足有两点:
效率问题,标记和清除两个过程的效率都不高;
空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不触发另一次垃圾收集动作。
标记-清除算法的执行过程如下:
2. 复制算法(Copying)
为了解决标记-清除算法的效率问题
它将可用内存按容量分成大小相等的两块,每次只使用其中的一块。当这一块内存用完,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。
2,1,优点与缺陷:
优点:这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。 缺陷:只是这种算法的代价是将内存缩小为原来的一半,代价过高。
2,2,复制算法的执行过程如下:
补充:堆分代(年轻代,老年代)
2,3,升级版复制算法
现在商业虚拟机都采用复制算法来回收新生代,而IBM公司的专门研究表明:
新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1 的比例来划分控件。
升级版算法主要思想为:
将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,
将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚使用的Survivor空间。
2,4,HotSpot虚拟机中的升级版复制算法
HotSpot虚拟机默认Eden和Survivor的大小比例是 8:1 ,也就是每次新生代中可用内存控件为整个新生代容量的90%(80%+10%),
只有10%的内存会被“浪费”。当然,98%的对象可回收只是一般场景下的数据,没有办法保证每次回收都只有不多于10%的对象存活,
当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion:理解成搬运晋升更恰当)。
内存的分配担保(Handle Promotion:晋升操作)
如果另外一块Survivor上没有足够空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代。
关于这点上面补充的堆分代中解释更详细。
3 . 标记-整理算法(Mark-Compact)
为了解决复制算法的不足,复制算法在对象存活率较高时要进行较多的复制操作,效率将会变低。
更关键的是:
- 如果不想浪费50%的空间,就需要有额外的空间进行分配担保,
- 以应对特别是所有对象都存活的极端情况,所以在老年代一般不选用复制算法。
根据老年代的特点(存活率比较高),标记-整理算法被提出来:
与 标记-清除算法不同在于标记后,让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存(也就是覆盖掉需要清理的对象)。
标记-整理算法的执行过程如下:
4. 分代收集算法(Generational Collection)
当前商业虚拟机的垃圾收集都采用分代收集算法,主要思想为:
根据对象存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法。
分代其实是一种总体思路,其他算法辅助:
- 在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法
- 在老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法
二. 垃圾收集器算法实现
Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、版本的虚拟机所提供的垃圾收集器都可能会有很大差别,
并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现
1. Java堆内存结构
接下来讨论的收集器基于JDK1.7 Update 14 之后的HotSpot虚拟机(在此版本中正式提供了商用的G1收集器,之前G1仍处于实验状态),
但是在正式讨论收集器之前,需要介绍一下Java堆内存结构—— 适用于非G1收集器外的垃圾收集器:
根据Java对象的生命周期长短把Java堆内存分为老年代和年轻代,然后年轻代又被划分为:一个Eden区和两个大小同等的Survivor区。
2. 垃圾收集器
了解Java堆内存中的大致划分,因为随后讲解HotSpot虚拟机中的各个收集器就是存在于此内存中
虚拟机中所包含所有的收集器:
如上图所示,展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。
虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。
- 虽然在对各个收集器进行比较,但并非是为了挑出一个最好的收集器。
- 因为目前并无完美的收集器出现,只是选择对具体应用最适合的收集器。
- 若真有完美收集器的存在,HotSpot虚拟机就没必要如上图所示实现那么多的收集器了 。
在后续几个收集器的介绍中,涉及到了并发和并行的收集器:
- 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
- 并发(Concurrent): 指用户线程与垃圾收集线程同时执行(并行or交替执行),用户程序在继续执行,而垃圾收集程序运行在另一个CPU上。
2.1 Serial 收集器
(1)特征
Serial收集器是最基本、发展历史最悠久的收集器,曾经(JDK1.3之前)是虚拟机新生代收集的唯一选择。
- 它是一个单线程收集器,但“单线程”并非指该收集器只会使用一个CPU或一条收集线程去完成垃圾收集工作,
- 更重要的是它在进行垃圾收集时,必须暂停其他所有的工作线程,直至Serial收集器收集结束为止。
这项工作是由虚拟机在后台自动发起和自动完成的,在用户不可见的情况下把用户正常工作的线程全部停掉
(2)图解运行过程
以下是Serial / Serial Old 收集器的运行过程:
(3)优势
也许你认为Serial收集器这种工作方式根本没有存在的必要,其实不尽然,它依然是虚拟机运行在Client模式下的默认新生代收集器。
优于其他收集器的地方:
- 简单而高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,
- Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得更高的单线程收集效率。
需要注意:从Serial收集器到Parallel收集器,再到最前沿成功的G1收集器,用户线程的停顿时间越来越少,但是仍无法完全消除。
(4)适用场景
- 其实实际情况并没有那么糟糕,在用户的桌面应用场景中,分配给虚拟机管理的内存一般不会很大,
- 收集几十兆甚至一两百兆的新生代(仅仅是新生代使用的内存,桌面应用基本不会再大了),停顿时间完全可以控制在几十毫秒最多一百毫秒以内,
- 只要不频繁发生,这点停顿时间可以接收。所以,Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择。
2.2 Serial Old 收集器
(1)特征
Serial Old 是 Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。
(该收集器的运行过程同Serial收集器的相同)
(2)适用场景
此收集器的主要意义也是在于给Client模式下的虚拟机使用。如果在Server模式下,它还有两大用途:
- 在JDK1.5 以及之前版本中与Parallel Scavenge收集器搭配使用。
- 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。
2.3 ParNew 收集器
(1)特征
在理解完Serial收集器后,得知了它的一个明显的特征——单线程。
ParNew收集器就是它的多线程版本,除了此特征其余地方与Serial收集器相同,两者共用了相当多的代码。
(2)图解运行过程
(3)不同使用场景中的优势与缺陷
单CPU的环境
- ParNew 收集器在单CPU的环境中绝对不会有比Serial收集器有更好的效果,甚至由于存在线程交互的开销,
- 该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证可以超越。
多CPU下的环境
- 随着CPU的数量增加,它对于GC时系统资源的有效利用是很有好处的。
- 它默认开启的收集线程数与CPU的数量相同,在CPU非常多的情况下可使用参数设置。
2.4 Parallel Scavenge 收集器
(1)特征
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法,并且是一个并行的多线程收集器,看上去与ParNew收集器类似,
其实此收集器的特点是它的关注点与其他收集器不同:
- 在讲述第一个Serial Old收集器时已经提过,后续产生的收集器它们都是为了尽可能缩短垃圾收集时用户线程的停顿时间,
- 而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。
(2)吞吐量(Throughput)
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗的比值,运算公式为:
吞吐量 = 用户运行时间 / (运行用户代码时间 + 垃圾收集时间)
举个例子说明:虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
(3)以下是Parallel Scavenge / Parallel Old 收集器的运行过程:
(4)适用场景
- 停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。
- 而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
PS:吞吐量和停顿时间不是一样吗,停顿少吞吐量肯定大呀。
(停顿少,可以gc采用并行。总体时间是变长的;吞吐量大,可(应用和gc)并发,但单个执行延长/缓慢。总体时间是变短的)
2.5 Parallel Old 收集器
(1)特征
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
(此收集器的运行过程与Parallel Scavenge收集器相同)
(2)Parallel Old 产生之前的收集器组合问题
结构图中的收集器不要忘记这些收集器之间的连线,代表这些收集器可以互相搭配使用,也是很重要的一点。接下来要讲的也与这个收集器组合有关。
此收集器在JDK1.6 中才出现,在此之前,Parallel Scavenge收集器处于一个很尴尬的位置,原因如下:
- 如果新生代选择了Parallel Scavenge收集器,那么老年代除了Serial Old收集器外别无选择,
- 可是Serial Old收集器在服务端应用性能上的效果不太理想,而导致Parallel Scavenge收集器在整体应用上无法获得吞吐量最大化的效果。
- 由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,此组合还不如Parallel Scavenge搭配CMS收集器。
(3)Parallel Old的产生解决组合问题
- 可是在Parallel Old收集器 出现后,“吞吐量优先”收集器终于有了比较理想的应用组合,
- 在注重吞吐量及CPU资源敏感的场合,可以有限考虑Parallel Scavenge搭配Parallel Old收集器。
2.6 CMS(Concurrent Mark Sweep) 收集器
(1)特征
CMS收集器是一种以获取最短回收停顿时间为目标的收集器。该收集器名字中的“Mark Sweep”已经体现出它是基于“标记-清除”算法实现的。
(2)解析运行过程
此收集器的运作过程相对于前面几种较为复杂,整个过程分为4个步骤:
1,初始标记 (CMS initial mark)
仅仅只是标记一下GC Roots 能直接关联到的对象,速度很快。
2,并发标记(CMS concurrent mark)
该阶段进行 GC Root Tracing的过程。
3,重新标记(CMS remark)
修改并发标记期间因用户程序继续运作而导致标记产生变动的那部分对象的标记记录。
此阶段停顿时间会比初始标记阶段稍长一些,但远比并发标记时间短。
4,并发清除(CMS concurrent sweep)
- 这4个步骤中的 初始标记、重新标记 过程仍需要停止所有工作线程。
- 由于整个过程中耗时最长的并发标记 和 并发清除过程收集器线程都可以与用户线程一起工作,
所以总体而言,CMS收集器的内存回收过程是与用户线程一起并发执行的。
(3)以下是CMS 收集器的运行过程:
(4)优点与缺点
主要优点:并发收集、低停顿,但是远远不到完美程度,不可避免的有以下3个缺点:
1,并发导致总吞吐量降低
CMS收集器对CPU资源非常敏感,实际上,面向并发设计的程序都对CPU资源比较敏感。
在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说是CPU资源)而导致应用程序变慢,总吞吐量会降低。
2,无法处理浮动垃圾导致 Full GC
由于CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。
由于CMS并发清理阶段用户线程还在运行,伴随程序运行自然会有新的垃圾不断生成,
此部分垃圾出现在标记之后,CMS无法再当次收集中处理,只好留给下一次GC时再清理,此部分的垃圾称为“浮动垃圾”。
3,本质算法导致大量空间碎片
CMS是一款基于“标记-清除”算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。
空间碎片过多时,将会给大对象分配带来很大麻烦,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象。
(5)适用场景
目前很大一部分的Java应用集中在互联网站或B/S系统的服务端上,
这类应用尤其重视服务的响应速度,希望系统停顿时间最短,来给用户带来较好体验,而CMS收集器最适合这类需求。
2.7 G1(Garbage-First) 收集器
(1)特征
G1收集器是最前沿的成果之一,未来可以替换JDK 1.5中发布的CMS收集器,特点如下:
1,并行与并发:
G1 能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短“Stop The World”停顿时间,
部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。 2,分代收集:
虽然G1不可不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同方式去处理新创建的对象和已存活一段时间、熬过多次GC的旧对象来获取更好的收集效果。 3,空间整合:
与CMS收集器采用的“标记-清除”算法不同,G1采用的是“标记-整理”算法,意味着G1运行期间不会产生内存空间碎片,收集后能提供规整的可用内存。
此特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。 4,可预测的停顿:
这是G1相对CMS的一大优势,G1除了降低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内。
在G1之前的收集器进行收集的范围都是整个新生代或老年代,而G1使用时,Java堆的内存布局与其他收集器有很大区别,
- 它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,
- 但新生代和老年代不再是物理隔离,而都是一部分Region(不需要连续)的集合。
(2)G1收集器的运作大致可划分以下几个步骤:
1,初始标记 (Initial Marking)
仅仅只是标记一下GC Roots 能直接关联到的对象,并且修改TAMS(Nest Top Mark Start)的值,
让下一阶段用户程序并发运行时,能在Region中创建对象,此阶段需要停止线程,但耗时很短。 2,并发标记(Concurrent Marking)
从GC Root 开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行。 3,最终标记(Final Marking)
修改并发标记期间因用户程序继续运作而导致标记产生变动的那部分对象的标记记录。此阶段需要停顿线程,但是可并行执行。 4,筛选回收(Live Data Counting and Evacuation)
首先对各个Region中的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。
此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。
(3)以下是G1 收集器的运行过程:
(4)适用场景
根据G1收集器的特征,最适用于面向服务端应用来进行垃圾收集。
总结:
- 其实内存回收与垃圾收集器在很多时候都是影响系统性能、并发能力的主要因素之一,
- 虚拟机之所以提供多种不同的收集器,也是因为只有根据实际应用需求、实现方式来选择最优的收集方式才能获取最高的性能。
了解收集器需要注意:
- 对哪个区域(新/老/整体)
- 采用哪种收集算法(复制,清除,整理,分代)
- 使用哪种方式(串行,并行,并发)
来源:周志明《深入理解Java虚拟机》
jvm特性(3)( 收集算法和收集器的概念)的更多相关文章
- JVM-垃圾收集算法、垃圾收集器、内存分配和收集策略
对象已死么? 判断一个对象是否存活一般有两种方式: 1.引用计数算法:每个对象都有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1.计数为0时可以回收. 2.可达性分析算法(Reachab ...
- JVM笔记(三) 垃圾收集器(2)收集算法
垃圾收集器2:收集算法 主要通过阅读<深入了解Java虚拟机>(周志明 著)和网络资源汇集而成,为本人学习JVM的笔记.同时,本文理论基于JDK 1.7版本,暂不考虑 1.8和1.9 的新 ...
- JVM(2)——GC算法和收集器
一.引入 上篇博客<JVM--简介>中主要介绍了JVM的内存模型,思考一下: 为什么要划分堆.栈.方法区等? 为什么把不同种类的数据信息分别存放? 答案可以分为很多很多条,这里就说一个方面 ...
- JVM之GC算法、垃圾收集算法——标记-清除算法、复制算法、标记-整理算法、分代收集算法
标记-清除算法 此垃圾收集算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记对象,它的标记过程前面已经说过——如何判断对象是否存活/死去 死去的对象就会 ...
- JVM垃圾收集算法——分代收集算法
分代收集算法(Generational Collection): 当前商业虚拟机的垃圾收集都采用"分代收集算法". 这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分 ...
- JVM的stack和heap,JVM内存模型,垃圾回收策略,分代收集,增量收集
(转自:http://my.oschina.net/u/436879/blog/85478) 在JVM中,内存分为两个部分,Stack(栈)和Heap(堆),这里,我们从JVM的内存管理原理的角度来认 ...
- jvm系列(三):GC算法 垃圾收集器
原文出处:纯洁的微笑 这篇文件将给大家介绍GC都有哪几种算法,以及JVM都有那些垃圾回收器,它们的工作原理. 概述 垃圾收集 Garbage Collection 通常被称为"GC" ...
- JVM垃圾回收算法 及 垃圾收集器
摘自<深入理解Java虚拟机> 一.什么是: GC算法是 方法论,那么垃圾收集器就是具体的 实现. 二.四种 垃圾回收算法 1.标记-清除算法:最基础的收集算法:不足有两点:1标记和清除两 ...
- JVM 的GC算法和垃圾收集器
1.标记清除算法 黑色部分代表可回收对象,灰色部分代表存活对象,绿色部分代表未使用的.最基础的收集算法就是标记清除算法如同他名字一样,算法分为"标记"和"清除" ...
随机推荐
- Laravel中用GuzzleHttp
阅读数:14715 今天项目中用到GuzzleHttp,开始不知道怎么用,其实还是很简单的. 直接在项目根目录,输入以下命令 composer require guzzlehttp/guzzle 1 ...
- Java代码实现依赖注入
http://zhangjunhd.blog.51cto.com/113473/126545 这里将模仿Spring实现一种基于xml配置文件的依赖注入机制.文件中将实现3中注入,一是单值注入,包括i ...
- Jsp的语法和指令
Jsp的三种注释 前端语言注释:<!-- --> 会被转译,也会被发送,但是不会被浏览器执行 java语言注释: 会被转译,但是不会被servlet执行 Jsp注释:<%-- -- ...
- 【Unity】2.1 初识Unity编辑器
分类:Unity.C#.VS2015 创建日期:2016-03-26 一.简介 本节要点:了解Unity编辑器的菜单和视图界面,以及最基本的操作,这是入门的最基础部分,必须掌握. 二.启动界面 双击桌 ...
- html 源码 引入样式
post-title2 示例 sdf post-title 示例
- 20170908工作日记--Volley源码详解
Volley没有jar包,需要从官网上下载源码自己编译出来,或者做成相关moudle引入项目中.我们先从最简单的使用方法入手进行分析: //创建一个网络请求队列 RequestQueue reques ...
- struts2和JSON的数据交互
一.实验环境 1.struts2基本包 2.json-plugin 在struts2的lib下可以找到. 3.web.xml 加入struts2 <filter> <filter-n ...
- hdu 4995 离线处理+模拟
http://acm.hdu.edu.cn/showproblem.php?pid=4995 给定一维坐标下的n个点,以及每个点的权值,有m次查询,每次将查询的x点上的权值修改为离x最近的k个点权值的 ...
- hdu 4946 凸包注意重点
http://acm.hdu.edu.cn/showproblem.php?pid=4946 给你n个点的坐标和速度,如果一个点能够到达无穷远处,且花费的时间是最少的,则此点输出1,否则输出0. 每个 ...
- D3_book 11.1 pie
<!-- pie example --> <!DOCTYPE html> <meta charset="utf-8"> <style> ...