jvm

浅谈JVM垃圾回收

浅谈回收机制以及回收算法

Posted by MasterJen on January 7, 2019

Hey Garbage Collection

Faith can move mountains.–精诚所至,金石为开.

前言

最近忙于项目上线,因此断更了些许文章,不过,后面会给大家带来这次项目上线的一些经验,让更多的开发者,尽量少踩坑,今天就从 JVM 的垃圾回收开始吧.

问题思考

我们都知道 JVM 里面有堆,有栈,栈又分为本地方法栈,虚拟机栈,里面都存有一些数据,那么这里面会不会引发什么问题呢?

栈内存溢出 (StackOverflowError)

栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息.局部变量表又包含基本数据类型,对象引用类型

那么我们可以理解成栈溢出就是方法执行时创建的栈帧超过了栈的深度,那么最可能的方法就是方法的无线递归调用产生的栈溢出.

那么如何解决栈溢出问题呢?

我们需要使用参数 -Xss 去调整 JVM 栈的大小

堆溢出 (OutOfMemoryError)

heap space 表示堆空间,堆中主要存储的是对象.如果不断的 new 对象则会导致堆中的空间溢出

如何解决堆溢出呢?

可以通过 -Xmx4096M 调整堆的总大小

以上便是数据过多引发的问题,那么平时 JVM 是怎么对这些数据进行管理的呢?

JVM 垃圾回收

 垃圾回收就是清除对象,把无用的对象进行处理,节省内存空间.

哪些内存需要回收?

java 堆中存放着几乎所有对象实例,垃圾收集器在堆进行回收前,判断哪些对象还存活着.    

它如何判定是垃圾对象呢?

1.引用计数算法

给对象添加一个引用计数器,当有一个地方引用它,计数器加 1,引用失效时,计数器就减 1,计数器为 0 的对象就不可能被使用

优点: 算法简单,效率高,但当存货对象相互引用就解决不了.所以 Java 中 GC 没有采用引用计数法来管理内存    

2.可达性分析算法

以 GC Roots 对象作为起始点,从这些节点依次向下搜索,如果当前对象到没有任何路径相连时,那么当前对象没有引用.

可以做 GC Roots 的对象: 

    1 java 虚拟机栈中的引用的对象
    
    2 本地方法栈中引用的对象
    
    3 方法区中的常量引用的对象
    
    4 方法去中静态属性引用的对象
    
当对象不可达,并不是宣告对象死亡,还有对象进行最后自我救赎-- finalize

何时回收

young gc 触发条件似乎要简单很多,当 eden 区的内存不够时,就会触发 young gc    

full gc

1.old gen 空间不足

   当创建一个大对象、大数组时,eden 区不足以分配这么大的空间,会尝试在 old gen 中分配,如果这时 old gen 空间也不足时,会触发 full gc 
    
   为了避免上述导致的 full gc,调优时应尽量让对象在 young gc 时就能够被回收,还有不要创建过大的对象和数组

2.统计得到的 young gc 晋升到 old gen 的对象平均总大小大于old gen 的剩余空间

   当准备触发一次 young gc 时,会判断这次 young gc 是否安全,这里所谓的安全是当前老年代的剩余空间可以容纳之前 young gc 晋升对象的平均大小,或者可以容纳 young gen 的全部对象,如果结果是不安全的,就不会执行这次 young gc,转而执行一次 full gc

3.perm gen 空间不足

   如果有 perm gen 的话,当系统中要加载的类、反射的类和调用的方法较多,而且 perm gen 没有足够空间时,也会触发一次 full gc 

4.ygc 出现 promotion failure

   promotion failure 发生在 young gc 阶段,即 cms 的 ParNewGC,当对象的 gc 年龄达到阈值时,或者 eden 的 to 区放不下时.会把该对象复制到 old gen,如果 old gen 空间不足时,会发生 promotion failure,并接下去触发 full gc

如何回收

标记-清除算法

标记清除算法分为”标记“和”清除“两个阶段: 首先标记处所有需要回收的对象,在标记完成后统一回收所有被标记的对象.

缺点 : 

    1.效率问题,标记和清除两个过程的效率都不高
    
    2.空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后的程序在运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作.

复制算法

为了解决效率问题,”复制“算法出现了,将可用的内存划分为两块,每次只使用其中一块, 当这一块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉,这样就不用考虑内存碎片等复杂情况

Hot spot 虚拟机默认使用Eden和Survivor的大小比例是8: 1

标记-整理算法

标记整理算法的“标记”过程和标记-清除算法一致,只是后面并不是直接对可回收对象进行整理,而是让所有存活的对象都向一段移动,然后直接清理掉端边界以外的内存.

分代收集算法

 当前商业虚拟机的垃圾收集都采用”分代收集“算法,其主要思想是将 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法

经典面试题

1.你知道哪几种垃圾收集器,各自的优缺点,重点讲 下 cms,包括原理,流程,优缺点?

串行垃圾收集器: 收集时间长,停顿时间久(单线程收集器,它进行垃圾收集时,暂停其他所有工作的线程,直到收集结束)

并发垃圾收集器: 碎片空间多(使用多线程来通过扫描并压缩堆,可以大幅度压缩停顿时间)
 
CMS收集器: 主要基于标记-清除算法实现,使用多线程算法去扫描并发现未使用的对象进行回收,初始标记(标记 GC Roots 直接关联的对象)、并发标记、并发预清理、并发清除、并发重置.

G1收集器: 主要步骤: 初始标记,并发标记,重新标记,复制清除(整理)它是“标记-整理”算法实现的收集器

缺点:

CMS 的缺点是对 cpu 的要求比较高. G1 是将内存化成了多块,所有对内段的大小有很大的要求

CMS 是清除,所以会存在很多的内存碎片.G1是整理,所以碎片空间较小

2.垃圾回收算法

引用计数,增加一个字段来标记当前应用次数,引用计数为0就可以GC,但不能解决循环引用的问题

可达性: 通过一系列 GC Root 的对象作为起点,向下搜索,搜索所有没有与当前对象 GC ROOT 有引用关系的对象,这是对象就可以 GC

或者用生活的常识,来表述垃圾回收

比如打扫房间时,比较乱,所以各个物品就是所谓的对象,那么有用的物品就留着,没用的,就可以丢弃.

以上就是对 JVM 垃圾回收的一些总结思考,如果大家还有什么好的问题,多请留言哦.