垃圾回收(二)

如何判断对象为垃圾对象:
  1. 引用计数法:   优点:实现简单,判断效率高
                                缺点:无法解决循环引用问题
 
  1. 可达性分析: GC Roots到某个对象不可达
                       可用于GC Roots的对象可以是:
                       GC Roots虚拟机栈的引用对象,
                       方法区的静态属性和常量引用的对象,
                       本地方法栈中的引用对象
                       优点:准确且严谨,解决循环引用的问题
                       缺点:实现复杂,需要分析大量数据,消耗大量时间、分析过程需要GC停顿(引用关系不能发生变化),即停顿所有Java执行线程(称为"Stop The World",是垃圾回收重点关注的问题)。
  1. 引用
          
 
定位垃圾:  
       定位垃圾-是垃圾回收的关键点,无用的对象占用的堆空间即是垃圾,那就需要先定位无用的对象,这里的无用是不再使用的意思,咋判断呢?文中介绍了两种方法,计数法和标记法(GC root)核心在于能定位出无用的对象,后出现的方法往往比早出现的更好一点,这里也一样,标记法能解决计数法,解决不了的循环引用不能回收的问题,但是也存在其他的问题,误报和漏报的问题,误报浪费点垃圾回收的机会浪费点空间,漏报在多线程并发工作时可能会死JVM的,所以,比较严重,所以,JVM采用了简单粗暴的stop-the-world的方式来对待,所以,老年代的回收有卡顿的现象
 
 
垃圾回收的方式:
清除法---简单,但易产生碎片,可能总空间够但分配不了的问题
压缩法---能解决清除法的问题,但是复杂且耗性能
复制法---折衷一些,但是空间利用率低
 
垃圾回收的算法:
  1. 标记-清除算法
           
      优点:基础最基础的可达性算法,后续的收集算法都是基于这种思想实现的
  缺点:标记和清除效率不高,产生大量不连续的内存碎片,导致创建大对象时找不到连续的空间,不得不提前触发另一次的垃圾回收。
      2.  标记-复制算法
       
    优点:实现简单,效率高。解决了标记-清除算法导致的内存碎片问题。
  缺点:代价太大,将内存缩小了一半。效率随对象的存活率升高而降低。
现在商业虚拟机改良了1:1分配内存的缺点:
     Hotspot虚拟机默认Eden和Survivor的大小比例是8:1。
      3.  标记-压缩(整理)算法
           
  优点:不会像复制算法那样随着存活对象的升高而降低效率,不像标记-清除算法那样产生不连续的内存碎片
  缺点:效率问题,除了像标记-清除算法的标记过程外,还多了一步整理过程,效率更低。
分代回收的方式:
          把堆空间分成新生代(存活率低用复制)和老年代(存活率高用清除和整理)
          新生代只认为用来存储新建的对象。
          当对象存活时间够长时,则将其移动到老年代。
         
新生代分配方式:
          java虚拟机采用一种动态分配的策略,动态调整Eden区和survivor区的比例
           分配步骤:
          (1)当new的时候,会从Eden区划出一块内存存储对象,但是由于堆内存共享,所以要进行同步操作,jvm采用了TLAB的方式,让每个线程去分配,如果TLAB空间不够大,会向Eden区重新申请。
          (2)当Eden区耗尽的时候,会触发一次Minor GC,来收集新生代的垃圾,存活后的对象会被复制到 to指向的survivor区,Minor GC下from指向的survivor区存活的对象会被复制到to指向的survivor区,然后to和 from对调,保证to指向的survivor永远都是空的
          (3)survivor区的对象被复制了15次之后会被晋升到老年代
          (4) 发生Minor GC,我们使用标记-复制算法
         
 
针对新生代的垃圾回收器有如下三个:Serial、Parallel Scavenge、Parallel New,他们采用的都是标记-复制的垃圾回收算法。
 
针对老年代的垃圾回收器有如下三个:Serial Old 、Parallel Old ,他们使用的都是标记-压缩的垃圾回收算法。
 
CMS  使用的是标记-清除算法
 
G1 整体上使用了标记-压缩算法,局部使用了标记-复制算法实现的
 
几个概念:
 
TLAB(Thread Local Allocation Buffer)-这个技术是用于解决多线程竞争堆内存分配问题的,核心原理是对分配一些连续的内存空间
 
并行收集:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。
 
并发收集:指用户线程与垃圾收集线程同时工作(不一定是并行的可能会交替执行)。用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。
 
吞吐量: 代码运行时间/(代码运行时间+垃圾收集时间) = 吞吐量
 
垃圾回收器:

(黑线线表示两个回收器可搭配使用,红线则表示两者可以在同一区域交替使用)
 
新生代垃圾回收器:
 
Serial:从名字就能看出是串行的意思,该回收器是最早实现的,基于单线程,实现简单且效率高,但是进行垃圾回收是会造成“Stop-the-World”(STW),当回收内存区域较大时,就会造成程序响应时间变长。
 
应用场景: 桌面应用场景
适用于Client模式下的虚拟机
ParNew:  其实就是Serial多线程版本
除了使用多线程外其余行为均和Serial收集器一模一样(参数控制、收集算法、Stop The World、对象分配规则、回收策略等)。
特点: 多线程收集
应用场景:ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为它是除了Serial收集器外,唯一一个能与CMS收集器配合工作的。
Parallel Scavenge:并行清理,也就是并行垃圾回收器。该回收器与ParNew的最大区别在于ParNew通常与CMS搭配,一般注重于减少垃圾回收的停顿时间,提高响应速度,而Parallel Scavenge则侧重于吞吐量的控制,又名"吞吐量优先"回收器(吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间);响应时间影响用户体验,而吞吐量则影响CPU利用率,进而影响程序程序运算效率)。此外需注意,该回收器不能够和CMS搭配使用。
 
应用场景:   
高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间;  
执行批量处理、订单处理、工资支付、科学计算的应用程序  
不需要太多交互的场景
 
 
Serial Old:没啥好说Serial的年老代版。
特点:同样是单线程收集器
应用场景:主要也是使用在Client模式下的虚拟机中。也可在Server模式下使用。
Server模式下主要的两大用途:
  1. 在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用。
  2. 作为CMS收集器的后备方案,在并发收集Concurent Mode Failure时使用。
Parallel Old:Parallel Scavenge的年老代版。
应用场景:注重高吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old 收集器。
CMS: Concurrent Mark Sweep 是一种以获取最短回收停顿时间为目标的收集器。
整个过程分为四部分:
 
  • 初始标记: 暂停所有的其他线程,并记录下直接与root相连的对象,速度很快 ;
  • 并发标记: 同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
  • 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
  • 并发清除: 开启用户线程,同时GC线程开始对为标记的区域做清扫。
 
   CMS回收器的缺点主要在三方面:1、占用一部分CPU资源,导致吞吐量下降;2、由于并发过程是回收和程序运行交替进行,会产生一些新垃圾进入年老代而未被清理 ,当年老代满时引起Full GC(因此一般年老代要预留一部分空间供程序使用);3、标记-清理的缺点——空间碎片,因此要适时进行压缩。 
 
应用场景:
 
      与用户交互较多的场景;       
      希望系统停顿时间最短,注重服务的响应速度;
      以给用户带来较好的体验;
      如常见WEB、B/S系统的服务器上的应用;
 
 
G1(Garbage-First) 
 
  • 并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
  • 分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。
  • 空间整合:与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
  • 可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点,但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内。
G1收集器的运作大致分为以下几个步骤:
 
  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收
G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了GF收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
 
应用场景:
 
      面向服务端应用,针对具有大内存、多处理器的机器;
      最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案;
      如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒;
 
      用来替换掉JDK1.5中的CMS收集器;
      在下面的情况时,使用G1可能比CMS好:
      (1)、超过50%的Java堆被活动数据占用;
      (2)、对象分配频率或年代提升频率变化很大;
      (3)、GC停顿时间过长(长于0.5至1秒)。
      是否一定采用G1呢?也未必:
      如果现在采用的收集器没有出现问题,不用急着去选择G1;
      如果应用程序追求低停顿,可以尝试选择G1;
      是否代替CMS需要实际场景测试才知道。

posted @ 2019-12-07 15:48  MR Li C  阅读(174)  评论(0编辑  收藏  举报