JVM--17---垃圾回收相关概念

Posted 高高for 循环

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM--17---垃圾回收相关概念相关的知识,希望对你有一定的参考价值。

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


垃圾回收相关概念

  1. System.gc() 的理解
  2. 内存溢出和内存泄漏
  3. Stop The World
  4. 垃圾回收的并行与并发
  5. 安全点与安全区域
  6. 再谈引用☆

1. System.gc() 的理解

System.gc()

在默认情况下,通过 System.gc() 者 Runtime.getRuntime().gc() 的调用,会显式触发 Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。

  • 然而 System.gc() 调用附带一个免责声明,无法保证对垃圾收集器的调用。(不能确保立即生效)
  • JVM 实现者可以通过 System.gc() 调用来决定 JVM 的 GC行为。而一般情况下,垃圾回收应该是自动进行的,无须手动触发,否则就太过于麻烦了。在一些特殊情况下,如我们正在编写一个性能基准,我们可以在运行之间调用 System.gc()

代码演示是否出发GC操作

public class SystemGCTest {
    public static void main(String[] args) {
        new SystemGCTest();
        // 提醒 JVM 进行垃圾回收
        System.gc();
        //System.runFinalization();
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("SystemGCTest 执行了 finalize方法");
    }
}
  • 运行结果,但是不一定会触发销毁的方法(不能确保立即生效)

调用 System.runFinalization() 会强制调用失去引用对象的 finalize()

手动 GC 来理解不可达对象的回收

public class LocalVarGC {

    /**
     * 触发Minor GC没有回收对象,然后在触发Full GC将该对象存入old区
     */
    public void localvarGC1() {
        byte[] buffer = new byte[10*1024*1024];
        System.gc();
    }

    /**
     * 触发YoungGC的时候,已经被回收了
     */
    public void localvarGC2() {
        byte[] buffer = new byte[10*1024*1024];
        buffer = null;
        System.gc();
    }

    /**
     * 不会被回收,因为它还存放在局部变量表索引为1的槽中
     */
    public void localvarGC3() {
        {
            byte[] buffer = new byte[10*1024*1024];
        }
        System.gc();
    }

    /**
     * 会被回收,因为它还存放在局部变量表索引为1的槽中,但是后面定义的value把这个槽给替换了
     */
    public void localvarGC4() {
        {
            byte[] buffer = new byte[10*1024*1024];
        }
        int value = 10;
        System.gc();
    }

    /**
     * localvarGC5中的数组已经被回收
     */
    public void localvarGC5() {
        localvarGC1();
        System.gc();
    }

    public static void main(String[] args) {
        LocalVarGC localVarGC = new LocalVarGC();
        localVarGC.localvarGC3();
    }
}

2. 内存溢出和内存泄漏

内存溢出

  • 内存溢出相对于内存泄漏来说,尽管更容易被理解,但是同样的,内存溢出也是引发程序崩溃的罪魁祸首之一。
  • 由于 GC 一直在发展,所有一般情况下,除非应用程序占用的内存增长速度非常快,造成垃圾回收已经跟不上内存消耗的速度,否则不太容易出现 OOM 的情况。
  • 大多数情况下,GC 会进行各种年龄段的垃圾回收,实在不行了就放大招,来一次独占式的 Full GC 操作,这时候会回收大量的内存,供应用程序继续使用。

javadoc 中对 OutOfMemoryError 的解释是,没有空闲内存,并且垃圾收集器也无法提供更多内存。

Java 虚拟机的堆内存不够—原因:

1. Java 虚拟机的堆内存设置不够。

  • 比如:可能存在内存泄漏问题;也很有可能就是堆的大小不合理,比如我们要处理比较可观的数据量,但是没有显式指定 JVM堆大小或者指定数值偏小。我们可以通过参数 -Xms 、-Xmx 来调整。

2.代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)

  • 对于老版本的 Oracle JDK,因为永久代的大小是有限的,并且 JVM 对永久代垃圾回收(如,常量池回收、卸载不再需要的类型)非常不积极,所以当我们不断添加新类型的时候,永久代出现 OutOfMemoryError 也非常多见,尤其是在运行时存在大量动态类型生成的场合;类似 intern 字符串缓存占用太多空间,也会导致 OOM 问题。对应的异常信息,会标记出来和永久代相关:“java.lang.OutOfMemoryError:PermGen space"。
  • 随着元数据区的引入,方法区内存已经不再那么窘迫,所以相应的 OOM 有所改观,出现 OOM,异常信息则变成了:“java.lang.OutOfMemoryError:Metaspace"。直接内存不足,也会导致 OOM

在抛出 OutOfMemoryError 之前,通常垃圾收集器会被触发

  • 在抛出 OutOfMemoryError 之前,通常垃圾收集器会被触发,尽其所能去清理出空间。 - 例如:在引用机制分析中,涉及到 JVM会去尝试回收软引用指向的对象等。 - 在 java.nio.BIts.reserveMemory()方法中,我们能清楚的看到,System.gc() 会被调用,以清理空间。

当然,也不是在任何情况下垃圾收集器都会被触发的

  • 比如,我们去分配一个超大对象,类似一个超大数组超过堆的最大值,JVM 可以判断出垃圾收集并不能解决这个问题,所以直接抛出 OutOfMemoryError。

内存泄漏

也称作“存储渗漏”。

严格来说,只有对象不会再被程序用到了,但是 GC 又不能回收他们的情况,才叫内存泄漏。

  • 但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致 OOM,也可以叫做宽泛意义上的“内存泄漏”。
  • 尽管内存泄漏并不会立刻引起程序崩溃,但是一旦发生内存泄漏,程序中的可用内存就会被逐步蚕食,直至耗尽所有内存,最终出现 OutOfMemory 异常,导致程序崩溃。

注意,这里的存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。

案例:

  • 买房子:80平的房子,但是有10平是公摊的面积,我们是无法使用这10平的空间,这就是所谓的内存泄漏
  • Java 使用可达性分析算法,最上面的数据不可达,就是需要被回收的。后期有一些对象不用了,按道理应该断开引用,但是存在一些链没有断开,从而导致没有办法被回收。

内存泄漏举例:

单例模式

  • 单例的生命周期和应用程序是一样长的,所以单例程序中,如果持有对外部对象的引用的话,那么这个外部对象是不能被回收的,则会导致内存泄漏的产生。(Runtime 运行时类)

一些提供 close 的资源未关闭导致内存泄漏

  • 数据库连接(dataSourse.getConnection() )
  • 网络连接(Socket)
  • IO 连接必须手动 close,否则是不能被回收的。

3. Stop The World

定义:

  • Stop-The-World,简称 STW,指的是 GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为 STW。

可达性分析算法中枚举根节点(GC Roots)会导致所有 Java 执行线程停顿。

  • 分析工作必须在一个能确保一致性的快照中进行
  • 一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上
  • 如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证

尽量减少STW 的发生

  • 被 STW 中断的应用程序线程会在完成 GC 之后恢复,频繁中断会让用户感觉像是网速不快造成电影卡带一样,所以我们需要减少 STW的发生。

所有的GC回收期都会产生STW

  • STW 事件和采用哪款 GC 无关所有的GC都有这个事件。
  • 哪怕是 G1 也不能完全避免 Stop-The-World情况发生,只能说垃圾回收器越来越优秀,回收效率越来越高,尽可能地缩短了暂停时间。

注意:

  • STW 是 JVM 在后台自动发起和自动完成的。在用户不可见的情况下,把用户正常的工作线程全部停掉。
  • 开发中不要用 System.gc() 会导致 Stop-The-World 的发生。

4. 垃圾回收的并行与并发

并发

在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理器上运行。

  • 并发不是真正意义上的“同时进行”,只是 CPU 把一个时间段划分成几个时间片段(时间区间),然后在这几个时间区间之间来回切换,由于 CPU 处理的速度非常快,只要时间间隔处理得当,即可让用户感觉是多个应用程序同时在进行。

并行

-当系统有一个以上 CPU 时,当一个 CPU 执行一个进程时,另一个 CPU 可以执行另一个进程,两个进程互不抢占CPU 资源,可以同时进行,我们称之为并行(Parallel)。

  • 其实决定并行的因素不是 CPU 的数量,而是 CPU 的核心数量,比如一个 CPU 多个核也可以并行。
  • 适合科学计算,后台处理等弱交互场景

并发和并行对比

  • 并发,指的是多个事情,在同一时间段内同时发生了。
  • 并行,指的是多个事情,在同一时间点上同时发生了。

并发: 同一时间段内客户端涌过来的请求数

并行:同一时刻,同时处理请求的线程数

并发的多个任务之间是互相抢占资源的。并行的多个任务之间是不互相抢占资源的

只有在多 CPU 或者一个 CPU 多核的情况中,才会发生并行。
否则,看似同时发生的事情,其实都是并发执行的。

垃圾回收的并行与并发

并发和并行,在谈论垃圾收集器的上下文语境中,它们可以解释如下:

  • 并行(Parallel):
    指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。如 ParNew、Parallel Scavenge、Parallel Old;
  • 串行(Serial)
    相较于并行的概念,单线程执行。
    如果内存不够,则程序暂停,启动 JVM 垃圾回收器进行垃圾回收。回收完,再启动程序的线程。

并发和并行,在谈论垃圾收集器的上下文语境中,它们可以解释如下:

  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾回收线程在执行时不会停顿用户程序的运行。(但会短暂stw)

用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上

如:CMS、G1

5.安全点与安全区域

安全点

如何在 GC 发生时,检查所有线程都跑到最近的安全点停顿下来呢?

  • 抢先式中断:
    (目前没有虚拟机采用了)首先中断所有线程。如果还有线程不在安全点,就恢复线程,让线程跑到安全点。
  • 主动式中断:
    设置一个中断标志,各个线程运行到Safe Point的时候主动轮询这个标志,如果中断标志为真,则将自己进行中断挂起。(有轮询的机制)

安全区域

安全区域是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始 GC 都是安全的

我们也可以把 Safe Region 看做是被扩展了的 SafePoint。

执行流程:

  • 当线程运行到 Safe Region 的代码时,首先标识已经进入了 Safe Relgion,如果这段时间内发生 GC,JVM会忽略标识为 Safe Region 状态的线程
  • 当线程即将离开 Safe Region 时,会检查JVM是否已经完成 GC,如果完成了,则继续运行,否则线程必须等待直到收到可以安全离开Safe Region 的信号为止;

以上是关于JVM--17---垃圾回收相关概念的主要内容,如果未能解决你的问题,请参考以下文章

16_垃圾回收相关概念

Day340.垃圾回收相关概念 -JVM

垃圾回收相关概念

JVM之垃圾回收相关概念

PHP中的垃圾回收相关函数

对“xxx”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。 错误解决一例。(代码片段