java虚拟机垃圾回收机制

一、相关概念

基本回收算法

  • 引用计数( Reference Counting ) 
    比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为 0 的对象。此算法最致命的是无法处理循环引用的问题。
  • 标记 - 清除( Mark-Sweep ) 
    此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。
  • 复制( Copying ) 
    此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不过出现 ” 碎片 ” 问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。
  • 标记 - 整理( Mark-Compact ) 
    此算法结合了 ” 标记 - 清除 ” 和 ” 复制 ” 两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象 ” 压缩 ” 到堆的其中一块,按顺序排放。此算法避免了 ” 标记 - 清除 ” 的碎片问题,同时也避免了 ” 复制 ” 算法的空间问题。
  • 增量收集( Incremental Collecting ) 
    实施垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因 JDK5.0 中的收集器没有使用这种算法的。
  • 分代( Generational Collecting ) 
    基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从 J2SE1.2 开始)都是使用此算法的。

分代垃圾回收详述

如上图所示,为 Java 堆中的各代分布。

  • Young (年轻代) 
    年轻代分三个区。一个 Eden 区,两个 Survivor 区。大部分对象在 Eden 区中生成。当 Eden 区满时,还存活的对象将被复制到 Survivor 区(两个中的一个),当这个 Survivor 区满时,此区的存活对象将被复制到另外一个 Survivor 区,当这个 Survivor 去也满了的时候,从第一个 Survivor 区复制过来的并且此时还存活的对象,将被复制 ” 年老区 (Tenured)” 。需要注意, Survivor 的两个区是对称的,没先后关系,所以同一个区中可能同时存在从 Eden 复制过来 对象,和从前一个Survivor 复制过来的对象,而复制到年老区的只有从第一个 Survivor 区过来的对象。而且, Survivor 区总有一个是空的。
  • Tenured (年老代) 
    年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。
  • Perm (持久代) 
    用于存放静态文件,如今 Java 类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些 class ,例如 Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过 -XX:MaxPermSize= 进行设置。

举个例子:当在程序中生成对象时,正常对象会在年轻代中分配空间,如果是过大的对象也可能会直接在年老代生成(据观测在运行某程序时候每次会生成一个十兆的空间用收发消息,这部分内存就会直接在年老代分配)。年轻代在空间被分配完的时候就会发起内存回收,大部分内存会被回收,一部分幸存的内存会被拷贝至Survivor的from区,经过多次回收以后如果from区内存也分配完毕,就会也发生内存回收然后将剩余的对象拷贝至to区。等到to区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝至年老区。

GC 类型

GC 有两种类型: Scavenge GC 和 Full GC 

  • Scavenge GC
    一般情况下,当新对象生成,并且在 Eden 申请空间失败时,就好触发 Scavenge GC ,堆 Eden 区域进行 GC ,清除非存活对象,并且把尚且存活的对象移动到Survivor 区。然后整理 Survivor 的两个区。
  • Full GC
    对整个堆进行整理,包括 Young 、 Tenured 和 Perm 。 Full GC 比 Scavenge GC 要慢,因此应该尽可能减少 Full GC 。有如下原因可能导致 Full GC :
  • Tenured 被写满
  • Perm 域被写满
  • System.gc() 被显示调用
  • 上一次 GC 之后 Heap 的各域分配策略动态变化

分代垃圾回收过程演示

 vm-2

vm-3

vm-4

vm-5

二、垃圾回收器

目前的收集器主要有三种:串行收集器、并行收集器、并发收集器 。

  • 串行收集器  

    使用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势,所以此收集器适合单处理器机器。当然,此收集器也可以用在小数据量( 100M 左右)情况下的多处理器机器上。可以使用 -XX:+UseSerialGC 打开。

  • 并行收集器 
  • 对年轻代进行并行垃圾回收,因此可以减少垃圾回收时间。一般在多线程多处理器机器上使用。使用 -XX:+UseParallelGC. 打开。并行收集器在 J2SE5.0 第六 6 更新上引入,在 Java SE6.0 中进行了增强 – 可以堆年老代进行并行收集。如果年老代不使用并发收集的话,是使用单线程进行垃圾回收,因此会制约扩展能力。使用 -XX:+UseParallelOldGC 打开。
  • 使用 -XX:ParallelGCThreads= 设置并行垃圾回收的线程数。此值可以设置与机器处理器数量相等。
  • 此收集器可以进行如下配置:
    • 最大垃圾回收暂停 : 指定垃圾回收时的最长暂停时间,通过 -XX:MaxGCPauseMillis= 指定。 为毫秒 . 如果指定了此值的话,堆大小和垃圾回收相关参数会进行调整以达到指定值。设定此值可能会减少应用的吞吐量。
    • 吞吐量 : 吞吐量为垃圾回收时间与非垃圾回收时间的比值,通过 -XX:GCTimeRatio= 来设定,公式为 1/ ( 1+N )。例如, -XX:GCTimeRatio=19 时,表示 5% 的时间用于垃圾回收。默认情况为 99 ,即 1% 的时间用于垃圾回收。

 

  • 并发收集器 
    可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用。使用 -XX:+UseConcMarkSweepGC 打开。

 

  • 并发收集器主要减少年老代的暂停时间,他在应用不停止的情况下使用独立的垃圾回收线程,跟踪可达对象。在每个年老代垃圾回收周期中,在收集初期并发收集器会对整个应用进行简短的暂停,在收集中还会再暂停一次。第二次暂停会比第一次稍长,在此过程中多个线程同时进行垃圾回收工作。
  • 并发收集器使用处理器换来短暂的停顿时间。在一个 N 个处理器的系统上,并发收集部分使用 K/N 个可用处理器进行回收,一般情况下1<=K<=N/4 。
  • 在只有一个处理器的主机上使用并发收集器,设置为 incremental mode 模式也可获得较短的停顿时间。
  • 浮动垃圾 :由于在应用运行的同时进行垃圾回收,所以有些垃圾可能在垃圾回收进行完成时产生,这样就造成了 ”Floating Garbage” ,这些垃圾需要在下次垃圾回收周期时才能回收掉。所以,并发收集器一般需要 20% 的预留空间用于这些浮动垃圾。
  • Concurrent Mode Failure :并发收集器在应用运行时进行收集,所以需要保证堆在垃圾回收的这段时间有足够的空间供程序使用,否则,垃圾回收还未完成,堆空间先满了。这种情况下将会发生 ” 并发模式失败 ” ,此时整个应用将会暂停,进行垃圾回收。
  • 启动并发收集器 :因为并发收集在应用运行时进行收集,所以必须保证收集完成之前有足够的内存空间供程序使用,否则会出现 ”Concurrent Mode Failure” 。通过设置 -XX:CMSInitiatingOccupancyFraction= 指定还有多少剩余堆时开始执行并发收集

 

  • 小结
    • 串行处理器: 
      – 适用情况:数据量比较小( 100M 左右);单处理器下并且对响应时间无要求的应用。 
      – 缺点:只能用于小型应用
    • 并行处理器: 
      – 适用情况: ” 对吞吐量有高要求 ” ,多 CPU 、对应用响应时间无要求的中、大型应用。举例:后台处理、科学计算。 
      – 缺点:应用响应时间可能较长
    • 并发处理器: 
      – 适用情况: ” 对响应时间有高要求 ” ,多 CPU 、对应用响应时间有较高要求的中、大型应用。举例: Web 服务器 / 应用服务器、电信交换、集成开发环境。

 

三、常见配置举例

  • 堆大小设置 
    JVM 中最大堆大小有三方面限制:相关操作系统的数据模型( 32-bt 还是 64-bit )限制;系统的可用虚拟内存限制;系统的可用物理内存限制。 32 位系统下,一般限制在 1.5G~2G ; 64 为操作系统对内存无限制。我在 Windows Server 2003 系统, 3.5G 物理内存, JDK5.0 下测试,最大可设置为 1478m 。
    典型设置:
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
    -Xmx3550m :设置 JVM 最大可用内存为 3550M 。 
    -Xms3550m :设置 JVM 促使内存为 3550m 。此值可以设置与 -Xmx 相同,以避免每次垃圾回收完成后 JVM 重新分配内存。 
    -Xmn2g :设置年轻代大小为 2G 。 整个堆大小 = 年轻代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小为 64m ,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大, Sun 官方推荐配置为整个堆的 3/8 。 
    -Xss128k :设置每个线程的堆栈大小。 JDK5.0 以后每个线程堆栈大小为 1M ,以前每个线程堆栈大小为 256K 。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000 左右。
  • java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
    -XX:NewRatio=4: 设置年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去持久代)。设置为 4 ,则年轻代与年老代所占比值为 1 : 4 ,年轻代占整个堆栈的 1/5
    -XX:SurvivorRatio=4 :设置年轻代中 Eden 区与 Survivor 区的大小比值。设置为 4 ,则两个 Survivor 区与一个 Eden 区的比值为 2:4,一个 Survivor 区占整个年轻代的 1/6
    -XX:MaxPermSize=16m : 设置持久代大小为 16m 。 
    -XX:MaxTenuringThreshold=0 :设置垃圾最大年龄。 如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代 。对于年老代比较多的应用,可以提高效率。 如果将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象再年轻代的存活时间 ,增加在年轻代即被回收的概论。

 

  • 回收器选择 
    JVM 给了三种选择:串行收集器、并行收集器、并发收集器 ,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下, JDK5.0 以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。 JDK5.0 以后, JVM 会根据当前 系统配置 进行判断。

 

 

 

 

 

 

 

   

四、调优总结

   

五、参考文献

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值