JVM学习-垃圾回收专题

目录

1.如何判断对象可以回收

        1.1引用计数法

        1.2可达性分析算法  

        1.3五种引用

        1.4拓展:直接内存

2.垃圾回收算法

        2.1标记清除算法

        2.2标记整理算法

        2.3复制

3.分代垃圾回收

3.垃圾回收器

        3.1串行垃圾回收器

        3.2吞吐量优先垃圾回收器

        3.3响应时间优先垃圾回收器 (CMS)

        3.4 G1垃圾回收器 

                3.4.1 相关介绍

               3.4.2 内部示意图(方便理解的)

                3.4.3 工作机制

                3.4.5字符串去重

                3.4.6类卸载

                3.4.7回收巨型对象

                3.4.8动态调整堆内存占用阈值

4.MinorGC和FullGC 

5.垃圾回收调优 

        5.1垃圾回收器选择

        5.2代码角度

        5.3内存调优

                5.3.1新生代调优

                        5.3.1.1调节新生代内存大小                         

                        5.3.1.2调节幸存区的大小

                 5.3.2老年代调优                       

        5.4案例调优分析

                 5.4.1GC发生频繁

                5.3.2请求高峰期发生FullGC且时间较长(CMS等并发回收器)

                5.3.3当使用JDK1.8之前的版本时出现老年代内存充足但有FullGC发生的情况



1.如何判断对象可以回收

        1.1引用计数法

        当一个对象被另一个对象引用时引用计数加一,当不被引用时计数减一,当计数为零也就是没有对象引用它时就将其当做垃圾回收;但这个方法有弊端,当两个对象相互引用时,即使这两个对象不再被使用,但这两个对象的计数都是一,还是无法被回收,造成内存泄漏。

        1.2可达性分析算法  

        在进行垃圾回收时先对堆内存进行扫描,查看是否每个对象都被根对象直接或间接地引用,如果被引用则不能作为垃圾回收,如果没有则会被当做垃圾回收。根对象包括:系统类(运行的核心类)、正在加锁的对象、活动线程使用的对象 (线程在运行时会进行方法调用,每次方法调用都是一个栈帧,也就是栈帧中那些局部变量所引用的对象都是根对象,比如String s=new String(),前半段是局部变量,存在于栈帧中,后半段才是引用的对象,放在堆内存中,后半段才是根对象;但当s=null后,String不再被引用,所以它就不再是根对象,再执行垃圾回收就会被回收掉),还有就是操作系统方法在执行时引用的java对象(JVM在进行一些方法调用时必须要用到一些操作系统的方法,所以这些方法引用的java对象也是根对象不能被回收)。

        1.3五种引用

        强引用:只有当该对象没有被强引用时才能被回收(new的对象都是强引用)。
        软引用:在只有软引用引用该对象时,在进行一次垃圾回收后若内存仍不足则会再次触发垃圾回收,此时会回收该对象;当该对象回收后,可以利用引用队列将软引用本身也回收掉,软引用自身也是一个对象,也会占用内存。
        弱引用:在只有弱引用引用该对象时,只要进行垃圾回收就会回收该对象;当该对象回收后,可以利用引用队列将弱引用回收掉,弱引用自身也是一个对象,也会占用内存。
        虚引用:必须配合引用队列使用,主要用于释放直接内存。当被引用对象被回收时(即被引用对象使用完毕),就会把虚引用放入引用队列,然后ReferenceHandler线程会定时在队列中查找有无虚引用,如果有就会调用虚引用的相关方法释放直接内存。
        终结器引用:须配合引用队列使用,在垃圾回收时会将终结器引用入队,此时被引用对象还没有被回收,再由Finalizer线程通过终结器引用找到被引用对象并调用它的finalize方法,第二次垃圾回收时才会回收被引用对象;由于Finalizer线程的优先级很低,所以finalize方法被执行的机会很少,并且需要两次垃圾回收才能将被引用对象回收掉,所以终结器引用用的次数很少。
          在实际开发中,比如要存一些图片,这些图片并不是核心业务资源,如果都采用强引用很可能导致内存不足,因为当垃圾回收时并不会回收强引用的对象,这时就可以使用软引用或弱引用,当内存不足时可以先回收一部分图片,待下次用时再加载即可,解决了内存占用过大的问题。

        1.4拓展:直接内存

        直接内存是操作系统的一块内存,用于供Java读写磁盘文件时使用。

        如果不使用直接内存执行IO操作,读写性能较低:

        如果使用直接内存,那么Java程序就能直接从直接内存读写数据,不用复制两次了:

        直接内存的分配:

//申请一块1Mb的直接内存
static int _1Mb=1024*1024;
ByteBuffer bb=ByteBuffer.allocateDirect(_1Mb);

//allocateDirect()底层是通过new了一个DirectBuffer对象实现的
public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }

//DirectByteBuffer构造器中是使用了Unsafe对象的setMemory方法
    DirectByteBuffer(int cap) {                   // package-private

        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        Bits.reserveMemory(size, cap);

        long base = 0;

        //调用了Unsafe对象的allocateMemory和setMemory方法实现分配直接内存
        try {
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);

        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }

        //使用虚引用对象进行监听
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;


    }

        由于直接内存不受JVM垃圾回收的管理,所以当直接内存占用过大时也会出现内存溢出的问题(比如一直分配多块直接内存且不被释放),需要对直接内存进行回收。而Unsafe对象可以完成对直接内存的回收,这需要调用Unsafe对象的freeMemory方法。

        一种回收方法是通过显式的垃圾回收(即输入代码System.gc()手动调用垃圾回收),将ByteBuffer对象回收掉,由于ByteBuffer的实现类内部使用了Cleaner虚引用来监测ByteBuffer对象,一旦被回收就由ReferenceHandler线程通过Cleaner的clean方法调用run(),而run方法中调用了freeMemory方法释放直接内存;另一种方法是直接用Usafe对象调用freeMemory方法释放直接内存。

       在进行JVM调优时一般会禁用显式的垃圾回收,因为显式gc不止回收新生代也会回收老年代,造成程序暂停时间较长,这时由于禁用了显式调用垃圾回收就无法通过释放ByteBuffer对象触发对直接内存的释放,所以就主动调用freeMemory方法释放直接内存。

2.垃圾回收算法

        2.1标记清除算法

        首先标记那些在堆内存中没有被引用的对象,然后再将这些对象占用的空间释放掉;释放并不是说把这部分内存中的数据清零,而是将这些内存的首尾地址放入一个空闲链表中表示这一区间的内存是空闲的,再次用到时直接将原来的数据覆盖掉。这种算法的优点就是速度快,只需要将首尾地址放入空闲链表中,但缺点就是会产生内存碎片,当需要分配一块较大的连续空间时,这些内存总和虽然满足,但每一块都是一小块空间,并没有将这些空间进行整理,会导致内存溢出

        2.2标记整理算法

        首先标记那些在堆内存中没有被引用的对象,然后在清除的过程中会将其他对象占用的内存前移,使得这些对象更紧凑,且能产生较大的连续空间。优点是没有内存碎片,缺点就是速度较慢,需要对剩下的对象进行整理。

        2.3复制

        首先标记那些在堆内存中没有被引用的对象,然后将存活的对象先复制到另一片空闲的区域,复制完成后原来的整个区域便可以当做一个空闲区域,然后交换这两个区域,将原来的区域作为下一次复制的空闲区域,解决了内存碎片的问题同时也提高了速度,缺点就是需要双倍的内存空间

        实际情况中这三种算法混合使用。

3.分代垃圾回收

        下面是和垃圾回收相关的参数,可以在编写项目时使用 :

        新生代回收时的跨代引用
        在垃圾回收时会先标记根对象,这些根对象要么被堆外部所引用,要么被老年代的对象所引用,这就需要遍历老年代来查找哪些新生代中的对象被老年代所引用,但遍历老年代所花费的时间较长,并且这种跨代引用的情况很少,所以使用记忆集来记录老年代中哪些引用了新生代中的对象,这样在标记根对象时就可以只看那些被堆外部引用的对象以及记忆集中的信息。记忆集存放在新生代中。

        但是这种方法也有漏洞,当老年代中的一个对象不可达(不被堆外引用)但还是引用了一个新生代对象时(比如上图的U和V),这个引用就会记录在记忆集中,但实际上这个新生代的对象也不可达了,需要被回收,但在标记根对象时由于记忆集中还有这条引用信息所以这个对象不会被回收,所以记忆集在提高时间效率的同时也降低了空间利用率。 

        不过无论如何,它依然确保了垃圾回收所遵循的原则:垃圾回收确保回收的对象必然是不可达对象,但是不确保所有的不可达对象都会被回收

        分代垃圾回收的案例:

        初始状态下各部分空间充足:

        然后添加一个7Mb大小的对象,并存到list中防止被垃圾回收器回收掉(存到list中就是一直被使用了,除非list置空) :

 

3.垃圾回收器

        3.1串行垃圾回收器

        3.2吞吐量优先垃圾回收器

        吞吐量优先的意思是追求总的垃圾回收时间在总的工作时间中的占比尽可能低。 

        3.3响应时间优先垃圾回收器 (CMS)

        响应时间优先追求的是在每次gc时的暂停时间(STW,stop the world)尽可能少,也就是追求低延迟。

        补充,为什么后面还要进行一次重新标记? 

        在并发标记时,由于标记垃圾的同时其他线程也在工作,所以会出现这样一种情况,某个对象在进行标记时是垃圾,但后面有一个线程的对象又引用了这个对象且并发标记还没有进行完,这时就又不能被清除;所以当一个对象在初始标记后,在并发标记完成前如果这个对象的引用关系发生了变化,那么就会先执行写屏障,也就是将这个对象放入到一个待处理队列,等到并发标记结束后会对这个待处理队列中所有对象再重新标记一次。

        3.4 G1垃圾回收器 

                3.4.1 相关介绍

               3.4.2 内部示意图(方便理解的)

                3.4.3 工作机制

        三个阶段循环进行:

  • 当伊甸园被占满时会触发垃圾回收,同时也会触发STW;幸存的对象会以复制的算法放到幸存区(对应示意图中E->S);当一个幸存区内存不足时则会再次触发垃圾回收,寿命达到阈值的会将其放入老年代中(对应示意图中S->O),其他寿命不足的则会复制到其他幸存区中(对应示意图中S->S)。
  • 新生代在垃圾回收时会进行初始标记,标记那些根对象,当老年代的内存占用达到阈值时,则会进行并发标记。
  • 并发标记完成后会先进行最终标记,类似于CMS的重新标记,也会触发STW;然后对伊甸园、幸存区和老年代的幸存对象进行复制,在此期间也会触发STW;其中在对老年代进行垃圾回收时,由于回收时间较长,且设置了最大暂停时间,所以G1只会选择最有价值的几个老年区进行垃圾回收,所谓最有价值也就是对这些老年区回收后能释放的空间最多;当然如果对所有老年区进行回收的时间在最大暂停时间内,G1也是会对所有的老年区进行回收。
                3.4.5字符串去重

        G1会将所有新分配的字符串放入一个队列,然后在进行新生代垃圾回收时会并发检查是否有字符串重复,由于字符串底层是一个char数组,所以如果他们的值一样则让他们引用同一个char数组,但他们还是两个不同的对象。

        优点是可以节省内存,缺点就是略微占用CPU的时间,垃圾回收的时间略微增加(因为要检查是否有重复的)。

        注意区分intern的去重方法,intern方法的结果是两个对象变成了同一个串池中的对象,而G1的结果是他们还是两个不同的对象,但底层只存储一个char数组

                3.4.6类卸载

        在并发标记结束后就能知道哪些类不被使用了,如果不进行类卸载就会一直占用内存,所以G1就会在一个类加载器中所有类都不被使用时卸载这个类加载器中的所有类。由于JDK中都是启动类加载器、扩展类加载器以及应用程序类加载器,会始终存在,所以类卸载主要用于自定义类加载器。

                3.4.7回收巨型对象

        当一个对象所占空间的大小占一个region(区域)的一半以上时就被称为巨型对象,G1会将这种对象单独放在一个或多个连续的region中;在进行垃圾回收时这些巨型对象会被优先考虑回收,并且在进行复制时不会复制巨型对象;当这些巨型对象没有被堆外部或老年代的对象所引用时就会在新生代垃圾回收中被回收掉。

                3.4.8动态调整堆内存占用阈值

        JDK9之前可以通过设置堆内存占用阈值来触发垃圾回收,但这个值是固定的,设置的较大就容易引发FullGC,退化为串行或并行回收器,设置的较小就容易产生频繁的标记清理,所以JDK9之后就可以只设置阈值的初始值,G1会在垃圾回收时对数据进行采样来动态调整阈值的大小,避免出现FullGC的情况,并且也能留出一个安全的空闲空间来容纳那些浮动垃圾。

4.MinorGC和FullGC 

        所有的垃圾回收器触发的新生代垃圾回收都叫做MinorGC;而串行、并行(响应比优先)的老年代垃圾回收器触发的垃圾回收是FullGC,CMS和G1的老年代回收不是FullGC,只有退化为串行或并行的老年代回收器在进行垃圾回收时才叫做FullGC。当回收垃圾的速度赶不上产生垃圾的速度时,此时的老年代回收才是FullGC。

5.垃圾回收调优 

        5.1垃圾回收器选择

        需要高吞吐量的选择ParallelGC,需要低延迟的选择CMS、G1或者ZGC。

        5.2代码角度

        尽量不触发垃圾回收,检查数据加载的是否过多、数据类型是否太臃肿、是否存在内存泄漏,需要缓存数据尽量使用第三方缓存数据实现,减少内存的占用来降低垃圾回收的频率。正所谓最好的垃圾回收调优就是不触发垃圾回收。

        5.3内存调优

                5.3.1新生代调优
                        5.3.1.1调节新生代内存大小                         

        新生代内存过小会导致垃圾回收频繁发生,会提升STW的时间,当新生代内存过大时,就会导致老年代内存过小,容易出现新生代内存充足但老年代内存不足的情况,这时触发垃圾回收就是FullGC了,花费的时间比MinorGC多很多。

        但是应该尽量扩大新生代的内存,因为新生代的垃圾回收使用的是复制算法,当内存太小时垃圾回收频繁,使得有些对象被来回复制,花费大量的时间,而新生代中的绝大部分对象都是朝生夕死的,所以可以通过减少垃圾回收的频次,等这些对象不被使用后再进行复制。新生代内存理想情况下是并发数 * 一次请求响应创建的对象数。

                        5.3.1.2调节幸存区的大小

        幸存区的大小首先应当能够承受那些虽然使用时间不长但是进行回收时仍在使用的对象,还有等待晋升到老年代的对象,如果内存过小会导致使用时间不长的对象因为内存不足提前晋升至老年代,而对老年代的回收很长时间才进行一次,造成这些对象不被使用后仍然长时间占据内存的问题;其次就是晋升阈值的设定,尽量让长时间存活的对象尽快晋升,这样可以减少垃圾回收时的复制时间。            

                 5.3.2老年代调优                       

        先观察程序运行是否会导致FullGC,如果不会就没有必要进行老年代调优,如果出现了FullGC优先对新生代进行调优。对老年代调优就是增大老年代的内存,减少FullGC的发生。

        5.4案例调优分析

                 5.4.1GC发生频繁

        这是由于新生代内存不足,创建的对象过多引发频繁的垃圾回收,可以适当增大新生代内存来减少GC发生的频率。

                5.3.2请求高峰期发生FullGC且时间较长(CMS等并发回收器)

        CMS等并发回收器在并发回收器中重新标记所花费的时间最长,重新标记需要扫描整个堆内存,当请求高峰时创建的对象很多,在扫描新生代时会扫描大量的对象,所以应当在重新标记之前先进行一次垃圾回收来减少要扫描的对象,这就需要打开CMSScavengeBeforeRemark开关(也是垃圾回收的一个相关参数),在重新标记前先进行一次新生代垃圾回收。

                5.3.3当使用JDK1.8之前的版本时出现老年代内存充足但有FullGC发生的情况

        在JDK1.8之前方法区位于永久代中,而永久代位于堆内存中,所以当方法区内存不足时会引发FullGC来清理整个堆内存,而JDK1.8之后方法区位于元空间中,元空间又放在操作系统的内存中,不在JVM的垃圾回收管理内,不会出现这种情况;需要增大永久代的内存空间来避免FullGC的发生。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/463511.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

51单片机-AT24C02(I2C总线)

目录 一,介绍及元件工作原理 7.时序结构(重要) 8.i2C总线数据帧(重要) 二,应用 一,介绍及元件工作原理 1.元件介绍 2.存储器 3.地址总线和数据总线 地址总线只能一次选中一行 4.引脚及应用…

python之自动化(django)

1、安装 我用的是pip install Django 在命令行中安装 然后django-admin startproject autotext(在命令行中) 这句话是创建一个django 项目 然后切换到你所创建项目的目录下 输入: python manage.py runserver 当你出现以下错误时 You…

[沉淀之华] 自研基于SpringBoot Mybaits 构建低代码数据治理脚手架分享:涵盖数据同步、数据比对、数据归档、数据恢复为一体

文章目录 成果演示背景整体能力功能描述相关细节安装使用 成果演示 Github地址:数据治理脚手架 wiki:kg-ctl-core使用文档 背景 为什么要做这个? 一个老生常谈且不得不谈问题:随着业务日益发展,如果不做数据迁移&…

【大模型系列】问答理解定位(Qwen-VL/Llama2/GPT)

文章目录 1 Qwen-VL(2023, Alibaba)1.1 网络结构1.2 模型训练 2 Llama2(2023, Meta)2.1 网络结构2.1.1 MHA/GQA/MQA2.1.2 RoPE(Rotary Position Embedding, 旋转式位置编码)2.1.3 RMSNorm 2.2 推理2.2.1 集束搜索(beam search)2.2.2 RoPE外推 3 GPT系列(OpenAI) 1 Qwen-VL(2023…

深度强化学习(六)(改进价值学习)

深度强化学习(六)(改进价值学习) 一.经验回放 把智能体与环境交互的记录(即经验)储存到 一个数组里,事后反复利用这些经验训练智能体。这个数组被称为经验回放数组(replay buffer)。 具体来说, 把智能体的轨迹划分成 ( s t , a t , r t ,…

软件架构设计 C/S与B/S架构的区别

一、什么是C/S架构? C/S是Client/Server的缩写。服务器通常采用高性能的PC、工作站或小型机,并采用大型数据库系统,如Oracle或SQLServer。 C/S架构软件有一个特点,就是如果用户要使用的话,需要下载一个客户端&#x…

【Linux系列】计算机系统中的架构与发行版:理解与区分

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

m3u8,一个超酷的 Python 库!

更多资料获取 📚 个人网站:ipengtao.com 大家好,今天为大家分享一个超酷的 Python 库 - m3u8。 Github地址:https://github.com/globocom/m3u8 在网络视频传输中,HLS(HTTP Live Streaming)是一…

unity内存优化之AB包篇(微信小游戏)

1.搭建资源服务器使用(HFS软件(https://www.pianshen.com/article/54621708008/)) using System.Collections; using System.Collections.Generic; using UnityEngine;using System;public class Singleton<T> where T : class, new() {private static readonly Lazy<…

Java项目:58 ssm012医院住院管理系统+vue

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 管理员&#xff1b;首页、个人中心、医护人员管理、科室管理、病人管理、病房管理、病人信息管理、病历管理、医嘱管理、手术安排管理、药品信…

(一)、机器人时间同步方案分析

1、是否有必要进行时间同步 目前的自动驾驶系统包括 感知、定位、决策规划、控制 等模块&#xff0c;这些模块的正常运行需要依靠各种不同类型的传感器数据的准确 融合。尤其是激光雷达与相机这两种传感器在感、知定位模块中起着至关重要的作用。机械式旋转扫描激光雷达本身较低…

(二)移植FreeRTOS到STM32中

一、概念 &#xff08;1&#xff09;任务&#xff08;线程&#xff09;&#xff1a;根据功能的不同&#xff0c;将一个系统分割成一个个独立且无法返回的函数&#xff0c;这个函数就被称为任务 &#xff08;2&#xff09;任务栈&#xff1a;静态创建的任务保存在栈中 &#xf…

LeetCode2. 两数相加(Java)

题目&#xff1a; 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这…

USB打印机改网络打印机

解决传统SMB缺陷可跨平台设备使用。 1、安装deepin 如何安装 – 深度科技社区 2、配置IP地址 vi /etc/network/interfaces && systemctl restart networking 3、安装程序上传到服务器并解压。运行0Dinstalld目录下文件 sh 0Dinstalld/0installdd.sh http://XX.XX.XX…

肝了三天,完成了AIGC工具网站大全,建议收藏再看

说是肝了三天&#xff0c;其实远远不止&#xff0c;前前后后&#xff0c;从资料搜集到最后整理成文&#xff0c;有近一个月了&#xff0c;大家看在整理不易的份上&#xff0c;给点个赞吧&#xff0c;不要光顾着收藏呀&#xff01; 国内网站 AIGC 导航 https://www.aigc.cn 网…

visual studio2019项目中引入头文件失效问题的解决

这几天把项目整理一下&#xff0c;但在引入头文件过程中非常曲折。 项目本身写好了可以运行&#xff0c;但是项目结构是这样的&#xff1a; 所以想把功能模块化&#xff0c;同一类协议功能放在一起。 于是建包&#xff0c;创建文件&#xff0c;导入头文件&#xff1a; 在新…

HTML基础:了解CSS的3种创建方法

你好&#xff0c;我是云桃桃。 CSS&#xff0c;即层叠样式表&#xff08;Cascading Style Sheets&#xff09;&#xff0c;是一种用于描述网页样式和布局的标记语言。它通过定义样式规则来控制网页元素的外观和排版&#xff0c;包括文字大小、颜色、边距、背景等&#xff0c;从…

3D Occupancy 预测冠军方案:FB-OCC

文章结尾有视频和连接 背景知识 Occupancy 更像是一个语义分割任务&#xff0c;但是它是 3D 空间的语义分割它的我们对 Occupancy 分自己的期望是它能够具有通用的这种目标建模的能力&#xff0c;才能够不是不受制于这种目标框这种几何的矩形的这种约束而能够建模任意形状的这…

欧科云链:ETH Dencun升级倒计时,哪些数据需要重点关注?

2024年3月13日 21:55&#xff08;epoch 269,568&#xff09;&#xff0c;以太坊将完成坎昆-德内布升级 &#xff08;Dencun 升级&#xff09;&#xff0c;OKLink 专题数据页传送门 &#x1f449; oklink.com/eth/dencun-upgrade 此次升级的主要目标是提升 Layer 2 网络的可扩展…

特殊文本文件、日志技术

特殊文件 为什么要用这些特殊文件&#xff1f; 存储多个用户的&#xff1a;用户名、密码 特殊文件:Properties属性文件 特点&#xff1a; 都只能是键值对键不能重复文件后缀一般是.properties结尾的 作用&#xff1a;存储一些有关系的键值对数据 Properties 是一个Map集合(键…