java垃圾回收总结(2)
上一篇文章 介绍了jvm虚拟机运行时内存结构以及如何标识需要回收的对象,这一节主要讲解垃圾回收的基本算法。
基本上 jvm内存回收有三种 基本算法
-
标记-清除
标记清除的算法最简单,主要是标记出来需要回收的对象,然后然后把这些对象在内存的信息清除。如何标记需要回收的对象,在上一篇文章里面已经有说明。
-
标记-清除-压缩
这个算法是在标记-清除的算法之上进行一下压缩空间,重新移动对象的过程。因为标记清除算法会导致很多的留下来的内存空间碎片,随着碎片的增多,严重影响内存读写的性能,所以在标记-清除之后,会对内存的碎片进行整理。最简单的整理就是把对象压缩到一边,留出另一边的空间。由于压缩空间需要一定的时间,会影响垃圾收集的时间。
-
标记-清除-复制
这个算法是吧内存分配为两个空间,一个空间(A)用来负责装载正常的对象信息,,另外一个内存空间(B)是垃圾回收用的。每次把空间A中存活的对象全部复制到空间B里面,在一次性的把空间A删除。这个算法在效率上比标记-清除-压缩高,但是需要两块空间,对内存要求比较大,内存的利用率比较低。适用于短生存期的对象,持续复制长生存期的对象则导致效率降低
由于现在的处理器都是多核的,处理器的性能得到了极大的提升,所以在此基础上有产生了几种垃圾收集算法。主要包括两种算法
- 并行标记清除
所谓并行,就是原来垃圾回收只是一个线程进行。现在创建多个垃圾回收线程。并行的进行标记和清除。比如把需要标记的对象平均分配到多个线程之后,当标记完成之后,多个线程进行清除。
- 并发标记清除
所谓并发,就是应用程序和垃圾回收可以同时执行。在标记清除算法中,在标记对象和清除对象,以及压缩对象的情况下是需要暂停应用的。那么并行标记清除压缩算法则是在标记清除压缩算法的基础上,把标记清除压缩算法分为以下几个过程
初始标记->并发标记->重新标记->并发清除->重置
以上几种算法是垃圾回收的基本算法,jvm垃圾回收就是在以上几种算法为基础的,在以上几种算法的基础上,java垃圾回收器可以分为以下几种:
- 串行收集器
用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势,所以此收集器适合单处理器机器
单线程收集器。在目前多核服务器端运行的情况下,效率比较低。比较适合堆内存小的情况下使用。
- 并行收集器
用多线程处理所有垃圾回收工作,利用多核处理器的优势。但是如果线程数量过多,导致线程之间频繁调度,也会影响性能。一半并行收集的线程是处理器的个数。
“对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用。举例:后台处理、科学计算。
- 并发收集器
并发收集器主要减少年老代的暂停时间,他在应用不停止的情况下使用独立的垃圾回收线程,跟踪可达对象。在每个年老代垃圾回收周期中,在收集初期并发收集器 会对整个应用进行简短的暂停(初始标记的过程),在收集中还会再暂停一次。第二次暂停会比第一次稍长(重新标记的过程),在此过程中多个线程同时进行垃圾回收工作。
并发收集器使用处理器换来短暂的停顿时间。在一个N个处理器的系统上,并发收集部分使用K/N个可用处理器进行回收,一般情况下1<=K<=N/4。
在只有一个处理器的主机上使用并发收集器,设置为incremental mode模式也可获得较短的停顿时间。
浮动垃圾:由于在应用运行的同时进行垃圾回收,所以有些垃圾可能在垃圾回收进行完成时产生,这样就造成了“Floating Garbage”,这些垃圾需要在下次垃圾回收周期时才能回收掉。所以,并发收集器一般需要20%的预留空间用于这些浮动垃圾。
Concurrent Mode Failure:并发收集器在应用运行时进行收集,所以需要保证堆在垃圾回收的这段时间有足够的空间供程序使用,否则,垃圾回收还未完成,堆空间先满了。这种情况下将会发生“并发模式失败”,此时整个应用将会暂停,进行垃圾回收。
并发收集器,在垃圾回收的时候采用并发标记清除算法的收集器
对响应时间要求高的,多CPU,大型应用。比如页面请求/web服务器。前端业务系统用的比较多。
我们以sun公司的hotspot虚拟机为例,hotspot采用的是分代回收算法,因为根据经验,一般80%的对象就是朝生夕灭,对象的生命期都很短。而根据对象的生命周期不同,可以划分出来几个区域,一部分区域的对象创建比较频繁,但是生命周期比较短,一部分区域对象生命周期比较长,还有一部分区域对象生命周期几乎和java虚拟机相同。
sunspot堆内存分为三个区域:
- 新生代(Young)
新生代包括两个区:Eden区和Survivor区,其中Survivor区一般也分成两块,简称Survivor1 Space 和 Survivor2 Space (或者From Space 和 To Space)。新生代通常存活时间较短,因此基于标记清除复制算法来进行回收,扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和From或To之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到Survior,最后到旧生代。
可以采用串行处理和并行处理器
- 老年代(Old)
在垃圾回收多次,如果对象仍然存活,并且新生代的空间不够,则对象会存放在老年代。
在老年代采用的是 标记清除压缩算法。因为老年代的对象一般存活时间比较长,每次标记清除之后,会有很多的零碎空间,这个就是所谓的浮动垃圾。当老年代的零碎空间不足以分配一个大的对象的时候,就会采用压缩算法。在压缩的时候,应用需要暂停。
可以采用串行处理,并行处理器,以及并发处理器。
- 持久代(Permanent)
这部分空间主要存放java方法区的数据以及启动类加载器加载的对象。这一部分对象通常不会被回收。所以持久代空间在默认的情况下是不会被垃圾回收的。
由于把内存空间分为三块,一般把新生代的GC称为minor GC ,把老年代的GC成为 full GC,所谓full gc 会先出发一次minor gc,然后在进行老年代的GC。
具体的过程如下:
首先想eden区申请分配空间,如果空间够,就直接进行分配,否则进行一次Minor GC。minor GC 首先会对Eden区的对象进行标记,标记出来存活的对象。然后把存活的对象copy到From空间。如果From空间足够,则回收eden区可回收的对象。如果from内存空间不够,则把From空间存活的对象复制到To区,如果TO区的内存空间也不够的话,则把To区存活的对象复制到老年代。如果老年代空间也不够(或者达到触发老年年垃圾回收条件的话)则触发一次full GC。
简单方向就是Eden->From->To->Old,如下图所示。
GodIsCoder
博客园blog地址:http://www.cnblogs.com/aigongsi/
独立Blog: God Is Coder
个人网站: iphone发码网
本人版权归作者和博客园所有,欢迎转载,转载请注明出处
leetcode17 Letter Combinations of a Phone Number
leetcode13 Roman to Integer
leetcode14 Longest Common Prefix
leetcode20 Valid Parentheses
leetcode392 Is Subsequence
leetcode121 Best Time to Buy and Sell Stock
leetcode198 House Robber
leetcode746 Min Cost Climbing Stairs
tomcat下使用druid配置jnid数据源