转载来自博客园:https://www.cnblogs.com/yclblogs/p/15925418.html

一、如何判断对象可以可以被回收

1.1 引用计数法

 定义:只要一个对象被变量所引用,则该对象计数就+1,若被引用了两次,则它的引用计数就变为2,如果某一个变量不再引用它了,则它的引用计数就减一,当该对象的引用变为0的时候就表示没有变量引用它了,该对象就可以被当作垃圾回收了。

 弊端:当两个对象循环引用时候,但是又没有被别的变量引用,并且这两个对象不在有实用价值,这时,垃圾回收并不能够回收这两个对象,可能导致內存溢出。

1.2 可达性分析算法

 根对象:那些肯定不能被当成垃圾回收的对象

 定义:在垃圾回收之前,先对堆中的所有对象进行一次扫描,判断每一个对象是否被根对象直接或者间接的引用,如果是,则不能被回收,反之则可以被当成垃圾回收

 Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象

 扫描堆中的对象,看是否能够沿着GC Root对象(一系列对象)为起点的引用链找到该对象,找不到,表示可以回收

 哪些对象可以作为GC Root ?

  虚拟机栈(栈帧中的本地变量表)中引用的对象。
  方法区中类静态属性引用的对象。.
  方法区中常量引用的对象。
  本地方法栈中JNI(即-般说的Native方法)引用的对象。

1.3 四种引用

 1.强引用
  只有所有GC Roots对象都不通过[强引用]引用该对象,该对象才能被垃圾回收
 2.软引用(SoftReference)
  仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
  可以配合引|用队列来释放软引用自身
 3.弱引用(WeakReference)
  仅有弱弓|用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
  可以配合引|用队列来释放弱引用自身
 4.虚引用(PhantomReference)
  必须配合引用队列使用,主要配合ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚弓|用相关方法释放直接内存
 5.终结器引用(FinalReference)
  无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被弓|用对象暂时没有被回收), 再由Finalizer线程通过终结器引用找到被引|用对象并调用它的finalize方法,第二_次GC时
  才能回收被引|用对象

二、垃圾回收算法

2.1 标记清除

  特点:速度较快、会造成內存碎片

2.2 标记整理

  特点:速度慢、没有內存碎片

2.3 复制

  特点:没有內存碎片、需要占用双倍内存空间

三、分代垃圾回收

3.1 分代垃圾回收机制

  对象首先分配在伊甸园区域
  新生代空间不足时,触发minor gc,伊甸园和from存活的对象使用copy复制到to中,存活的对象年龄加1并且交换from to
  minor gc会引发stop the world, 暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
  当对象寿命超过阈值时,会晋升至老年代,最大寿命是15 (4bit)
  当老年代空间不足,会先尝试触发minor gc,如果之后空间仍不足,那么触发full gc, stw的时间更长

3.2 相关VM参数

四、垃圾回收器

4.1串行 

  单线程
  堆内存较小,适合个人电脑

4.2吞吐量优先

  多线程
  堆内存较大,多核cpu
  让单位时间内,STW的时间最短0.2 0.2=0.4

4.3响应时间优先

  多线程
  堆内存较大,多核cpu
  尽可能让单次STW的时间最短0.1 0.10.1 0.10.1=0.5

4.4G1

适用场景
  同时注重吞吐量(Throughput) 和低延迟(Low latency) ,默认的暂停目标是200 ms
  超大堆内存,会将堆划分为多个大小相等的Region
  整体上是标记+整理算法,两个区域之间是复制算法
相关JVM参数
-XX:UseG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time

G1垃圾回收阶段

  1)Young Collection

  2)Young Collection + CM

  在Young GC时会进行GC Root的初始标记
  老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),由下面的JVM参数决定
  -XX:InitiatingHeapOccupancyPercent=percent (默认45%)

  3)Mixed Collection 

   会对E、S、O进行全面垃圾回收
  最终标记(Remark) 会STW
  拷贝存活(Evacuation) 会STW
  - XX:MaxGCPauseMillis=ms

FullGC 

■SerialGC
  新生代内存不足发生的垃圾收集- minor gc
  老年代内存不足发生的垃圾收集- full gc
■ParallelGC
  新生代内存不足发生的垃圾收集- minor gc
  老年代内存不足发生的垃圾收集- full gc
■CMS
  新生代内存不足发生的垃圾收集- minor gc
  老年代内存不足:并发失败以后,才叫Full GC,否则不会触发Full GC
■G1
  新生代内存不足发生的垃圾收集- minor gc
  老年代内存不足:当老年代內存跟堆內存占比达到45%以上,会触发并发标记的阶段,以及后续混合收集的阶段,如果垃圾回收的速度比新产生的垃圾的速度要快,来的及打扫,这是还不叫Full GC,还是并发垃圾回收的阶段(也会有暂停,但是时间很短), 当垃圾回收的速度跟不上垃圾产生的速度,并发收集就会失败,转化为Full GC(并发进行),stw时间也会更长。

Young Collection 跨代引用

  卡表与Remembered Set
  在引用变更时通过post-write barrier + dirty card queue
  concurrent refinement threads更新Remembered Set

Remark 

 

JDK 8u20 字符串去重 

  优点:节省大量内存
  缺点:略微多占用了cpu时间,新生代回收时间略微增加
  -XX: +UseStringDeduplication   默认开启

  将所有新分配的字符串放入一个队列
  当新生代回收时, G1并发检查是否有字符串重复
  如果它们值一样,让它们引用同-一个char[]
  注意,与String. intern()不- -样
  String. intern()关注的是字符串对象
  而字符串去重关注的是char[]
  在JVM内部,使用了不同的字符串表

JDK 8u40 并发标记类卸载 

  所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它
  所加载的所有类
  -XX:+ClassUnloadingWi thConcurrentMark默认启用

JDK 8u60 回收巨型对象 

  一个对象大于region的一半时,称之为巨型对象
  G1不会对巨型对象进行拷贝
  回收时被优先考虑
  G1会跟踪老年代所有incoming引用,这样老年代incoming引用为0的巨型对象就可以在新生代垃圾回收时处理掉

 

 

JDK 9  并发标记起始时间的调整 

  并发标记必须在堆空间占满前完成,否则退化为FullGC
  JDK9之前需要使用-XX:InitiatingHeap0ccupancyPercent
  JDK 9可以动态调整
    -XX: Initiat ingHeapOccupancyPercent用来设置初始值
    进行数据采样并动态调整
    总会添加一个安全的空档空间

JDK 9 更高效的回收 

  250+增强

  180+bug修复

五、垃圾回收调优 

  预备知识
     掌握GC相关的VM参数,会基本的空间调整
     掌握相关工具
     明白一点:调优跟应用、环境有关,没有放之四海而皆准的法则

5.1 调优领域 

  ■内存
  ■锁竞争
  ■cpu占用
  ■io

5.2 确定目标 

  [低延迟]还是[高吞吐量],选择合适的回收器
  CMS,G1(JDK9,在更大的內存下工作的比CMS要好),ZGC   (低延迟)
  ParallelGC   (高吞吐量)

  Zing(stw 0停顿、可管理超大的內存)

       互联网项目主要是针对 低延迟

5.3 最快的GC是不发生GC 

  查看FullGC前后的内存占用,考虑下面几个问题
  数据是不是太多?
             resultSet = statement.executeQuery("select * from大表limit n")
  数据表示是否太臃肿?
       对象图
       对象大小16 Integer 24 int 4
  是否存在内存泄漏?
       static Map map =
     软 
       弱
       第三方缓存实现

5.4 新生代调优  

新生代的特点 

  所有的new操作的内存分配非常廉价
      TLABlthread-local allocation buffer
  死亡对象的回收代价是零
  大部分对象用过即死
  Minor GC的时间远远低于Full GC

  新生代能容纳所有[并发量* (请求-响应)]的数据

  幸存区大到能保留[当前活跃对象+需要晋升对象]

  升阈值配置得当,让长时间存活对象尽快晋升
  -XX:MaxTenuringThreshold=threshold
  -XX:+PrintTenuringDistribution

  

5.5 老年代调优 

    以CMS为例

  ■CMS的老年代内存越大越好
  ■先尝试不做调优,如果没有Full GC那么已经...否则先尝试调优新生代
  ■观察发生Full GC时老年代内存占用,将老年代内存预设调大1/4~ 1/3
  ■-XX: CMSInitiatingOccupancyFraction=percent

5.6 调优案例  

案例1  Full GC和Minor GC频繁  

 分析:GC频繁说明空间紧张,究竟是哪一空间紧张呢,如果是新生代,当业务高峰期来了,大量的对象被创建,新生代的空间很快就满了,幸存区的空间紧张,那么它的最大晋升阈值就会降低,导致很多本来生存周期很短的对象,也会晋升到老年代去,这样情况就进一步恶化,这样就导致老年代的Full GC频繁发生。

 解决:通过检测工具去观察堆空间的大小,发现新生代的內存设置的太小了,先试着增大新生代的內存,新生代的內存充裕了之后,新生代的垃圾回收就变得不那么频繁了,同时增大了幸存区的空间,以及晋升阈值,这样就能够使得生命周期较短的对象尽可能的留在新生代里,这样就可以让老年代的FullGC也不那么频繁了。

案例2  请求高峰期发生Full GC,单次暂停时间特别长(CMS)

 分析:已确定垃圾回收器是CMS,先去查看GC日志,看看CMS哪个阶段耗时比较长,CMS在重新标记的时候要扫描整个堆内存,如果业务高峰期的时候,新生代的对象较多标记时间会变的很长。

 解决:在重新标记之前对新生代先做一次垃圾回收,减少新生代对象的数量,这样就可以减少在重新标记时所耗费的时间。    - XX:+CMSScavengeBeforeRemark

案例3  老年代充裕情况下发生Full GC(JDK1.7  CMS)

 分析:之前介绍过CMS可能由于空间不足,导致并发失败,或者是空间碎片比较多,会产生Full GC;若日志排查后,在GC日志里没有并发失败,碎片过多的错入提示,说明老年代的空间是充裕的,不是老年代空间不足产生的Full GC。从案例中可知项目所用的JDK为1.7,JDK1.8是元空间作为方法区的实现,JDK1.7及以前是永久代作为方法区的实现,JDK1.7以前的永久代空间不足也会导致Full GC,JDK1.8以后,元空间不再由JAVA控制,元空间的默认情况下他的內存空间是使用了操作系统的內存空间,空间的容量一般是比较充裕的。而JDK1.7以前永久代的空间如果设小了,就会导致触发整个堆的Full GC。

 解决:增大永久代的最大值和初始值。