这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战
在JVM中,垃圾回收(Garbage Collection 简称GC)指的将无用的垃圾进行清楚何回收,这样能够避免内存泄露,有效的利用内存空间,而所谓的垃圾指的是在内存堆中长时间没有使用或者已经死亡的对象。如果不进行垃圾回收的话,每次你程序运行所产生的对象都会一直存活在内存中,那么不管你有多少内存,也扛不住永无止尽增加的对象,最终导致OOM,那么怎么判断一个对象是否是垃圾呢?
- 引用计数法:引用计数法顾名思义就是对对象的引用进行计数,在对象的头部有一个counter计数器,每次引用都会+1,如果引用数为0的话,那么判定该对象是可回收的,但是后面这种方法被弃用了,原因是因为这种方法无法判定一些无效引用,比如说
Object objA = new Object();
Object objB = new Object();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
复制代码
像这种循环引用是无法判断为垃圾的
- 可达性分析:可达性分析就完美的解决了循环引用的问题,因为可达性分析是以GC Root为节点,进行搜寻,你没有和GC Root建立联系,那么会对你进行标记和筛选,相当于进入死缓阶段,可达性分析一共要经历两次标记,筛选的条件是重写了finalize()方法并没有执行过,对于重写了且没有执行finalize()方法的对象将其放在一个F-Queue队列中,并在之后由一个虚拟机自动建立的低优先级的Finalizer线程去执行它,在此之后,系统会对对象进行第二次标记,如果在第一次标记之后的对象在执行finalize()方法时没有被引用到一个新的变量,该对象将被回收掉,对于finalize方法的释义:
当垃圾回收确定不再有对对象的引用时,由垃圾回收器在对象上调用。子类覆盖{@code finalize}方法来处理系统资源或执行其他清理
GC Root的定义:
- 虚拟机栈的栈帧的局部变量表引用的对象
- 本地方法栈的JNI(Java本地调用)所引用的对象
- 方法区的静态变量和常量所引用的对象
在JDK1.1的描述中:如果Reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。此定义下,一个对象只有被引用和没有被引用两种状态,这在使用中不够灵活方便,所以在JDK1.2中对引用的概念进行扩充,细分如下四种:
- 强引用:强引用就是在程序中一般使用的引用方式
StringBuffer buffer = new StringBuffer( );
复制代码
强引用是可达的,不可回收的,相对软引用,弱引用,虚引用,对应的是软可达,弱可达,虚可达,在一定的条件下是可以回收的,假设这个代码是写在函数里,那么buffer就会被分配在栈上面,StringBuffer的实例会被分配在堆上面,通过buffer操作StringBuffer的实例,那么buffer就是StringBuffer实例的强引用,如果像下面这样操作:
StringBuffer str = buffer;
复制代码
在栈上的局部变量表里会继续为str分配空间并且指向StringBuffer的实例,所以对两个变量用==判断时,因为引用的是同一个地址,所以会判定为相等
- 软引用:
StringBuffer buffer = new StringBuffer(); SoftReference<StringBuffer> soft = new SoftReference<StringBuffer>(buffer);
复制代码
软引用的是一些还有用但是但是并非必须的对象,当要发生内存泄漏的时候,会将软引用对象加入名单,进行二次回收,如果还是内存不够的话,就会出现OOM,一些系统的缓存就是软引用
- 弱引用
StringBuffer buffer = new StringBuffer();
WeakReference<StringBuffer> soft = new WeakReference<StringBuffer>(buffer);
复制代码
只要发生垃圾回收,弱引用对象就一定会被回收
- 虚引用
ReferenceQueue<Integer> queue = new ReferenceQueue<>(); Integer integer = new Integer(1); PhantomReference<Integer> phantomRef = new PhantomReference<Integer>(integer, queue);
复制代码
虚引用对象有引用几乎和没引用一样,虚引用必须和引用队列一起使用,他的作用在于跟踪垃圾回收过程,当垃圾回收器准备回收一个对象时,如果发现它有虚引用,就会在回收对象之后将这个虚引用加入到引用对列
JVM垃圾回收主要是对新生代进行回收,因此按照gc回收的类型,可以分为普通回收和全局回收,普通回收针对的是新生代,全局回收针对的是老年代
垃圾回收一共有几种回收算法:
标记清除算法在标记阶段,会遍历gcRoot,找到存活的对象,并给他们打上标记,在标记之后,将之前未标记上的对象给清除,然后将之前标记上的对象的标记置空
缺点:
效率问题,标记和清除过程效率都很低;空间问题,收集之后会产生大量的内存碎片,不利于大对象的分配
将可用内存划分成大小相等的两块A和B,每次只使用其中一块,当A的内存用完了,就把存活的对象复制到B,并清空A的内存,不仅提高了标记的效率,因为只需要标记存活的对象,同时也避免了内存碎片的问题
缺点
可用内存缩小为原来的一半,对象存活率非常高的时候,这种开销是不可忽视的
标记整理算法在标记阶段,会遍历gcRoot,找到存活的对象,并给他们打上标记, 整理阶段,在老年代中,对象存活率较高,复制算法的效率很低。在标记-整理算法中,标记出所有存活的对象,并移动到一端,然后直接清理边界以外的内存
缺点
效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址
因为原本的空间被划分为三个代,新生代,老年代,永久代
新生代:
一般存放些生命周期比较短的对象;比如:局部变量,和一些临时变量。因为生命周期短如此所以他比较适合复制算法。考虑到比较占内存所以只给了两块10%的内存。新生代的每个对象都有年龄(在gc操作下存活下来的次数),当达到一定年龄的时候则会进入老年代。又或者当新生代内存满(10%)的时候,对象会进入老年代,可以说老年代是新生代的备用仓库。
老年代:存放生命周期比较长的对象;比如:缓存对象、数据库连接对象、单例对象(单例模式)等等。适合标记/整理或者标记/清除算法。
永久代(java8改为享元空间):一旦出生几乎很难死去,一般存放在方法区。比如:String池中的对象(享元模式)、加载过的类信息等等。适合标记/整理或者标记/清除算法。
- 新生代:复制算法
所有新生成的对象首先都是放在年轻代的,年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。且新生代发生MinorGC频率比较高。
- 年老代:标记-清除或标记-整理
老年代存活率高,复制算法效率低
- 分代收集算法
以上这种年轻代与年老代分别采用不同回收算法的方式称为”分代收集算法”,这也是当下企业使用的一种方式。
每一种算法都会有很多不同的垃圾回收器去实现,在实际使用中,可以根据自己的业务进行选择。




近期评论