Garbage Collector

gc垃圾回收,整理内存
 

垃圾回收器只知道释放那些new分配的内存。

用于处理那些不是java创建对象的内存。
 
  • 对新生代的GC 是轻量级的GC (又叫 minor GC)速度很快,一般零点几秒就完成了。
  • 对旧生代的GC 是重量级得GC (又叫 full GC)速度很慢。触发情况: 旧生栈 内存使用达到70% (可以自己调参数) 新生代满了 等等
 
对于程序员来说,分配对象使用new关键字;释放对象时,只要将对象所有引用赋值为null,让程序不能够再访问到这个对象,我们称该对象为"不可达的"。GC将负责回收所有"不可达"对象的内存空间。
 
垃圾收集的优先级是非常低的。几乎低于所有的线程。因此大部分时间都处于等待状态。
只有两种情况下启动:(1)内存紧张 (2)系统闲置
Garbage Collector - DaySpring - DaySpring
 
Garbage Collector - DaySpring - DaySpring
 
protected void finalize(){
super.finalize();
}
System.gc();
 
用finalize释放资源不是很好原因有三,
  • 其一,GC为了能够支持finalize函数,要对覆盖这个函数的对象作很多附加的工作。
  • 其二,在finalize运行完成之后,该对象可能变成可达的,GC还要再检查一次该对象是否是可达的。因此,使用finalize会降低GC的运行性能。
  • 其三,由于GC调用finalize的时间是不确定的,因此通过这种方式释放资源也是不确定的。
 
通常,finalize用于一些不容易控制、并且非常重要资源的释放,例如一些I/O的操作,数据的连接。这些资源的释放对整个应用程序是非常关键的。在这种情况下,程序员应该以通过程序本身管理(包括释放)这些资源为主,以finalize函数释放资源方式为辅,形成一种双保险的管理机制,而不应该仅仅依靠finalize来释放资源。
 
技巧:
  • 1:最基本的建议就是尽早释放无用对象的引用。大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域(scope)后,自动设置为null。我们在使用这种方式时候,必须特别注意一些复杂的对象图,例如数组,队列,树,图等,这些对象之间有相互引用关系较为复杂。对于这类对象,GC回收它们一般效率较低。如果程序允许,尽早将不用的引用对象赋为null。这样可以加速GC的工作。
  • 2:尽量少用finalize函数。finalize函数是Java提供给程序员一个释放对象或资源的机会。但是,它会加大GC的工作量,因此尽量少采用finalize方式回收资源。
  • 3:如果需要使用经常使用的图片,可以使用soft应用类型。它可以尽可能将图片保存在内存中,供程序调用,而不引起OutOfMemory。
  • 4:注意集合数据类型,包括数组,树,图,链表等数据结构,这些数据结构对GC来说,回收更为复杂。另外,注意一些全局的变量,以及一些静态变量。这些变量往往容易引起悬挂对象(dangling reference),造成内存浪费。
  • 5:当程序有一定的等待时间,程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。使用增量式GC可以缩短Java程序的暂停时间。增量式GC在整体性能上可能不如普通GC的效率高,但是它能够减少程序的最长停顿时间。
Garbage Collector - DaySpring - DaySpring
 
垃圾回收的算法
  • 引用计数法(Reference Counting Arithmetic)
  • 引用计数法是唯一没有使用根集的垃圾回收的法,该算法使用引用计数器来区分存活对象和不使用的对象。一般来说,堆中的每个对象对应一个引用计数器。当每一次创建一个对象并赋给一个变量时,引用计数器置为1。当对象被赋给任意变量时,引用计数器每次加1当对象出了作用域后(该对象丢弃不再使用),引用计数器减1,一旦引用计数器为0,对象就满足了垃圾收集的条件。此算法最致命的是无法处理循环引用的问题。 
  •  
  • tracing算法(Tracing Arithmetic)
  • tracing算法是为了解决引用计数法的问题而提出,它使用了根集的概念。基于tracing算法的垃圾收集器从根集开始扫描,识别出哪些对象可达,哪些对象不可达,并用某种方式标记可达对象,例如对每个可达对象设置一个或多个位。在扫描识别过程中,基于tracing算法的垃圾收集也称为标记和清除(mark-and-sweep)垃圾收集器.
  •  
  • compacting算法(Compacting Arithmetic)
  • 为了解决堆碎片问题,基于tracing的垃圾回收吸收了Compacting算法的思想,在清除的过程中,算法将所有的对象移到堆的一端,堆的另一端就变成了一个相邻的空闲内存区,收集器会对它移动的所有对象的所有引用进行更新,使得这些引用在新的位置能识别原来的对象。在基于Compacting算法的收集器的实现中,一般增加句柄和句柄表
  • copying算法(Coping Arithmetic)
  • 该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分成一个对象 面和多个空闲面,程序从对象面为对象分配空间,当对象满了,基于coping算法的垃圾 收集就从根集中扫描活动对象,并将每个活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。一种典型的基于coping算法的垃圾回收是stop-and-copy算法,它将堆分成对象面和空闲区域面,在对象面与空闲区域面的切换过程中,程序暂停执行。此算法的缺点也是很明显的,就是需要两倍内存空间
  •  
  • 标记-整理(Mark-Compact)
  • 此算法结合了"标记-清除"和"复制"两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象"压缩"到堆的其中一块,按顺序排放。此算法避免了"标记-清除"的碎片问题,同时也避免了"复制"算法的空间问题。
  •  
  • generation算法(Generational Arithmetic)
  • stop-and-copy垃圾收集器的一个缺陷是收集器必须复制所有的活动对象,这增加了程序等待时间,这是coping算法低效的原因。在程序设计中有这样的规律:多数对象存在的时间比较短,少数的存在时间比较长。因此,generation算法将堆分成两个或多个,每个子堆作为对象的一代(generation)。由于多数对象存在的时间比较短,随着程序丢弃不使用的对象,垃圾收集器将从最年轻的子堆中收集这些对象。在分代式的垃圾收集器运行后,上次运行存活下来的对象移到下一最高代的子堆中,由于老一代的子堆不会经常被回收,因而节省了时间。
  •  
  • adaptive算法(Adaptive Arithmetic)
  • 在特定的情况下,一些垃圾收集算法会优于其它算法。基于Adaptive算法的垃圾收集器就是监控当前堆的使用情况,并将选择适当算法的垃圾收集器。
 
垃圾收集器 
  垃圾收集器就是收集算法的具体实现,不同的虚拟机会提供不同的垃圾收集器。并且提供参数供用户根据自己的应用特点和要求组合各个年代所使用的收集器。本文讨论的收集器基于Sun Hotspot虚拟机1.6版。
Garbage Collector - DaySpring - DaySpring
图1展示了1.6中提供的6种作用于不同年代的收集器,两个收集器之间存在连线的话就说明它们可以搭配使用。在介绍着些收集器之前,我们先明确一个观点:没有最好的收集器,也没有万能的收集器,只有最合适的收集器。 
 
1.Serial收集器 
  单线程收集器,收集时会暂停所有工作线程(我们将这件事情称之为Stop The World,下称STW),使用 复制收集算法,虚拟机运行在Client模式时的默认新生代收集器。
2.ParNew收集器 
  ParNew收集器就是Serial的 多线程版本,除了使用多条收集线程外,其余行为包括算法、STW、对象分配规则、回收策略等都与Serial收集器一摸一样。对应的这种收集器是虚拟机运行在Server模式的默认新生代收集器,在单CPU的环境中,ParNew收集器并不会比Serial收集器有更好的效果。 
3.Parallel Scavenge收集器 
PS收集器也是一个 多线程收集器,也是使用 复制算法,但它的对象分配规则与回收策略都与ParNew收集器有所不同,它是以吞吐量最大化(即 GC时间占总运行时间最小)为目标的收集器实现,它允许较长时间的STW换取总吞吐量最大化。 
4.Serial Old收集器 
  Serial Old是单线程收集器,使用 标记-整理算法,是老年代的收集器,上面三种都是使用在新生代收集器。
5.Parallel Old收集器 
  老年代版本吞吐量优先收集器,使用多线程和 标记-整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的话,老年代除Serial Old外别无选择,因为PS无法与CMS收集器配合工作。 
6.CMS(Concurrent Mark Sweep)收集器 
  CMS是一种以 最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间,这一点对于实时或者高交互性应用(譬如证券交易)来说至关重要,这类应用对于长时间STW一般是不可容忍的。CMS收集器使用的是 标记-清除算法,也就是说它在运行期间会产生空间碎片,所以虚拟机提供了参数开启CMS收集结束后再进行一次内存压缩。 
 
基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代。 
  • 1. Young(年轻代) 
  • 年轻代分三个区。一个Eden区,两个 Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。
  •  2. Tenured(年老代) 
  • 年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。 
  • 3. Perm(持久代) 
  • 用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。
 
GC有两种类型:
  • 1. Scavenge GC 
  • 一般情况下,当新对象生成,并且在Eden申请空间失败时,就好触发Scavenge GC,堆Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。
  •  2. Full GC 
  • 对整个堆进行整理,包括Young、Tenured和Perm。Full GC比Scavenge GC要慢,因此应该尽可能减少Full GC。
  • * Tenured被写满 
  • * Perm域被写满 
  • * System.gc()被显示调用 
  • * 上一次GC之后Heap的各域分配策略动态变化 
 
调优总结:
  • 1. 年轻代大小选择 
  • * 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
  •  * 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。 
  • 2. 年老代大小选择 
  • * 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
  • o 并发垃圾收集信息 
  • o 持久代并发收集次数 
  • o 传统GC信息 
  • o 花在年轻代和年老代回收上的时间比例 
  • 减少年轻代和年老代花费的时间,一般会提高应用的效率 
  • * 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。 
  • 3. 较小堆引起的碎片问题 
  • 因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
  •  * -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。 
  • * -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩 
 

猜你喜欢

转载自www.cnblogs.com/xiaowater/p/9554486.html