【4】JVM-GC设计思路分析

Java中将内存的控制交给JVM来实现,方便了JAVA程序猿,当然牺牲了一部分效率,不过总体来看是值得的。那么JVM中是如何设计GC的呢,本文从几个问题入手,然后分析了一下设计思路,如果有理解错误的地方,请批评指正!主要参考了《深入理解JAVA虚拟机》这本书,图是盗来的,图的内容和书上一样。

在JVM的内存模型中,堆内存是JAVA内存区域中最大的一部分,GC主要就是发生在堆中,用来回收那些无用的对象。这样直接就引申出了第一个问题:什么样的对象需要被回收?判断条件是什么?如何判断?

先谈谈什么对象需要被回收,OK,我们自己想一想,肯定是没用的对象需要被回收,对吧?那么如何判断哪些对象还有用,哪些没用了呢?一个对象被创建,如果被引用了,那这个对象肯定是有用的对吧,如果引用全失效了,那就是没用的对象了,需要被回收。基于这个思想,引用计数法诞生了。

  • 引用计数算法:这个非常容易理解,给每个对象添加一个引用计数器,对象每被引用一次,引用计数器就+1,引用失效时就-1。那么判断一个对象是否有用的条件就变成了对这个计数器值得判断了,如果为0,那么被回收,如果为>0,那么保留。但是这种方式会产生一个问题,就是对象之间的循环引用无法被识别,即使这两个对象不能被访问,但是它们之间互相引用着对方,故而计数器肯定>0,那么就不能被回收。JVM中并没有使用引用计数算法,而是使用了根搜索算法。
  • 根搜索算法:这个算法也不难理解,通过条件,选择一系列的对象成为“GC Roots"对象,然后将”GC Roots"对象作为起始点开始向下搜索,搜索所有走过的路径成为“引用链”。在这个引用链上的对象就保留,而如果一个或多个互相引用的对象不在这个引用链上,或者说对象到“GC Roots"不可达,那么这些就是无用的对象,都需要被回收。

注:Java语言中,可作为GC Roots的对象包括下面几种:

1) 虚拟机栈(栈帧中的本地变量表)中引用的对象

2) 方法区中类静态属性引用的对象

3) 方法区中常量引用的对象

4) 本地方法栈中JNI(即一般说的Native方法)引用的对象

既然根搜索算法需要考虑到对象之间的引用,那么就要说一下JAVA中对象的引用类型了:

从JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用,软引用,弱引用,虚引用,这四种引用的强度依次减弱

1) 强引用就是指在程序代码之中普遍存在的,类似 “Object obj = new Object()” 这类的引用,只要强引用还存在,垃圾回收器永远不会回收被引用的对象。我们也正是利用这个原理来重现了OOM异常。

2) 软引用(SoftReference类)是用来描述一些还有用但并非需要的对象,对于软引用关联着的对象,在系统将要发生内存异常之前,将会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存异常

3) 弱引用(WeakReference类)也是用来描述非必需对象的,被弱引用关联的对象只能生存到下一次GC发生之前,当垃圾收集器工作时,无论当前内存释放足够,都会回收掉只被弱引用关联的对象

4) 虚引用(PhantomReference类)也称为幽灵引用或者幻影引用,它是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,对一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知

那么上述内容看完之后想必都知道了什么样的对象会被GC了吧,那么JVM又是通过什么方式来回收这些内存的呢?下面就需要了解一下垃圾的回收算法了。

  • 标记-清除算法

    试着想一想,如果要你要设计一个算法清除满足收集条件的对象来释放内存的时候你该怎么做呢?最简单的是不是就是把需要回收的对象标记一下,然后直接全部回收就行了?照着这个思路就是”标记-清除算法”的思想了,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。想法很简单,实际也就是这么做的。但是呢,这种方式是不是最好的?有什么缺陷?

想到这里,就需要分析一下了。一个个的标记然后清除,效率高吗?当然不。看看下图的标记-清除算法的示意图,可以发现,标记-清除之后会产生大量的内存碎片,如果碎片太多,当程序运行没有足够连续的内存空间来存放大对象的时候,就会不得不提前触发一次GC。概括来说就是有两个缺点:效率不高;内存碎片可能导致提前发生GC。

学习算法的童鞋应该都很清楚,效率是很重要的,有时候需要使用空间来换时间提高效率,那么就需要了解一下第二种回收算法了——复制算法。

  • 复制算法

复制算法呢?它的思想就是空间换时间,将内存容量划分成相等的两块,当这一块的内存用完了,就将还存活的内存复制到另一块上,然后再把使用过的内存空间一次性清理干净。这样每次都是对其中的一块的内存进行回收,也就不需要考虑内存碎片等复杂情况了,只需要移动堆顶指针,然后按照顺序分配即可,实现简单,运行高效。但是缺点也很明显:内存变成一半了.......下图就是复制算法的示意图:

我们知道,在JVM中堆内存的新生代(new )中的对象存活率较低,采用复制算法每次需要复制的对象也不是很多,效率较高,空间换时间值得的。现在的商业虚拟机都是采用复制算法来回收新生代,IBM的专门研究表明:新生代中对象98%是朝生夕死,所以并不需要按照1:1的比例来划分空间来实现复制算法,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中一个Survivor空间。当发生GC的时候,将Eden空间和Survivor空间中还存活的对象拷贝到另一个没使用的Survivor空间中,然后再清理掉Eden和刚刚使用的Survivor空间。Hotspot虚拟机默认Eden和Survivor的大小比例是8:1,也就是新生代每次可以使用的内存空间是整个新生代的90%,只有10%的空间会被浪费。

OK,通过上述的分析,我们知道了在JVM中对于新生带的垃圾回收使用的复制算法(此时发生的GC成为young gc),效率高,我们也就只牺牲了10%的内存空间,挺不错的。请注意这里提到的young gc,后面会提到full gc。但是虽然IBM研究表明一般情况下有98%的对象是朝生夕死,需要回收的,但是不能保证每次回收的时候对象的存活率都低于10%啊,是不是?一旦超过了10%,那么空闲的survivor空间就不够用了,此时就必须依赖老年代的空间来进行分配担保(就相当于A找B借钱,C替A做担保,保证如果A换不起就自己来还,C就是担保人,映射到内存中老年代所占内存就是担保人)。如果空闲的Survivor空间无法存放上次GC之后的存活对象,那么这些对象就会通过分配担保机制进入老年代。

老年代呢,里面保存的都是生存周期较长的对象(老年代里面的对象都是经过了新生代,然后多次存活下来的对象),而复制算法在应对这种存活率极高的内存区域的对象回收时,需要执行较多的复制操作,效率将会变低。关键的还是如果不想浪费50%的空间,那么就需要分配担保机制(参考新生代的设计),但是并没有额外的空间来担保了。所以对于老年代的特性,有人提出了一种“标记-整理算法”,看到这里肯定就想到了前面提到的“标记-清除算法“了,OK,这两个算法标记的过程都是一样的,就在于”标记-整理算法”不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,示意图如下图所示。

很明显,这种”标记-整理算法“的效率不高,所以如果老年代发生GC,那么效率也就不高了,并且一旦老年代发生GC,那么发生的必然是Full GC ,Full GC 会同时对老年代和新生代进行GC操作,顺便也会回收一下perm gen中的内存,所以相比较young gc来说很慢,我们在JVM调优的时候需要避免JVM频繁发生full gc。full gc的速度比young gc要慢10倍。

  • 分代收集算法

通过上述的分析呢,就知道了对于堆中的新生代和老年代会采用不同的垃圾回收算法来回收“死亡”的对象,这种分代回收对象的方法称为“分代收集算法”。这个分代收集算法根据各个年代的特点采用适当的收集算法。在新生代中,每次GC的时候都发现大批的对象死去,只有少量存活,自然选用复制算法;而对于老年代这种存活率高、没有额外担保空间的,就必须使用“标记-清除算法”或者“标记-整理算法“了。

GC设计的理论基础就是这些了,其实原理还是比较容易理解的。GC的具体实现就是垃圾收集器,目前尚没有一个垃圾收集器是完美的,需要配合使用。下面插上一副堆内存划分图。

注:本文写的比较片面,如果想更深入了解,推荐这篇博文:http://jbutton.iteye.com/blog/1569746

时间: 2025-01-06 13:32:59

【4】JVM-GC设计思路分析的相关文章

天津地铁出行线路规划项目需求分析与设计思路分析

天津地铁出行线路规划项目需求分析与设计思路分析 项目概要 以下是天津地铁线路总图,本项目的受众可以通过本软件,获得天津市地铁出行最便捷,最快速的线路推荐. 需求分析 实现一个帮助进行地铁出行路线规划的命令行程序. 支持地铁线路的更改,站点更改.取消与添加,以及线路的局部封闭. 支持查询线路的所有站点. 支持查询到某终止站点的途径最少站点的路线. 数据存储结构分析 由于单一的线路表与站点表是无法表示如此复杂的地铁线路情况的. 有多个前驱的站点如:,以及有多个后继的站点如:,这种情况无法只通过这两个

Backbone设计思路和关键源码分析

一. Backbone的江湖地位: backbone作为一个老牌js框架为大规模前端开发提供了新的开发思路:前端MVC模式,这个模式也是前端开发演变过程中的一个重要里程碑,也为MVVM和Redux等开发思路奠定了夯实的基础,后来的react,vue无不是在backbone的影响下开创出来的经典模式.为什么这么说呢?我们先来回顾下Web前端开发的大概演变流程,本过程纯粹个人理解,抛砖引玉,共同探讨,如有偏差请看官指出错误: 1. 无前端:最早的网页就是HTML,还只是静态页面,当时的脚本含量极少甚

TYPESDK手游聚合SDK服务端设计思路与架构之一:应用场景分析

TYPESDK 服务端设计思路与架构之一:应用场景分析 作为一个渠道SDK统一接入框架,TYPESDK从一开始,所面对的需求场景就是多款游戏,通过一个统一的SDK服务端,能够同时接入几十个甚至几百个各种渠道的SDK.而且这些渠道接口的具体接入字段和接入逻辑,每个月以至每周,都可能发生或大或小的变动.在这样一个复杂的应用场景下,我们应该如何设计一个足够强大而又足够灵活的SDK服务端呢? 首先我们需要厘清,在整个应用场景中,TYPESDK所处的位置,以及它所需要实现的核心功能. 图1 如图1所示,T

MapReduce 之PageRank 算法概述、设计思路和源码分析

早就对PageRank 算法感兴趣,但一直都是轮廓性的概念,没有具体深入学习.最近要学习和总结MapReduce 的实例,就又把PageRank 算法重新学习了一遍,并基于MapReduce 进行了实现. 1. PageRank是什么 PageRank,网页排名,右脚网页级别.是以Google 公司创始人Larry Page 之姓来命名.PageRank 计算每一个网页的PageRank值,并根据PageRank值的大小对网页的重要性进行排序.PageRank的基本思想是:对于一个网页A来说,链

JVM GC调优一则--增大Eden Space提高性能

缘起 线上有Tomcat升级到7.0.52版,然后有应用的JVM FullGC变频繁,在高峰期socket连接数,Cpu使用率都暴增. 思路 思路是Tomcat本身的代码应该是没有问题的,有问题的可能是应用代码升级,或者环境改变了,总之Tomcat的优先级排在最后. 先把应用的heap dump下来分析下: jmap -dump:format=b,file=path pid 用IBM的Heap Analyser分析,发现dubbo rpc调用的RpcInvocation对象和taglibs的Si

深入浅出 JVM GC(3)

# 前言 在 深入浅出 JVM GC(2) 中,我们介绍了一些 GC 算法,GC 名词,同时也留下了一个问题,就是每个 GC 收集器的具体作用.有哪些 GC 收集器呢? Serial 串行收集器(只适用于堆内存 256M 以下的 JVM ) ParNew 并行收集器(Serial 收集器的多线程版本) Parallel Scavenge (PS 收集器,该收集器以吞吐量为主要目的,是1.8的默认 GC) CMS 收集器(该收集器全称 Concurrent Mark Sweep,是一种关注最短停顿

深入浅出 JVM GC(2)

# 前言 在 深入浅出 JVM GC(1) 中,限于上篇文章的篇幅,我们留下了一个问题 : 如何回收? 这篇文章将重点讲述这个问题. 在上篇文章中,我们也列出了一些大纲,今天我们就按照那个大纲来逐个讲解.在此,我将大纲复制过来. 垃圾回收算法 标记清除算法 复制算法 标记整理算法 分代收集算法(堆如何分代) 有哪些垃圾收集器 Serial 串行收集器(只适用于堆内存256m 以下的 JVM ) ParNew 并行收集器(Serial 收集器的多线程版本) Parallel Scavenge (P

流程管理中WEB表单开发服务需求分析及设计思路

在流程管理应用中,BPM产品所提供的表单设计工具,主要是面向开发人员的.而一些办公系统产品所提供的表单设计工具,受自身平台限制,无法在大型定制化应用中使用.在此通过对用户需求分析,提出WEB表单开发服务设计思路. 一.需求分析 现如今,在创新与改革社会环境推动下,办公管理系统的管理需求变化已经是常态了,如何让信息系统快速响应支撑管理需求的多变,已经成为使信息化建设和运维人员头痛的事情.特别是在一些大型企事业单位,快速支撑需求更突出.而原有信息系统很难适应这样的需求,必须走创新的路来解决这些需求,

用MongoDB数据库来管理办公系统中文档型的表单和信息——通用流程化应用审批单设计思路(二,续)

1.办公系统中文档的定义 办公系统中的文档就是指对数据不敏感的业务,例如流程中的审批单.信息专栏.数据上报.信息记录等.而对于这些信息的管理,特别是时效性较强的管理记录,仍采用关系型数据库进行管理. (1)流程中审批单 流程中审批单由功能按钮区.特殊功能区.业务表单区.附件区.审批意见区等区域构成,其中,业务表单区理论上包含附件和意见,但是由于附件和意见的业务特殊性,需要单独进行管理,剩下的业务表单就可以看作文档了. 在一些流程审批业务中,业务信息有的是以Excel或word文件等方式专递,这样