深入理解jvm——垃圾收集策略具体解释

mthoutai mthoutai     2022-09-15     759

关键词:

Java虚拟机的内存模型分为五个部分。各自是:程序计数器、Java虚拟机栈、本地方法栈、堆、方法区。

这五个区域既然是存储空间,那么为了避免Java虚拟机在执行期间内存存满的情况,就必须得有一个垃圾收集者的角色。不定期地回收一些无效内存,以保障Java虚拟机可以健康地持续执行。

这个垃圾收集者就是寻常我们所说的“垃圾收集器”。那么垃圾收集器在何时清扫内存?清扫哪些数据?这就是接下来我们要解决的问题。

程序计数器、Java虚拟机栈、本地方法栈都是线程私有的,也就是每条线程都拥有这三块区域,并且会随着线程的创建而创建。线程的结束而销毁。那么。垃圾收集器在何时清扫这三块区域的问题就攻克了。

此外,Java虚拟机栈、本地方法栈中的栈帧会随着方法的開始而入栈,方法的结束而出栈。并且每一个栈帧中的本地变量表都是在类被载入的时候就确定的。因此以上三个区域的垃圾收集工作具有确定性,垃圾收集器可以清楚地知道何时清扫这三块区域中的哪些数据。

然而,堆和方法区中的内存清理工作就没那么easy了。
堆和方法区全部线程共享,并且都在JVM启动时创建,一直得执行到JVM停止时。因此它们没办法依据线程的创建而创建、线程的结束而释放。

堆中存放JVM执行期间的全部对象,尽管每一个对象的内存大小在载入该对象所属类的时候就确定了。但到底创建多少个对象仅仅有在程序执行期间才干确定。
方法区中存放类信息、静态成员变量、常量。类的载入是在程序执行过程中,当须要创建这个类的对象时才会载入这个类。因此,JVM到底要载入多少个类也须要在程序执行期间确定。


因此,堆和方法区的内存回收具有不确定性,因此垃圾收集器在回收堆和方法区内存的时候花了一些心思。



堆内存的回收

1. 怎样判定哪些对象须要回收?

在对堆进行对象回收之前,首先要推断哪些是无效对象。我们知道。一个对象不被不论什么对象或变量引用。那么就是无效对象。须要被回收。

一般有两种判别方式:

  • 引用计数法
    每一个对象都有一个计数器,当这个对象被一个变量或还有一个对象引用一次,该计数器加一;若该引用失效则计数器减一。当计数器为0时,就觉得该对象是无效对象。

  • 可达性分析法
    全部和GC Roots直接或间接关联的对象都是有效对象。和GC Roots没有关联的对象就是无效对象。


    GC Roots是指:

    1. Java虚拟机栈所引用的对象(栈帧中局部变量表中引用类型的变量所引用的对象)
    2. 方法区中静态属性引用的对象
    3. 方法区中常量所引用的对象
    4. 本地方法栈所引用的对象
      PS:注意!GC Roots并不包含堆中对象所引用的对象!这样就不会出现循环引用。

两者对照:
引用计数法尽管简单,但存在一个严重的问题,它无法解决循环引用的问题。


因此。眼下主流语言均使用可达性分析方法来推断对象是否有效。


2. 回收无效对象的过程

当JVM筛选出失效的对象之后,并非马上清除,而是再给对象一次重生的机会。详细步骤例如以下:

  1. 推断该对象是否覆盖了finalize()方法

    • 若已覆盖该方法,并该对象的finalize()方法还没有被执行过。那么就会将finalize()扔到F-Queue队列中;
    • 若未覆盖该方法。则直接释放对象内存。
  2. 执行F-Queue队列中的finalize()方法
    虚拟机会以较低的优先级执行这些finalize()方法们。也不会确保全部的finalize()方法都会执行结束。假设finalize()方法中出现耗时操作,虚拟机就直接停止执行,将该对象清除。

  3. 对象重生或死亡
    假设在执行finalize()方法时,将this赋给了某一个引用。那么该对象就重生了。

    假设没有,那么就会被垃圾收集器清除。

注意:
强烈不建议使用finalize()函数进行不论什么操作!

假设须要释放资源,请使用try-finally。
因为finalize()不确定性大,开销大,无法保证顺利执行。


方法区的内存回收

我们知道,假设使用复制算法实现堆的内存回收,堆就会被分为新生代和老年代。新生代中的对象“朝生夕死”,每次垃圾回收都会清除掉大量的对象;而老年代中的对象生命较长。每次垃圾回收仅仅有少量的对象被清除掉。

因为方法区中存放生命周期较长的类信息、常量、静态变量,因此方法区就像是堆的老年代,每次垃圾收集的仅仅有少量的垃圾被清除掉。

方法区中主要清除两种垃圾:
1. 废弃常量
2. 废弃的类


1. 怎样判定废弃常量?

清除废弃的常量和清除对象相似。仅仅要常量池中的常量不被不论什么变量或对象引用,那么这些常量就会被清除掉。


2. 怎样废弃废弃的类?

清除废弃类的条件较为苛刻:
1. 该类的全部对象都已被清除
2. 该类的java.lang.Class对象没有被不论什么对象或变量引用
仅仅要一个类被虚拟机载入进方法区,那么在堆中就会有一个代表该类的对象:java.lang.Class。

这个对象在类被载入进方法区的时候创建,在方法区中该类被删除时清除。
3. 载入该类的ClassLoader已经被回收


垃圾收集算法

如今我们知道了判定一个对象是无效对象、判定一个类是废弃类、判定一个常量是废弃常量的方法,也就是知道了垃圾收集器会清除哪些数据,那么接下来介绍怎样清除这些数据。


1. 标记-清除算法

首先利用刚才介绍的方法推断须要清除哪些数据,并给它们做上标记。然后清除被标记的数据。

分析:
这样的算法标记和清除过程效率都非常低。并且清除完后存在大量碎片空间。导致无法存储大对象,减少了空间利用率。


2. 复制算法

将内存分成两份,仅仅将数据存储在当中一块上。当须要回收垃圾时,也是首先标记出废弃的数据,然后将实用的数据拷贝到还有一块内存上,最后将第一块内存全部清除。

分析:
这样的算法避免了碎片空间,但内存被缩小了一半。


并且每次都须要将实用的数据全部拷贝到还有一片内存上去,效率不高。

解决空间利用率问题:
在新生代中。因为大量的对象都是“朝生夕死”。也就是一次垃圾收集后仅仅有少量对象存活。因此我们可以将内存划分成三块:Eden、Survior1、Survior2,内存大小各自是8:1:1。

分配内存时,仅仅使用Eden和一块Survior1。

当发现Eden+Survior1的内存即将满时,JVM会发起一次MinorGC,清除掉废弃的对象。并将全部存活下来的对象拷贝到还有一块Survior2中。

那么,接下来就使用Survior2+Eden进行内存分配。

通过这样的方式,仅仅须要浪费10%的内存空间就可以实现带有压缩功能的垃圾收集方法,避免了内存碎片的问题。

可是,当一个对象要申请内存空间时,发现Eden+Survior中剩下的空间无法放置该对象,此时须要进行Minor GC,假设MinorGC过后空暇出来的内存空间仍然无法放置该对象,那么此时就须要将对象转移到老年代中。这样的方式叫做“分配担保”。


什么是分配担保?
当JVM准备为一个对象分配内存空间时,发现此时Eden+Survior中空暇的区域无法装下该对象,那么就会触发MinorGC,对该区域的废弃对象进行回收。

但假设MinorGC过后仅仅有少量对象被回收,仍然无法装下新对象,那么此时须要将Eden+Survior中的全部对象都转移到老年代中,然后再将新对象存入Eden区。

这个过程就是“分配担保”。


3. 标记-整理算法

在回收垃圾前。首先将全部废弃的对象做上标记,然后将全部未被标记的对象移到一边,最后清空还有一边区域就可以。

分析:
它是一种老年代的垃圾收集算法。老年代中的对象一般寿命比較长。因此每次垃圾回收会有大量对象存活,因此假设选用“复制”算法。每次须要复制大量存活的对象,会导致效率非常低。

并且,在新生代中使用“复制”算法,当Eden+Survior中都装不下某个对象时,可以使用老年代的内存进行“分配担保”,而假设在老年代使用该算法。那么在老年代中假设出现Eden+Survior装不下某个对象时。没有其它区域给他作分配担保。因此,老年代中一般使用“标记-整理”算法。


4. 分代收集算法

将内存划分为老年代和新生代。老年代中存放寿命较长的对象,新生代中存放“朝生夕死”的对象。然后在不同的区域使用不同的垃圾收集算法。


Java中引用的种类

Java中依据生命周期的长短,将引用分为4类。

1. 强引用

我们平时所使用的引用就是强引用。
A a = new A();
也就是通过keywordnew创建的对象所关联的引用就是强引用。
仅仅要强引用存在,该对象永远也不会被回收。

2. 软引用

仅仅有当堆即将发生OOM异常时,JVM才会回收软引用所指向的对象。
软引用通过SoftReference类实现。


软引用的生命周期比强引用短一些。

3. 弱引用

仅仅要垃圾收集器执行。软引用所指向的对象就会被回收。


弱引用通过WeakReference类实现。
弱引用的生命周期比软引用短。

4. 虚引用

虚引用也叫幽灵引用,它和没有引用没有差别。无法通过虚引用訪问对象的不论什么属性或函数。
一个对象关联虚引用唯一的作用就是在该对象被垃圾收集器回收之前会受到一条系统通知。


虚引用通过PhantomReference类来实现。

深入理解jvm:垃圾收集器与内存分配策略

堆里面存放着Java世界差点儿全部的对象实例,垃圾收集器在对堆进行回收前。第一件事情就是要确定这些对象之中哪些还存活,哪些已经死去。推断对象的生命周期是否结束有下面几种方法引用计数法详细操作是给对象加入一... 查看详情

《深入理解jvm——7种垃圾收集器》

JVM  深入理解JVM(3)——7种垃圾收集器 PostedbyCrowonAugust15,2017如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不... 查看详情

《深入理解jvm——7种垃圾收集器》

JVM  深入理解JVM(3)——7种垃圾收集器 PostedbyCrowonAugust15,2017如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不... 查看详情

深入理解jvm-垃圾收集器与内存分配策略

在上面一篇文章中,介绍了java内存运行时区域,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程生灭;栈中的栈帧随着方法的进入和退出而有条不紊的执行着进栈出栈的操作,每一个栈帧中分配着多少内存基本上是在类... 查看详情

深入理解jvm读书笔记二:垃圾收集器与内存分配策略

3.2对象已死吗?3.2.1引用计数法给对象添加一个引用计数器,每当有一个地方引用它的地方,计数器值+1;当引用失效,计数器值就减1;任何时候计数器为0,对象就不可能再被引用了。它很难解决对象之间相互循环引用的问题。3.... 查看详情

深入理解jvm——7种垃圾收集器

文章转载来源:深入理解JVM(3)——7种垃圾收集器如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、版本... 查看详情

深入理解jvm-垃圾回收器

看完《深入理解JVM》,结合网上资料后根据跟人理解整理出的简洁版,主要关注是什么,怎么做到的,特点等,没有进入深入剖析,旨在快速了解,具体应用时个人再根据具体点去进行深入。欢迎留言讨论!是什么?垃圾回收器... 查看详情

深入理解jvm——hotspot垃圾收集器详解

HotSpot虚拟机提供了多种垃圾收集器,每种收集器都有各自的特点,没有最好的垃圾收集器,只有最适合的垃圾收集器。根据新生代和老年代各自的特点,我们应该分别为它们选择不同的收集器,以提升垃圾回收效率。新生代垃... 查看详情

深入理解jvm--垃圾收集算法

一. 概述    说起垃圾收集(Garbage Collection, GC), 大部分人都把这项技术当做Java语言的伴随生产物. 事实上, GC的历史远远比Java久远,1960年 诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术... 查看详情

深入理解jvm---7种垃圾收集器

新生代收集器1.Serial收集器(1)概念:它是采用复制算法的新生代收集器,曾经(JDK1.3.1之前)是虚拟机新生代收集的唯一选择。它是一个单线程收集器,只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是它在进... 查看详情

《深入理解jvm——gc算法与内存分配策略》

 JVM深入理解JVM(2)——GC算法与内存分配策略 PostedbyCrowonAugust10,2017说起垃圾收集(GarbageCollection,GC),想必大家都不陌生,它是JVM实现里非常重要的一环,JVM成熟的内存动态分配与回收技术使Java(当然还有其他运行... 查看详情

深入理解jvm——7种垃圾收集器

文章转载来源:深入理解JVM(3)——7种垃圾收集器如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、版本... 查看详情

深入理解jvm(③)经典的垃圾收集器

前言如果说垃圾收集算法是内存回收的方法论,那垃圾收集器就是内存回收的实践者。本次要介绍的是几款“经典”的垃圾收集器,之所以被称之为“经典”,是为了与几款目前仍处于实验状态,但是执行效果上哟革命性改进的... 查看详情

jvm,深入理解java虚拟机,垃圾收集算法与垃圾收集器

垃圾收集算法由于垃圾收集算法的实现涉及大量的程序细节,而且各个平台的虚拟机操作内存的方法又各不相同,因此本节不打算过多地讨论算法的实现,只是介绍几种算法的思想及其发展过程。标记-清除算法最基... 查看详情

《深入理解jvm——gc算法与内存分配策略》(代码片段)

 JVM深入理解JVM(2)——GC算法与内存分配策略 PostedbyCrowonAugust10,2017说起垃圾收集(GarbageCollection,GC),想必大家都不陌生,它是JVM实现里非常重要的一环,JVM成熟的内存动态分配与回收技术使Java(当然还有其他运行... 查看详情

jvm垃圾回收2(垃圾收集算法)

  根据《深入理解java虚拟机》这本书总结  一、关于几个概念:(标记垃圾算法、垃圾收集算法、垃圾收集器)  前面说了如何寻找jvm垃圾,有两种方法:引用计数法/可达性算法。这篇准备讲,标记完垃圾之后,回收的... 查看详情

深入理解_jvm内存管理内存分配和回收策略06

解决两个问题:   1、对象分配内存;   2、回收分配给对象的内存。本节详细讲解分配的问题:名词解释:新生代GC(MinorGC):指发生在新生代的垃圾回收动作,非常频繁,回收速度很快。老生代GC(MajorGC/FullGC)... 查看详情

垃圾收集器与内存分配策略(深入理解java虚拟机)

3.1 概述垃圾收集器要解决哪些问题?哪些内存需要回收什么时候回收如何回收引用计数算法:当有一个地方引用,+1,引用失效,-1。   缺点:对象之间相互循环引用的问题。可达性分析算法:思路:通过一系列... 查看详情