深刻浅出Java垃圾回收机制

简述

虚拟机中的共划分为三个代:年轻代(Young Generation)、老年代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。算法

jvm内存参数

-vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M -vmargs 说明后面是VM的参数,因此后面的其实都是JVM的参数了 -Xms128m JVM初始分配的堆内存 -Xmx512m JVM最大容许分配的堆内存,按需分配 -XX:PermSize=64M JVM初始分配的非堆内存 -XX:MaxPermSize=128M JVM最大容许分配的非堆内存,按需分配安全

按代的垃圾回收机制

在Java中,开发人员没法直接在程序代码中清理内存,而是由垃圾回收器自动寻找没必要要的垃圾对象,而且清理掉他们。垃圾回收器会在下面两种假设(hypotheses)成立的状况下被建立(称之为假设不如改成推测(suppositions)或者前提(preconditions))。多线程

  • 大多数对象会很快变得不可达。
  • 只有不多的由老对象(建立时间较长的对象)指向新生对象的引用。

这些假设咱们称之为弱年代假设( weak generational hypothesis)。为了强化这一假设,HotSpot虚拟机将其物理上划分为两个–新生代(young generation)和老年代(old generation)。jvm

  • 新生代(Young generation): 绝大多数最新被建立的对象会被分配到这里,因为大部分对象在建立后会很快变得不可到达,因此不少对象被建立在新生代,而后消失。对象从这个区域消失的过程咱们称之为”minor GC“。性能

  • 老年代(Old generation): 对象没有变得不可达,而且重新生代中存活下来,会被拷贝到这里。其所占用的空间要比新生代多。也正因为其相对较大的空间,发生在老年代上的GC要比新生代少得多。对象从老年代中消失的过程,咱们称之为”major GC“(或者”full GC“)线程

请看下面这个图表。对象

 图1 : GC 空间 & 数据流

上图中的持久代( permanent generation )也被称为方法区(method area)。他用来保存类常量以及字符串常量。所以,这个区域不是用来永久的存储那些从老年代存活下来的对象。这个区域也可能发生GC。而且发生在这个区域上的GC事件也会被算为major GC。事件

若是老年代的对象须要引用一个新生代的对象,会发生什么呢?

为了解决这个问题,老年代中存在一个”card table“,他是一个512 byte大小的块。全部老年代的对象指向新生代对象的引用都会被记录在这个表中。当针对新生代执行GC的时候,只须要查询card table来决定是否能够被收集,而不用查询整个老年代。这个card table由一个write barrier来管理。write barrier给GC带来了很大的性能提高,虽然由此可能带来一些开销,但GC的总体时间被显著的减小。图片

输入图片说明

新生代的空间

新生代是用来保存那些第一次被建立的对象,他能够被分为三个空间:内存

  • 一个伊甸园空间(Eden )
  • 两个幸存者空间(Survivor )

一共有三个空间,其中包含两个幸存者空间。每一个空间的执行顺序以下:

  1. 绝大多数刚刚被建立的对象会存放在伊甸园空间。
  2. 在伊甸园空间执行了第一次GC以后,存活的对象被移动到其中一个幸存者空间。
  3. 此后,在伊甸园空间执行GC以后,存活的对象会被堆积在同一个幸存者空间。
  4. 当一个幸存者空间饱和,还在存活的对象会被移动到另外一个幸存者空间。以后会清空已经饱和的那个幸存者空间。
  5. 在以上的步骤中重复几回依然存活的对象,就会被移动到老年代。

若是你仔细观察这些步骤就会发现,其中一个幸存者空间必须保持是空的。若是两个幸存者空间都有数据,或者两个空间都是空的,那必定标志着你的系统出现了某种错误。 经过频繁的minor GC将数据移动到老年代的过程能够用下图来描述:

输入图片说明

须要注意的是HotSpot虚拟机使用了两种技术来加快内存分配。他们分别是是”bump-the-pointer“和“TLABs(Thread-Local Allocation Buffers)”。

  • Bump-the-pointer技术跟踪在伊甸园空间建立的最后一个对象。这个对象会被放在伊甸园空间的顶部。若是以后再须要建立对象,只须要检查伊甸园空间是否有足够的剩余空间。若是有足够的空间,对象就会被建立在伊甸园空间,而且被放置在顶部。这样以来,每次建立新的对象时,只须要检查最后被建立的对象。这将极大地加快内存分配速度。可是,若是咱们在多线程的状况下,事情将大相径庭。若是想要以线程安全的方式以多线程在伊甸园空间存储对象,不可避免的须要加锁,而这将极大地的影响性能。
  • TLABs 是HotSpot虚拟机针对这一问题的解决方案。该方案为每个线程在伊甸园空间分配一块独享的空间,这样每一个线程只访问他们本身的TLAB空间,再与bump-the-pointer技术结合能够在不加锁的状况下分配内存。

老年代GC处理机制

JDK7一共有5种GC类型:

  • Serial GC
  • Parallel GC
  • Parallel Old GC (Parallel Compacting GC)
  • Concurrent Mark & Sweep GC (or “CMS”)
  • Garbage First (G1) GC

1. Serial GC (-XX:+UseSerialGC)

新生代空间的GC方式咱们在前面已经介绍过了,在老年代空间中的GC采起称之为”mark-sweep-compact“的算法。

  1. 算法的第一步是标记老年代中依然存活对象。(标记)
  2. 第二步,从头开始检查堆内存空间,而且只留下依然幸存的对象。(清理)

最后一步,从头开始,顺序地填满堆内存空间,而且将对内存空间分红两部分:一个保存着对象,另外一个空着(压缩)。

2. Parallel GC (-XX:+UseParallelGC)

输入图片说明

从上图中,你能够轻易地看出serial GC和parallel GC的区别,serial GC只使用一个线程执行GC,而parallel GC使用多个线程,所以parallel GC更高效。这种GC在内存充足以及多核的状况下会颇有用,所以咱们也称之为”throughput GC“。

3. Parallel Old GC(-XX:+UseParallelOldGC)

Parallel Old GC在JDK5以后出现。与parallel GC相比,惟一的区别在于针对老年代的GC算法。Parallel Old GC分为三步:标记-汇总-压缩(mark – summary – compaction)。汇总(summary)步骤与清理(sweep)的不一样之处在于,其将依然幸存的对象分发到GC预先处理好的不一样区域,算法相对清理来讲略微复杂一点。 ###4. CMS GC (-XX:+UseConcMarkSweepGC) 输入图片说明

第一步初始化标记(initial mark) 比较简单。这一步骤只是查找那些距离类加载器最近的幸存对象。所以,停顿的时间很是短暂。在以后的并行标记( concurrent mark )步骤,全部被幸存对象引用的对象会被确认是否已经被追踪和校验。这一步的不一样之处在于,在标记的过程当中,其余的线程依然在执行。在从新标记(remark)步骤,会再次检查那些在并行标记步骤中增长或者删除的与幸存对象引用的对象。最后,在并行交换( concurrent sweep )步骤,转交垃圾回收过程处理。垃圾回收工做会在其余线程的执行过程当中展开。一旦采起了这种GC类型,由GC致使的暂停时间会极其短暂。CMS GC也被称为低延迟GC。它常常被用在那些对于响应时间要求十分苛刻的应用之上。

固然,这种GC类型在拥有stop-the-world时间很短的优势的同时,也有以下缺点:

  • 它会比其余GC类型占用更多的内存和CPU
  • 默认状况下不支持压缩步骤 在使用这个GC类型以前你须要慎重考虑。若是由于内存碎片过多而致使压缩任务不得不执行,那么stop-the-world的时间要比其余任何GC类型都长,你须要考虑压缩任务的发生频率以及执行时间。

5. G1 GC 一种全新的内存模型

G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停全部应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分红不少区域,G1收集器经过将对象从一个区域复制到另一个区域,完成了清理工做。这就意味着,在正常的处理过程当中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。

相关文章
相关标签/搜索