【JVM】垃圾回收算法

垃圾回收

垃圾回收(Garbage Collecting ,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可使用的内存,对内存堆中已经死亡或者长时间没有使用的对象进行清除和回收。垃圾回收机制就是java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据内存空间的一种机制。java

判断对象是否存活的算法

一、引用计数法

引用计数法的作法是为每一个对象添加一个引用计数器,用来统计指向该对象的引用个数,当有地方引用该对象时计数器加1,当引用失效时计数器减1,用对象计数器是否为0来判断对象是否可被回收。一旦某个对象的引用计数器为0,则说明该对象已经死亡,即可以回收了。算法

(1)具体实现
若是有一个引用,被赋值为某一对象,那么该对象的引用计数器 + 1 。若是一个指向某一对象的引用,被赋值为其余值,那么将该对象的引用计数器 - 1 。也就是说,咱们须要截获全部的引用更新操做,而且相应的增减目标对象的引用计数器。多线程

(2)缺点
除了须要额外的空间来存储计数器,以及繁琐的更新操做,引用计数法还有一个重大的漏洞——没法处理循环引用对象。ide

举个例子,假设对象 a 与 b 相互引用,除此以外没有其余引用指向 a 或者 b 。在这种状况下,a 和 b 实际上已经死了,但因为它们的引用计数器皆不为0,因此这些循环引用对象所占据的空间将不可回收,从而形成了内存泄露。
在这里插入图片描述ui

二、可达性分析

目前Java虚拟机的主流垃圾回收器采起的是可达性分析算法。可达性分析算法的实质在于将一系列GC Roots 做为初始的存活对象合集(live set),而后从该合集出发,探索全部可以被该集合引用到的对象,并将其加入到该集合中,这个过程也称之为标记(Mark)。最终,未被探索到的对象即是死亡的,是能够回收的。spa

![在这里插入图片描述](https://img-blog.csdnimg.cn/20200707181157852.png =500px)
可达性分析能够解决引用计数法不能解决的循环引用问题。还拿上面例子说明,即使对象 a 和 b 相互引用,只要从GC Roots出发没法到达 a 或者 b ,那么可达性分析便不会将它们加入存活对象合集中。线程

(1)GC Roots
暂时理解为由堆外指向堆内的引用,通常来讲,GC Roots包括如下几种:指针

  • Java方法栈帧中的局部变量
  • 已加载类的静态变量
  • JNI handles
  • 已启动且未中止的Java线程

(2)缺点对象

在多线程环境下,其余线程可能会更新已经访问过的对象中的引用,从而形成误报(将引用设置为null),或者漏报(将引用设置为未被访问过的对象)。blog

若是发生了误报,Java虚拟机最多损失了部分垃圾回收的机会。漏报比较麻烦,由于垃圾回收器可能回收事实上仍被引用的对象内存。一旦从原引用访问已经被回收了的对象,则颇有可能会直接致使Java虚拟机崩溃。

经常使用垃圾收集(GC)算法

一、标记-清除算法

分为两个步骤:第一就是标记,也就是标记全部须要回收的对象;第二就是清理,标记完成后进行统一的回收带有标记的对象占据的内存空间。这个算法效率不高,并且在标记清除以后会产生大量的内存不连续的内存碎片,当程序运行过程当中须要分配较大对象时,没法找到足够的连续内存而形成内存空间浪费。
在这里插入图片描述

缺点
(1)形成内存碎片。因为Java虚拟机的堆中对象必须是连续分布的,所以可能出现总空闲内存足够,可是没法分配的极端状况。
(2)分配效率低。若是是一块连续的内存空间,能够经过指针加法来作分配。对于空闲列表,Java虚拟机则须要逐个访问列表中的项,来查找可以放入新建对象的空闲内存。

二、复制算法

复制算法是将内容容量划分红大小相等的两块,每次只使用其中的一块。当一块内存用完以后,就将还存活的对象复制到另外一块上面,而后再把已使用的内存空间一次性清理。这样使得每次都对其中一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂状况,只是这种算法的代价就是将内存缩小为原来的一半了。
在这里插入图片描述

三、标记-整理算法(或叫压缩算法)

标记整理算法和标记清除算法很类似,显著的区别是:标记清除算法只对不存活的对象进行处理,剩余存活对象不作任何处理,因此形成了内存碎片的问题;而标记整理算法对不存活的对象进行清除,还对存活的对象进行从新整理,所以不会产生内存不连续的现象。
在这里插入图片描述

分代收集算法

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。其核心思想是根据对象存活的生命周期将内存划分为若干个不一样的区域。而后根据不一样的区域采用合适的收集算法,它自己并非一个新的收集算法。在jdk1.7以前,对JVM分为三个区域:新生代,老年代,永久代。

(1)新生代(复制算法)

新生代的目标就是尽量快速的收集掉那些生命周期较短的对象,通常状况下新生成的或者朝生夕亡的对象通常都是首先存放在新生代里。
由于新生代会频繁的进行GC清理,因此采用的是复制算法,先标记出存活的实例,而后清除掉无用实例,将存活的实例根据年龄(每一个实例被经历一次GC后年龄会加1)拷贝到不一样的年龄代。
在这里插入图片描述

(2)老年代(标记整理或标记清除算法)

老年代通常存放的是一些生命周期较长的对象,好比是新生代中经历了N次垃圾回收后仍然存活的对象都进入了老年代。
这块内存区域通常大于年轻代,GC发生的次数也比年轻代要少。
在老年代中由于对象存活率较高,没有额外的空间对它分配担保,就必须使用标记清除或标记整理。

(3)永久代

永久代主要存放静态文件,如java类,方法等,永久代对垃圾回收没有显著影响。
方法区主要回收的内容有:废弃的常量,无用的类,对于废弃的常量能够经过引用的可达性分析判断,可是对于无用类须要同时知足如下三个条件:
一、该类的全部实例都已经被回收了
二、加载该类的ClassLoader已经被回收了
三、该类对于的java.lang.Class 对象没有在任何地方被引用,没法在任何地方经过反射访问该类的方法。

什么时候触发GC(垃圾收集)?

  1. 执行System.gc()的时候
  2. 老年代空间不足,一次Full GC以后,而后不足 会触发Java.outofmemoryError.java heap space
  3. 永久代空间不足,永生代或者永久代,java.outofMemory PerGen Space
  4. minor 以后 survivor 放不下,放入老年代,老年代也放不下,触发FullGC,或者新生代有对象放入老年代,老年代放不下,触发FullGC
  5. 新生代晋升为老年代的时候,老年代剩余空间低于新生代晋升为老年代的速率,会触发老年代回收
  6. new 一个大对象,新生代放不下,直接到老年代,空间不够,触发FullGC

如何避免频繁的GC?

  1. 不要频繁的new对象
  2. 不要显式的调用system.gc()
  3. 不要用String+ ,使用StringBuilder
  4. 不要使用Long ,Integer,尽可能使用基本类型
  5. 少用静态变量,不会回收
  6. 可使用null进行回收
相关文章
相关标签/搜索