• JVM(十五)—— 垃圾回收(一)


    垃圾回收概述

    垃圾收集机制是Java的招牌能力,极大地提高了开发效率。如今垃圾收集几乎称为现代语言的标配,即使经过如此长时间的发展,Java的垃圾收集机制仍然在不断地演进中,不同大小的设备,不同特征的应用场景对垃圾收集提出了新的挑战,这当然也是面试人热点。

    什么是垃圾?

    垃圾是指在运行程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾。
    通过前面的篇章我们知道,当栈区的方法执行完出栈之后,对应的引用也会出栈,但是该引用指向的对象还在堆中,这样堆中就会出现垃圾,需要回收。
    如果不及时对内存中的垃圾进行清理,那么这些垃圾对象所占的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。甚至可能导致内存溢出

    GC的必要性

    • 对于高级语言来说,一个基本认知是如果不进行垃圾回收,内存迟早会被消耗完,因为不断地分配内存空间而不进行回收,就好像不停地生辰生活垃圾而从不打扫一样。
    • 除了释放没用的对象,垃圾回收也可以清楚内存里的记录碎片。碎片整理将所占用的堆内存移到堆的一端,以便JVM将整理出的内存分配给新的对象。
    • 随着应用程序所应付的业务越来越庞大,复杂,用户越来越多,没有GC就不能保证应用程序的正常运行,而经常造成STW的GC又跟不上实际的需求,所以才会不断尝试对GC进行优化。

    自动的内存管理

    • 自动内存管理,无需开发人员手动参与内存的分配与回收,这样降低内存泄露和内存溢出的缝风险。
    • 自动内存管理机制,将程序员从繁重的内存管理中释放出来,可以更专心的专注于业务开发。
    • 自动内存管理对于程序员来说就像是一个黑匣子,如果国度依赖于自动,就会弱化程序在程序中出现内存溢出时定位问题和解决问题的能力。
    • 了解JVM的自动内存分配和内存回收是非常重要的,只有在真正了解JVM是如何管理内存后,我们才能在遇见OOM时,快速的根据错误异常日志定位问题和解决问题。
    • 当需要排查各种内存溢出,内存泄漏问题时,当垃圾收集称为系统达到更高并发量的瓶颈时,我们就必须对这些自动化的技术实施必要的监控和调节。

    Java中GC的区域

    在这里插入图片描述
    Java垃圾回收主要关注的是方法区和堆区,Java栈,本地方法栈,程序计数器是没有GC行为的。

    垃圾回收器可以对年轻代回收,也可以对老年代回收,甚至是全堆和方法区回收,其中,Java堆是垃圾收集器的工作重点。

    从频率上讲:年轻代最频繁,养老区较少,永久代/元空间基本不回收。

    垃圾回收算法

    垃圾标记阶段:对象存活判断

    在堆里放着几乎所有Java对象实例,在GC执行垃圾回收之前,首先需要区分内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为已经死亡的对象,GC才会在执行垃圾回收时,释放掉所占用的北村空间,因此这个过程我们称为垃圾标记阶段。

    那么在JVM中酒精是如何标记一个死亡对象呢?简单说,当一个对象已经不再被任何存活对象继续引用时,就可以宣判已经死亡。

    判断对象存活一般有两种方式:引用计数法和可达性分析算法

    引用计数算法

    引用计数算法比较简单,对每一个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况

    对于一个对象A,只要有任何一个对象引用了A ,则A的引用计数器就加1,当引用失效时,引用计数器就减1.只要对象A的引用计数器的值为0,即表示对象A不可能在被使用,可进行回收。

    优点
    实现简单,垃圾对象便于辨识,判定效率高,回收没有延迟性。
    缺点:

    • 它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
    • 每次赋值都需要更细腻计数器,伴随着加法和减法操作,这增加了时间开销。
    • 引用计数器有一个严重的问题,无法处理循环引用的情况。这是一条致命缺陷。导致在Java的垃圾回收器中没有使用这类算法。

    在这里插入图片描述
    上图中p一个对象,后边的三个对象相互引用,形成了循环依赖。当我们将p的引用置空,由于三个对象的循环引用,导致了三者的计数器都不为0无法被回收,造成了内存泄漏。

    可达性分析算法

    可达性分析算法又叫做根搜索算法或者追踪性垃圾收集。

    相较于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效的解决在引用计数算法中循环引用的问题,防止内存泄漏发生。这里的可达性分析就是Java,C#选择的。这种类型的垃圾收集通常也叫作追踪性垃圾收集

    GC Roots
    • 可达性分析算法是以根对象集合(GC Roots: 根集合,一组必须活跃的引用)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。
    • 使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链。
      在这里插入图片描述
    • 在可达性分析算法中,只有能够被根对象集合直接或间接连接的对象才是存活对象。

    在Java语言中,GC Roots包括以下几类元素:

    • 虚拟机栈中引用的对象
      • 比如:各个线程被调用的方法中使用到的参数,局部变量等。
    • 本地方法栈内JNI(通常说的本地方法)引用的对象。
    • 方法区中类静态属性引用的对象。
      • 比如:Java类的引用类型静态变量。
    • 方法区中常量引用的对象
      • 比如:字符串常量池里的引用。
    • 所有被同步锁syncchronized持有的对象。
    • Java虚拟机内部的引用。
      • 基本数据类型对应的Class对象,一些常驻的异常对象,系统类加载器。
    • 反映Java虚拟机内部情况的JMXBean,JVMTI中注册的回调,本地代码缓存等。

    在这里插入图片描述
    (红色是垃圾,蓝色不是垃圾)

    除了这些固定的GC Roots集合外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象临时性的假如,共同构成完整GC Roots集合。比如:分带收集和局部收集。

    如果只针对Java堆中的某一块区域进行垃圾回收,比如典型的只针对新生代,必须考虑到内存区域是虚拟机自己的实现细节,更不是孤立封闭的,这个区域的对象完全有可能被其他区域的对象所引用,这时候就需要一并将关联的区域也加入GC Roots集合中去考虑,才能保证可达性分析的准确性。

    小技巧:
    由于ROOT采用栈方式存放变量和指针,所以如果一个指针,它保存了堆内存里面的对象,但是自己又不存放在堆内存里面,那它就是一个Root.

    注意

    如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保证一致性的快照中进行。这点不满足的话分析结果的准确性就无法保证。 这点也是导致GC进行时必须Stop The World的一个重要原因,即使是号称几乎不会发生停顿的CMS收集器,枚举根节点时也是必须要停顿的。

  • 相关阅读:
    SQL server 2008链接服务器OLE DB 访问接口 "SQLNCLI10" 返回了消息 "未指定的错误"
    当需要在不同操作系统和编程语言环境中共享和处理 XML 数据时,可能会遇到哪些兼容性问题,以及如何解决?
    SpringBoot全局异常处理请求参数校验及响应体包装
    easyExcel不同版本按照模板导出
    CentOS8.0搭建 RockerMq
    推荐系统笔记(九):SGL --利用自监督对比学习缓解推荐系统长尾效应
    ssm+爱尚购物 毕业设计-附源码211622
    SpringBoot+Vue实现前后端分离的宠物医院管理系统
    30张图说清楚 TCP 协议
    HIVE存储格式和压缩方式详解
  • 原文地址:https://blog.csdn.net/weixin_40920359/article/details/127737727