JVM教程-15-垃圾回收相关算法

JVM GC垃圾回收机制简述       Java相比于C++一大特点便是其特有的自动垃圾回收机制,有了它,我们就可以不用再关系内存分配的问题了,也一般不需要担心出现内存泄漏。大家也可以愉快的聚焦于业务发展。那么Java的GC回收到底是怎么实现的呢,本文就来简单的说一下。     要想了解Java的GC机制,首先要对JVM的内存结构有一定的了解,首先让我们来一起了解一下。1、JVM内存结构     JVM内存结构通常来说由虚拟... 阅读详情

转载:
https://www.bilibili.com/video/BV1PJ411n7xZ
参考:
https://gitee.com/moxi159753/LearningNotes/tree/master/JVM,
https://www.cnblogs.com/yanl55555/p/12623447.html

引言

在这里插入图片描述

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

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

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

标记阶段:引用计数算法

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

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

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

  • 缺点:

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

循环引用

在这里插入图片描述

代码演示循环引用

/**
 * -XX:+PrintGCDetails
 * 证明:java使用的不是引用计数算法
 */
public class RefCountGC {
    // 这个成员属性的唯一作用就是占用一点内存
    private byte[] bigSize = new byte[5*1024*1024];
    // 引用
    Object reference = null;

    public static void main(String[] args) {
        RefCountGC obj1 = new RefCountGC();
        RefCountGC obj2 = new RefCountGC();
        obj1.reference = obj2;
        obj2.reference = obj1;
        obj1 = null;
        obj2 = null;
        // 显示的执行垃圾收集行为,判断obj1 和 obj2是否被回收?
        System.gc();
    }
}

在这里插入图片描述
在这里插入图片描述

如果使用引用计数算法,那么这两个对象将会无法回收。而现在两个对象被回收了,说明Java使用的不是引用计数算法来进行标记的。

在这里插入图片描述

小结

  • 引用计数算法,是很多语言的资源回收选择,例如因人工智能而更加火热的Python,它更是同时支持引用计数和垃圾收集机制。

  • 具体哪种最优是要看场景的,业界有大规模实践中仅保留引用计数机制,以提高吞吐量的尝试。

  • Java并没有选择引用计数,是因为其存在一个基本的难题,也就是很难处理循环引用关系。

  • Python如何解决循环引用?

     手动解除:很好理解,就是在合适的时机,解除引用关系。
     使用弱引用weakref,weakref是Python提供的标准库,旨在解决循环引用。
    

标记阶段:可达性分析算法

概念

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

用法

  • 所谓"GC Roots"根集合就是一组必须活跃的引用。
  • 可达性分析算法是以根对象集合(GCRoots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。
  • 使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Reference Chain)
  • 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。
  • 在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。

在这里插入图片描述

GC Roots

  • 虚拟机栈中引用的对象

     比如:各个线程被调用的方法中使用到的参数、局部变量等。	
    
  • 本地方法栈内JNI(通常说的本地方法)引用的对象

  • 方法区中类静态属性引用的对象

     比如:Java类的引用类型静态变量
    
  • 方法区中常量引用的对象

     比如:字符串常量池(string Table)里的引用
    
  • 所有被同步锁synchronized持有的对象

  • Java虚拟机内部的引用。

     基本数据类型对应的Class对象,一些常驻的异常对象(如:Nu11PointerException、outofMemoryError),系统类加载器。
    
  • 反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

在这里插入图片描述

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

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

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

注意

  • 如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的快照中进行。这点不满足的话分析结果的准确性就无法保证。

     就是你在分析的时候,这些引用要保持不变
    
  • 这点也是导致GC进行时必须“stop The World”的一个重要原因。

  • 即使是号称(几乎)不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。

GC进行的停顿重要原因是进行了可达性分析

对象的finalization机制

  • Java语言提供了对象终止(finalization)机制来允许开发人员提供对象被销毁之前的自定义处理逻辑。

  • 当垃圾回收器发现没有引用指向一个对象,即:垃圾回收此对象之前,总会先调用这个对象的finalize()方法。

  • finalize()方法允许在子类中被重写,用于在对象被回收时进行资源释放。通常在这个方法中进行一些资源释放和清理的工作,比如关闭文件、套接字和数据库连接等。

  • 永远不要主动调用某个对象的finalize()方法I应该交给垃圾回收机制调用。理由包括下面三点:

     ① 在finalize()时可能会导致对象复活。
     ② finalize()方法的执行时间是没有保障的,它完全由Gc线程决定,极端情况下,若不发生GC,则finalize()方法将没有执行机会。
     ③ 一个糟糕的finalize()会严重影响Gc的性能。
    
  • 从功能上来说,finalize()方法与c++中的析构函数比较相似,但是Java采用的是基于垃圾回收器的自动内存管理机制,所以finalize()方法在本质上不同于C++中的析构函数。

由于finalize()方法的存在,虚拟机中的对象一般处于三种可能的状态。

生存还是死亡?

如果从所有的根节点都无法访问到某个对象,说明对象己经不再使用了。一般来说,此对象需要被回收。但事实上,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段。一个无法触及的对象有可能在某一个条件下“复活”自己,如果这样,那么对它的回收就是不合理的,为此,定义虚拟机中的对象可能的三种状态。如下:

  • 可触及的:从根节点开始,可以到达这个对象。
  • 可复活的:对象的所有引用都被释放,但是对象有可能在finalize()中复活。
  • 不可触及的:对象的finalize()被调用,并且没有复活,那么就会进入不可触及状态。不可触及的对象不可能被复活,因为finalize()只会被调用一次。

以上3种状态中,是由于finalize()方法的存在,进行的区分。只有在对象不可触及时才可以被回收。

具体过程

判定一个对象objA是否可回收,至少要经历两次标记过程:

  1. 如果对象objA到GC Roots没有引用链,则进行第一次标记。
  2. 进行筛选,判断此对象是否有必要执行finalize()方法
  • 如果对象objA没有重写finalize()方法,或者finalize()方法已经被虚拟机调用过,则虚拟机视为“没有必要执行”,objA被判定为不可触及的。
  • 如果对象objA重写了finalize()方法,且还未执行过,那么objA会被插入到F-Queue队列中,由一个虚拟机自动创建的、低优先级的Finalizer线程触发其finalize()方法执行。
  • finalize()方法是对象逃脱死亡的最后机会,稍后GC会对F-Queue队列中的对象进行第二次标记。如果objA在finalize()方法中与引用链上的任何一个对象建立了联系,那么在第二次标记时,objA会被移出“即将回收”集合。之后,对象会再次出现没有引用存在的情况。在这个情况下,finalize方法不会被再次调用,对象会直接变成不可触及的状态,也就是说,一个对象的finalize方法只会被调用一次。

在这里插入图片描述

代码测试对象可复活

/**
 * 测试Object类中finalize()方法,即对象的finalization机制。
 *
 * 没重写finalize
 * 第1次 gc
 * obj is dead
 * 第2次 gc
 * obj is dead
 *
 * 重写finalize
 * 调用当前类重写的finalize()方法
 * 第1次 gc
 * obj is still alive
 * 第2次 gc
 * obj is dead
 */
public class CanReliveObj {
    public static CanReliveObj obj;//类变量,属于 GC Root


//    此方法只能被调用一次
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("调用当前类重写的finalize()方法");
        obj = this;//当前待回收的对象在finalize()方法中与引用链上的一个对象obj建立了联系
    }


    public static void main(String[] args) {
        try {
            obj = new CanReliveObj();
            // 对象第一次成功拯救自己
            obj = null;
            System.gc();//调用垃圾回收器
            System.out.println("第1次 gc");
            // 因为Finalizer线程优先级很低,暂停2秒,以等待它
            Thread.sleep(2000);
            if (obj == null) {
                System.out.println("obj is dead");
            } else {
                System.out.println("obj is still alive");
            }
            System.out.println("第2次 gc");
            // 下面这段代码与上面的完全相同,但是这次自救却失败了
            obj = null;
            System.gc();
            // 因为Finalizer线程优先级很低,暂停2秒,以等待它
            Thread.sleep(2000);
            if (obj == null) {
                System.out.println("obj is dead");
            } else {
                System.out.println("obj is still alive");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

MAT与JProfiler的GC Roots溯源

MAT是Memory Analyzer的简称,它是一 款功能强大的Java堆内存分析器。用于查找内存泄漏以及查看内存消耗情况。
MAT是基于Eclipse开发的,是一款免费的性能分析工具。
可以在http://www.eclipse org/mat/下载并使用MAT。

获取dump文件

方式1: 命令行使用jmap(后面具体说)

在这里插入图片描述

方式2:使用JVisualVM导出

捕获的heap dump文件是一个临时文件,关闭JVisua1VM后自动删除,若要保留,需要将其另存为文件。
可通过以下方法捕获heap dump:

  • 在左侧“Application”(应用程序)子窗口中右击相应的应用程序,选择Heap Dump(堆Dump)。
  • 在Monitor (监视)子标签页中点击Heap Dump (堆Dump)按钮。

本地应用程序的Heap dumps作为应用程序标签页的一个子标签页打开。同时, heap dump在左侧的Application (应用程序)栏中对应一个含有时间戳的节点。右击这个节点选择save as (另存为)即可将heap dump保存到本地。

public class GCRootsTest {
    public static void main(String[] args) {
        List<Object> numList = new ArrayList<>();
        Date birth = new Date();

        for (int i = 0; i < 100; i++) {
            numList.add(String.valueOf(i));
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("数据添加完毕,请操作:");
        new Scanner(System.in).next();
        numList = null;
        birth = null;

        System.out.println("numList、birth已置空,请操作:");
        new Scanner(System.in).next();

        System.out.println("结束");
    }
}

在这里插入图片描述
在这里插入图片描述

使用MAT查看GC Roots:打开dump文件->数据库图标->Java Basics->GC Roots

JProfiler的GC Roots溯源

在这里插入图片描述

使用Jprofiler分析OOM

/**
 * -Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
 *
 */
public class HeapOOM {
    byte[] buffer = new byte[1 * 1024 * 1024];//1MB

    public static void main(String[] args) {
        ArrayList<HeapOOM> list = new ArrayList<>();

        int count = 0;
        try{
            while(true){
                list.add(new HeapOOM());
                count++;
            }
        }catch (Throwable e){
            System.out.println("count = " + count);
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

将生成的dump文件打开,然后点击Biggest Objects就能够看到超大对象

通过线程,还能够定位到哪里出现OOM

垃圾清除阶段

当成功区分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收,释放掉无用对象所占用的内存空间,以便有足够的可用内存空间为新对象分配内存。目前在JVM中比较常见的三种垃圾收集算法是

  • 标记一清除算法(Mark-Sweep)
  • 复制算法(copying)
  • 标记-压缩算法(Mark-Compact)

清除阶段:标记-清除算法(Mark-Sweep)

背景

标记-清除算法(Mark-Sweep)是一种非常基础和常见的垃圾收集算法,该算法被J.McCarthy等人在1960年提出并并应用于Lisp语言。

执行过程:

当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被称为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除

  • 标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。标记的是引用的对象,不是垃圾!!
  • 清除:Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收。
    在这里插入图片描述

缺点

  • 效率不算高
  • 在进行GC的时候,需要停止整个应用程序,用户体验较差
  • 这种方式清理出来的空闲内存是不连续的,产生内碎片,需要维护一个空闲列表(类似创建对象分配空间的某种方式)

何为清除?(类似window的格式化机械硬盘)

这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放覆盖原有的地址。

清除阶段:复制算法

背景

为了解决标记-清除算法在垃圾收集效率方面的缺陷,M.L.Minsky于1963年发表了著名的论文,“使用双存储区的Lisp语言垃圾收集器CA LISP Garbage Collector Algorithm Using Serial Secondary Storage)”。M.L.Minsky在该论文中描述的算法被人们称为复制(Copying)算法,它也被M.L.Minsky本人成功地引入到了Lisp语言的一个实现版本中。

核心思想

将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收
在这里插入图片描述

优点

  • 没有标记和清除过程,实现简单,运行高效
  • 复制过去以后保证空间的连续性,不会出现“碎片”问题。

缺点

  • 此算法的缺点也是很明显的,就是需要两倍的内存空间。
  • 对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小。
  • 即特别适合垃圾对象很多,存活对象很少的场景;例如:Young区的Survivor0和Survivor1区

应用场景

在新生代,对常规应用的垃圾回收,一次通常可以回收70%一 99%的内存空间。回收性价比很高。所以现在的商业虚拟机都是用这种收集算法回收新生代。
在这里插入图片描述

清除阶段:标记-压缩算法(或者标记-整理,Mark-Compact)

背景

复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。这种情况在新生代经常发生,但是在老年代,更常见的情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活对象较多,复制的成本也将很高。因此,基于老年代垃圾回收的特性,需要使用其他的算法。

标记一清除算法的确可以应用在老年代中,但是该算法不仅执行效率低下,而且在执行完内存回收后还会产生内存碎片,所以JvM的设计者需要在此基础之上进行改进。标记-压缩(Mark-Compact)算法由此诞生。

1970年前后,G.L.Steele、C.J.Chene和D.s.Wise等研究者发布标记-压缩算法。在许多现代的垃圾收集器中,人们都使用了标记-压缩算法或其改进版本。

执行过程

  1. 第一阶段和标记清除算法一样,从根节点开始标记所有被引用对象
  2. 第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。之后,清理边界外所有的空间。

在这里插入图片描述

区别

标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark-Sweep-Compact)算法。

二者的本质差异在于标记-清除算法是一种非移动式的回收算法,标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策。可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。

指针碰撞:如果内存空间以规整和有序的方式分布,即已用和未用的内存都各自一边,彼此之间维系着一个记录下一次分配起始点的标记指针,当为新对象分配内存时,只需要通过修改指针的偏移量将新对象分配在第一个空闲内存位置上,这种分配方式就叫做指针碰撞(Bump the Pointer) 。

优点

  • 消除了标记-清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可。
  • 消除了复制算法当中,内存减半的高额代价。

缺点

  • 从效率上来说,标记-整理算法要低于复制算法。
  • 移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址
  • 移动过程中,需要全程暂停用户应用程序。即:STW

小结

标记清除标记整理复制
速率中等最慢最快
空间开销少(但会堆积碎片)少(不堆积碎片)通常需要活对象的2倍空间(不堆积碎片)
移动对象否是是
  • 效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存。
  • 而为了尽量兼顾上面提到的三个指标,标记-整理算法相对来说更平滑一些,但是效率上不尽如人意,它比复制算法多了一个标记的阶段,比标记-清除多了一个整理内存的阶段。

没有最好的算法,只有最合适的算法

分代收集算法

引言

  • 前面所有这些算法中,并没有一种算法可以完全替代其他算法,它们都具有自己独特的优势和特点。分代收集算法应运而生。
  • 分代收集算法,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,**不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。**一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点使用不同的回收算法,以提高垃圾回收的效率。
  • 在Java程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如Http请求中的Session对象、线程、Socket连接,这类对象跟业务直接挂钩,因此生命周期比较长。但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:string对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。

HotSpot中,基于分代的概念

目前几乎所有的GC都采用分代手机算法执行垃圾回收的

在HotSpot中,基于分代的概念,GC所使用的内存回收算法必须结合年轻代和老年代各自的特点。

  • 年轻代(Young Gen)

     年轻代特点:区域相对老年代较小,对象生命周期短、存活率低,回收频繁。
    
     这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关,因此很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓解。
    
  • 老年代(Tenured Gen)

     老年代特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。
    
     这种情况存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记-清除或者是标记-清除与标记-整理的混合实现。
     	① Mark阶段的开销与存活对象的数量成正比。
     	② Sweep阶段的开销与所管理区域的大小成正相关。
     	③ compact阶段的开销与存活对象的数据成正比。
    

以HotSpot中的CMS回收器为例,CMS是基于Mark-Sweep实现的,对于对象的回收效率很高。而对于碎片问题,CMS采用基于Mark-Compact算法的Serial old回收器作为补偿措施:当内存回收不佳(碎片导致的Concurrent Mode Failure时),将采用serial old执行FullGC以达到对老年代内存的整理。

分代的思想被现有的虚拟机广泛使用。几乎所有的垃圾回收器都区分新生代和老年代

增量收集算法

概述

上述现有的算法,在垃圾回收过程中,应用软件将处于一种stop the World的状态。在stop the World状态下,应用程序所有的线程都会挂起,暂停一切正常的工作,等待垃圾回收的完成。如果垃圾回收时间过长,应用程序会被挂起很久,将严重影响用户体验或者系统的稳定性。为了解决这个问题,即对实时垃圾收集算法的研究直接导致了增量收集(Incremental Collecting)算法的诞生。

如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。

总的来说,增量收集算法的基础仍是传统的标记-清除和复制算法。增量收集算法通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作

缺点

使用这种方式,由于在垃圾回收过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

分区算法

一般来说,在相同条件下,堆空间越大,一次Gc时所需要的时间就越长,有关GC产生的停顿也越长。为了更好地控制GC产生的停顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。

分代算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同小区间。 每一个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间。
在这里插入图片描述

JVM-垃圾回收 垃圾回收及其算法 阅读详情

相关推荐

【JVM】一文掌握JVM垃圾回收机制

作为Java程序员,除了业务逻辑以外,随着更深入的了解,都无法避免的会接触到JVM以及垃圾回收相关知识。JVM调优是一个听起来很可怕,实际上很简单的事。 感到可怕,是因为垃圾回收相关机制都在JVM的C++层实现,我们在Java开发中看不见摸不着;而实际很简单,是因为它说到底,也只是JVM替我们实现的垃圾对象回收机制,也是普通的程序代码,只要理解了垃圾回收器的底层设计思想,掌握JVM调优并非难事!

何小成的成长记录 1615

JDK15新特性

文章目录背景主要新特性密封类概念使用隐藏类概念特性相关APIinstanceof模式匹配ZGC转正文本块转正介绍JDK15中的改进Records类介绍使用次要新特性EdDSA数字签名算法重新实现DatagramSocket API禁用偏向锁定Shenandoah回收器转正外部存储器访问API移除Solaris和SPARC端口移除Nashorn JS引擎废弃RMI激活机制其他新特性添加项移除项和废弃项其他事项 背景 本文记录Oracle发布的JDK15新特性学习笔记,来源于宋红康的相关视频 主要新特性 密封类

四国棋客 1982

JVM垃圾回收相关面试问题总结

大多数情况下,对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:新生代内存(Young Generation)老生代(Old Generation)永久代(Permanent Generation)(JDK 8 版本之后 PermGen(永久代) 已被 Metaspace(元空间) 取代,元空间使用的是本地内存,方法区的实现就是由Metaspace,方法区是个逻辑概念。

qazwsxxxxxasd的博客 1155

JVM-垃圾回收器

官方文档G1在JDK1.7出现,1.8成熟,1.9成为默认垃圾回收器。G1是一个分代的,增量的,并行与并发的标记-复制垃圾回收器。它的设计目标是为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间(pause time),同时兼顾良好的吞吐量。G1还有一个及其重要的特性:软实时(soft real-time)。所谓的实时垃圾回收,是指在要求的时间内完成垃圾回收。

namelessmyth的专栏 1120

垃圾回收算法与实现

垃圾回收,相关的算法与实现,非常不错的书籍,垃圾回收,相关的算法与实现,非常不错的书籍,

量化实战:用三种波动率模型给你的期货策略做个“体检”(附Python代码与螺纹钢案例)

本文深入探讨了三种专业波动率模型(Parkinson、Garman-Klass和Rogers-Satchell)在期货量化策略中的应用,并以螺纹钢期货为例,展示了如何通过Python实现这些模型进行多维度波动率分析。文章详细比较了不同模型的特点和适用场景,提供了动态仓位管理、止损策略优化等实战应用方案,帮助交易者更精准地评估市场风险,优化策略表现。

weixin_26824299的博客 460

JVM监控和诊断的工具(JProfiler)

存在大的对象:读取文件时,byte[]应该 边读边写,如果长时间不写出的话,导致byte[]过大。根据标记当前,看那个JAVA对象频繁创建,就有可能存在内存溢出:死循环,循环次数过多。如果存在内存泄露问题开启记录的对象功能,但是会影响系统性能。1、web容器的线程最大数,比如:Tomcat的线程容量应该略大于最大并发数。方法执行时间越长,占用CPU率就越高,通过查看时间分析。4.2、重构模式(Instrumentation)优点:对CPU的开销非常低,对应用影响小。重点:实时内存,CPU,线程。

CB_Beginner的博客 720

硬件基础(4):(6)高精度ADC设计中模拟地与数字地的划分与连接

在实际 PCB 设计中,**接地与布线**对于获得干净的参考电压和高精度 ADC 测量至关重要。下面详细阐述**如何在硬件层面减少数字噪声耦合到模拟电路、保障参考电压的稳定性**。

FCZlll的博客 2642

垃圾回收篇~~垃圾回收算法

一、垃圾回收算法分类 1、标记-清除算法 最基础的算法,分为标记和清除两个阶段:首先标记出所有需要回收的对象,完成后统一回收掉所有被标记的对象。后续的算法都是基于此算法来改进的。 主要有两个缺点: 效率问题:标记和清除的过程效率都不高。 空间问题:标记清除之后会产生大量不连续的内存碎片,这样可能会导致以后再运行过程中需要分配大对象时无法找到足够的连续内存而不得不触发新的一次垃圾回收。 标记-清除算法图示-1 2、复制算法 为了解决效率问题,复制算法出现了。将内存按容量划分为大小相等的两块,每次只使用其

Dream 2915

camera 知识速查

本文总结了Camera开发中的常见问题排查方法和关键技术点。主要内容包括:I2C/MIPI不通的排查步骤、时序问题分析方法、Sensor/AF/Flash/PDAF的Bring Up流程、OTP调试方法、PDAF原理及调试、CTS/ITS问题解决思路、AE震荡解决方案、Systrace性能分析方法、Flicker产生原理、Tombstone问题定位等。还涉及V4L2框架解析、问题定位分层方法、Sensor Setting注意事项、时钟信号概念、第三方应用卡顿排查、大光圈概念、Monkey测试方法。

qq_41665131的博客 783

银河飞腾FT6678实战指南:OpenMP多核并行编程优化技巧

本文详细介绍了在银河飞腾FT6678多核处理器上使用OpenMP进行并行编程的实战技巧与优化方法。从基础语法到环境配置,再到性能优化的五大核心技巧,包括循环并行化、并行粒度控制、reduction子句使用等,帮助开发者充分发挥FT6678的8核计算能力。通过实际案例展示如何将图像处理算法从200ms优化到28ms,并深入探讨了任务调度、内存访问优化等进阶主题。

weixin_29234423的博客 285

九、JVM从入门到精通之垃圾回收机制(算法篇)

前言 垃圾收集,不是Java语言的伴生产物。早在1960年,第一门开始使用内存动态分配和垃圾收集技术的Lisp语言诞生。 关于垃圾收集有三个经典问题: 哪些内存需要回收? 什么时候回收、 如何回收? 垃圾收集机制是Java的招牌能力,极大地提高了开发效率。如今,垃圾收集几乎称为现代语言的标配,即使经过如此长的时间发展,Java的垃圾收集机制仍然在不断的演进中,不同大小的设备、不同特征的应用场景,对垃圾收集提出了新的挑战,这当然也是面试的热点。 垃圾回收热点面试题 蚂蚁金服: 你知道哪几种垃圾回收器,

猿小许的博客 436

PCL 将对象模板与点云对齐【2025最新版】

PCL模版匹配算法的使用案例,博客长期更新,本文最近更新时间为:2025年1月18日。

点云侠的博客 5378

【JVM】垃圾回收

文章目录JVM-垃圾回收1.如何判断对象可以回收1-1 引用计数法1-2 可达性分析算法1-3 五种引用2.垃圾回收算法2-1 标记清除2-2 标记整理2-3 复制3.分代垃圾回收3-1 回收流程3-2 GC 分析4.垃圾回收器4-1 串行4-2 吞吐量优先4-3 响应时间优先4-4 G15.垃圾回收调优5-1 调优领域5-2 确定目标5-3 最快的 GC5-4 新生代调优5-5 老年代调优 JVM-垃圾回收 本文章参考:黑马程序员JVM 1.如何判断对象可以回收 1-1 引用计数法 当一个对象被其他变量

小皮皮LL的博客 4089

从零开始学习PX4源码20(遥控器模式切换如何执行)

本节主要学习PX4的手动遥控器切换模式,具体是如何实现的,具体改变了哪些变量,和模式管理有什么联系。

陌城烟雨 315

车载软件刷写工具vFlash --- 自动化接口(Automation API)应用简介

车载软件刷写工具vFlash --- 自动化接口(Automation API)应用简介

Soly_kun的博客 359

【JVM基础篇】Java的四种垃圾回收算法介绍

本博客从垃圾回收的基本概念出发,追溯至其历史起源,深入探讨了标记-清除算法与复制算法的演变,并在此基础上分析了现代垃圾回收算法的优化与发展。文章不仅阐述了垃圾回收算法的评价标准——吞吐量、最大暂停时间及堆使用效率,还提供了具体代码示例来验证Stop The World现象,即GC过程中对用户线程的影响。

我只管埋头前行,剩下的交给时间。 4066

【QNX Hypervisor 2.2 用户手册】2.6 处理qvm终止

你可以注册通知,以在qvm进程实例退出时得到通知。这样你就可以采取相应的措施来响应这种终止事件。

从0到1,突破自己 1103

JVM之GC垃圾回收

JVM之GC垃圾回收

qq_60749185的博客 2987

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

JVM垃圾回收

再次创建对象,如果伊甸园区又满了,再次触发Minor GC,这时不仅会回收伊甸园垃圾,也会回收幸存区垃圾,再将可用对象(伊甸园区和幸存区from)复制到TO中,之后交换两个幸存区,并让幸存区中对象寿命+1。软弱引用还可以配合,引用队列使用,因为软引用,弱引用本身就是一个对象,当他们所引用对象被回收时,他们就会进入这个引用队列,因为他们自身也会占有一定内存,如果想对他们的内存进行释放,就需要引用队列。在对象中添加一个引用计数器,当有地方引用时计数器就加一,引用失效时计数器减一,当计数器值为0时,进行回收。

GJ_863的博客 2080

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

15.垃圾回收相关算法

总结一句话就是,除了堆空间外的一些结构,比如 虚拟机栈、本地方法栈、方法区、字符串常量池 等地方对堆空间进行引用的,都可以作为GC Roots进行可达性分析除了这些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整GC Roots集合。比如:分代收集和局部回收(PartialGC)。

The_flying_pig的博客 1044

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

图解JVM - 11.垃圾回收概述及算法

本文深入解析JVM垃圾回收机制,系统探讨内存泄漏、GC调优、Full GC频发等核心问题,结合典型案例与调优参数矩阵给出解决方案。针对高频面试考点,剖析引用类型、收集器原理及OOM排查技巧,通过多维度技术图解(如G1/ZGC创新设计)和实战命令,助力开发者构建完整的GC知识体系。

Anarkh_Lee的博客 1497

JVM的垃圾回收机制

(默认的大小分配是8:1:1),其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活的对象将被移动到老年代。这时因为并发标记与重新标记阶段都是从灰色对象开始遍历,而C没有与任何一个灰色对象连接,所以C不会被标记,会被当成垃圾回收,这时候问题就很严重了,应为A还引用了C呢,在业务层面需要C对象,这必然会导致严重的事故。

release_lonely的博客 2034

JVM-垃圾回收 (一文弄懂)

在Java虚拟机(JVM)中,垃圾收集(GC)是自动管理内存的重要机制。其主要任务是识别不再被引用的对象并回收其占用的内存。

我爱JAVA 2187

JVM 垃圾回收

在CMS的并发标记和并发清理阶段, 用户线程是还在继续运行的, 程序在运行自然就还会伴随有新的垃圾对象不断产生, 但这一部分垃圾对象是出现在标记过程结束以后, CMS无法在当次收集中处理掉它们, 只好留待下一次垃圾收集时再清理掉。如果当用户需要存入一个很大的对象时,新生代放不下去,老年代由于浮动垃圾过多,就会退化为 serial Old 收集器,将老年代垃圾进行标记-整理,很耗费时间,本来是响应优先的垃圾回收器,响应时间变得更长了,这也是CMS存在的问题。因为是串行的,所以只有一个垃圾回收线程。

人生需要动态规划 学习需要贪心算法 1337

JVM垃圾回收详解

垃圾回收器的组合关系虽然很多,但是针对几个特定的版本,比较好的组合选择如下:JDK8及之前:ParNew+CMS(关注暂停时间)、ParallelScavenge+ParallelOld(关注吞吐量)、G1(JDK8之前不建议,较大堆并且关注暂停时间)JDK9之后:G1(默认)从JDK9之后,由于G1日趋成熟,JDK默认的垃圾回收器已经修改为G1,所以强烈建议在生产环境上使用G1。

Y_eatMeat的博客 1119
上一篇: JVM教程-14-垃圾回收概述
下一篇: JVM教程-16-垃圾回收相关概念
zgcadmin
博客等级 码龄9年 174粉丝 109原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值