菜鸟之旅——.NET垃圾回收机制

菜鸟之旅——.NET垃圾回收机制

  .NET的垃圾回收机制是一个非常强大的功能,尽管我们很少主动使用,但它一直在默默的在后台运行,我们仍需要意识到它的存在,了解它,做出更高效的.NET应用程序;下面我分享一下我对于垃圾回收机制(GC)的学习心得。

GC的必要性

  我们知道程序会需要向内存堆使用new请求内存,然后将请求的内存初始化并使用,使用完毕之后,变清理资源和释放内存,等待别的程序来请求使用;对内存资源的管理方式,现在存在这么几种管理方式:

  1、手动管理:C、C++

  2、计数管理:COM

  3、自动管理:.NET、JAVA、PHP

  现在的高级语言基本上都实现了自动管理内存,这是因为手动管理内存会因为人为的原因产生以下问题:

  1、开发人员忘记释放请求的内存,造成内存泄漏,若是内存泄露过多,则可能会造成内存溢出,导致程序无法运行;

  2、应用程序访问已释放的内存,造成数据读取错误。

  由此可见,手动去管理堆里面的内存可靠程度,会因开发人员的不同而不同,在C++因指针而出现的问题可不少;而且易出现Bug等乱七八糟的问题,影响系统稳定性,所以自动化管理内存是必要的。

GC的工作原理

 通用概念

  回收时机

  当应用程序分配新的对象,GC的代的预算大小已经达到阈值,比如GC的第0代已满;

  代码主动显式调用System.GC.Collect();

  其他特殊情况,比如,windows报告内存不足、CLR卸载AppDomain、CLR关闭,甚至某些极端情况下系统参数设置改变也可能导致GC回收。

  应用程序根

  应用程序根(application root):根(root)就是一个存储位置其中保存着对托管堆上一个对象的引用,根可以属性下面任何一个类别

  • 全局对象和静态对象的引用
  • 应用程序代码库中局部对象的引用
  • 传递进一个方法的对象参数的引用
  • 等待被终结(finalize,后面介绍)对象的引用
  • 任何引用对象的CPU寄存器

  代

  垃圾回收器将托管堆(heap)里面的对象划分为3个代(一般为3代),可以使用GC.MaxGeneration()方法来进行查询当前系统所支持的最大代数:

  1、G0 小对象(Size<85000Byte):新分配的小于85000字节的对象

  2、G1:在GC中幸存下来的G0对象

  3、G2:大对象(Size>=85000Byte);在GC中幸存下来的G1对象

  当一个对象被new的时候,它的代为0,经过一次回收之后,若该对象没有被回收,则代上升,变为1,若每次回收都幸存下来,则代都会上升,最大代为操作系统所支持的最大代。

  因为将对象以代划分,并且可以单独回收某一个世代,避免回收整个托管堆,提升性能。一个基于代的垃圾回收器有一下特点:

  1、对象越新,生存期越短;

  2、对象越老,生存期越长;

  3、回收堆的一部分,速度快于回收整个堆。

 工作过程

  标记对象

  在垃圾回收的第一步就是标记对象:垃圾回收器会认为托管堆中的所有对象都是垃圾,然后垃圾回收器会去检查所有的应用程序根,遍历每个根所引用到的对象,将其标记为活动的(live ),所有的根对象都检查完之后,有标记的对象就是可达对象,未标记的对象就是不可达对象,不可达对象就是回收的目标。

  弱引用对象则不在考虑范围之内,所以一定会被回收掉的。

  销毁对象,释放内存

  在经过第一步的对象筛选之后,回收没有被引用的对象,就是不可达对象,GC调用对象默认的终结器Finalize(),销毁对象之后,将内存也释放掉。

  同时,还存在引用的对象,就是可达对象的世代变为下一个世代。

  压缩堆内存

  经过第二步的销毁对象和释放内存之后,幸存下来的对象在堆中的排列可能是不连续的,这时在堆中存在非常多的内存碎片,程序在new对象的时候都是请求一段连续的内存,则内存碎片可能就无法再次利用(虽然没有被使用),造成内存资源的浪费,所以垃圾回收的最后一步就是压缩内存:将垃圾回收后幸存的对象移动到一起,并且将各个对象的引用更新到对象新的位置上,保证对象引用的正确性。

  注:从这里看得出,在压缩堆内存的时候,所有相关线程必须暂停,因为压缩时不能保证对象引用的正确性,所以在垃圾回收的时候,GC会劫持所有相关线程,在回收完毕之后,被劫持的线程才会正常工作,所以垃圾回收势必会影响一定的性能,所以慎用System.GC.Collect()。

 Finalize()与Dispose()

  上面说到,GC在回收对象的时候是调用对象的终结器Finalize()来实现的,那么,就简单的总结一下Finalize()与Dispose()吧:

  1、调用者:

    Finalize只能由GC调用

    Dispose由开发人员显示调用,也可以使用use区块,在程序离开区块使自动调用Dispose方法

  2、调用时机:

    Finalize由于是GC调用的,所以调用时机是垃圾回收的时候调用,时机不确定

    Dispose由于是显示调用,所以调用时机是确定的,在调用方法的时候就调用了

  3、目的:

    这里的目的主要说是Dispose出现的目的;

    首先是.NET存在托管资源和非托管资源,一般来说,非托管资源数量有限,比较珍贵,在使用完毕之后,希望能够释放掉,那么将释放非托管资源的方法写到终结器Finalize里面也是可以的,但是由于Finalize的调用时机不确定,导致释放资源不及时,那么有限的非托管资源很快就被占用完毕,所以,为了能够及时的释放掉这类资源,我们需要能够显示调用的方法,这就是Dispose。

    Finalize主要是为了GC释放托管资源和销毁对象,释放内存

    Dispose主要是为了释放托管和非托管资源和销毁对象,释放内存

  注:不必担心资源的重复释放问题,就算是重复释放,.NET也做好了相应措施来处理,不会抛出异常。

    下面贴一个MSDN推荐的标准的Dispose实现方式

复制代码
    class Class : IDisposable
    {
        // 标识:是否释放托管资源
        private bool disposed = false;

        // 显示调用的方法
        public void Dispose()
        {
            Dispose(true);
            // 将对象从垃圾回收器链表中移除,
            // 从而在垃圾回收器工作时,只释放托管资源,而不执行此对象的析构函数
            GC.SuppressFinalize(this);
        }

        // 受保护的释放资源方法
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    // 此处写释放托管资源的方法
                }
                disposed = true;

                // 此处写释放非托管资源的方法
            }
        }
        ~Class()
        {
            // 这里是防止忘记显示调用Dispose(),在GC进行垃圾回收的时候进行释放非托管资源
            Dispose(false);
        }
    }
复制代码

 

总结

  GC所带来的便利是不言而喻的,但是这是付出一定的系统性能来实现的:在垃圾回收的时候GC会劫持所有相关的线程,并且会有一定的时空开销,所以在平时开发过程中注意一些良好的开发习惯可能会对GC有一些积极的影响。

  1、尽量不要new很大的对象,大对象(>=85000Byte)直接归为G2代,GC回收算法从来不对大对象堆(LOH)进行内存压缩整理,移动大对象将会消耗更多的CPU时间,也更容易造成内存碎片。这里也可以将大对象或者生命周期长的对象进行池化。

  2、不要频繁的new生命周期短的小对象,这可能会导致频繁的垃圾回收,这里可以考虑使用结构体放在栈中来代替,或者也可以使用对象池化来优化。

  3、不推荐使用对象池化的解决方案,它比较笨重和容易出错,设计一个高性能稳定的对象池并不容易。

  4、降低对象之间的纵向深度,GC在回收过程中,会先顺着根来进行对象遍历和标记,减少深度可以加快遍历速度;若系统中各个类之间的关系错综复杂,那么考虑一下设计方案是否合理。

  当然注意的地方还有不少,最后贴一篇博客,这里介绍了如何编写高性能的.NET代码,其中的GC介绍非常详细:

  [翻译]【目录】编写高性能 .NET 代码

   

转载于:https://www.cnblogs.com/cjm123/p/8564360.html

C++智能指针深入解析,搞不懂这几点别说你会C++ / 自定义删除器——用于管理非 new 分配的资源if (fp) {fclose(fp);// 使用自定义删除器// 或者使用函数指针// 注意:自定义删除器会使 unique_ptr 体积增大// sizeof(unique_ptr<T>) == sizeof(T*) // 无自定义删除器// 场景一:管理 OpenGL 资源if (id) {delete id;// 场景一:管理 OpenGL 资源 struct GLDeleter {if(id) { 阅读详情

相关推荐

深入解析 ReDoc:从零构建高性能 API 文档的终极指南

ReDoc 是一个强大的开源工具,用于将 OpenAPI 规范转换为高质量、响应式的 API 文档。本文详细介绍了如何使用 ReDoc 构建和自定义 API 文档,包括如何编写支持 OpenAPI 的 API 注解、配置 HTML 页面以加载文档,以及如何利用 ReDoc 的高级功能进行定制。通过实例代码,您将学会如何灵活地展示 API 文档,提升用户体验。无论是初学者还是经验丰富的开发者,都能从本文中获得实用的技巧与方案,帮助快速搭建符合需求的 API 文档界面。

楠搏万的博客 4523

【C语言】初识 C 语言(点到为止)

C 语言概览

BaconZzz的博客 741

CAN-do-paser:Java开发的CAN总线数据帧解析库

CAN总线数据帧使用DBC文件信息解析为物理量,基于Java开发的数据解析工具。

佳瑞的博客 1104

C++ STL set 与 map 全套详解:关联式容器、红黑树底层、代码坑点、刷题实战

本文系统讲解STL中set/multiset/map/multimap的接口、底层红黑树原理及高频坑点,对比有序与无序容器性能差异,结合力扣例题剖析实际应用,涵盖迭代器特性、插入删除机制、区间操作与面试重点,全面掌握关联式容器核心知识。

xxwxx__的博客 297

C++三方组件】toml++:人性化的配置文件格式

配置文件写 JSON 没注释、写 YAML 踩缩进陷阱、写 INI 没类型——TOML 就是冲这些痛点设计的「给人写的配置格式」。toml++ 是它的单头 C++ 实现:解析、访问、序列化、报错一应俱全。How 实测完整回路:at_path 路径访问、数组表遍历、value_or 默认值、toml_formatter 写出、parse_error 报错。Why 拆三问:TOML 凭什么更适合人写、动态配置与 schema 序列化是两种什么哲学、单引号字符串背后的「写出来就能读回去」原则。

码工许师傅 370

C++三方组件】cxxopts:轻量首选的命令行解析

不是每个工具都需要子命令和校验器——更多时候要的只是「十行声明、一次解析、拿到值」。cxxopts 用 60KB 单头给出这一档:add_options() 一张表声明,parse 后按字符串 key 取值。How 实测六组:默认值与类型转换、隐式值布尔开关、逗号分拆、分组帮助文本(–help 不内建)、异常家族真实报文、allow_unrecognised 透传。Why 拆四问:砍掉子命令换来什么、字符串 key 代价怎么管、隐式值从哪来、help 为何不内建。与 CLI11 繁简分界一表看清。

码工许师傅 563

C++之类和对象(上)02

本文介绍了C++中this指针的作用与使用限制,指出其由编译器隐式传递,用于访问当前对象的成员变量。同时讲解了类的默认成员函数,重点阐述构造函数的定义、作用及特点:函数名与类同名、无返回值、对象创建时自动调用、可重载,且用户未定义时编译器自动生成默认构造函数。默认构造函数包括无参、全缺省及编译器生成的三种形式,三者不可共存。对于自定义类型成员,会调用其默认构造函数初始化,内置类型则不保证初始化。

2401_88805889的博客 61

C++笔记-文件IO-<fcntl.h>

fcntl.h>open打开,readwrite读写,fsync落盘,close关闭。要落盘、要网络 fd、要非阻塞 → 用它;日常读文本 → 用<fstream>。

weilx1234的博客 47

de风——【从零开始学C++】(二十)红黑树封装map和set

本文从零实现C++中set与map的封装,核心在于利用泛型与红黑树共用底层结构。通过KeyOfT仿函数统一比较逻辑,确保仅按key排序;借助const修饰使key不可修改,保障数据结构有序性;结合迭代器实现范围遍历,并通过Insert返回pair<iterator, bool>支持map的operator[]功能。最终完成mymap与myset的完整封装,揭示其本质为“红黑树+泛型外壳”,帮助新手彻底理解容器底层机制。

Xiao_running的博客 667

Linux:UDP Socket API 的 C++ 工程化实践

这一部分我们先不讲太复杂的并发,也不讲各种封装技巧,只把 UDP 服务端最基础的初始化过程和接收过程讲清楚。在开始看代码之前,先想清楚一个问题:网络通信到底在找谁?在互联网里,要找到一台主机,靠的是 IP 地址;要找到这台主机上的某一个进程,靠的是端口号。IP 地址确定互联网中唯一一台主机,端口号确定这台主机里的唯一一个进程。服务端要想被客户端找到,就必须把自己的 IP 和端口固定下来。比如服务端启动时写成:./udp_server 0.0.0.0 8888。

我的个人gitee仓库:https://gitee.com/chen-yukun-1030/linux_-study.git 228

C语言基础知识点汇总

#include 就是一条预处理命令, 它的作用是通知C语言编译系统在对C程序进行正式编译之前需做一些预处理工作。

ChenC000的博客 104

鱼香ros2(十)C++面向对象编程

在构造函数中,传入了诫命名称、姓名和年龄作为参数,这里的参数从传递采用都是静态引用方式,在std::string后添加&表示传递引用,引用与指针类似,传递引用避免了不必要的数据复制,可以提高代码效率。std::string前面的const限制变量为只读,即不能修改,这样可以避免它在方法内被意外修改,提高代码的安全性。在构造函数后面添加:Node(node_name)用于调用父类的构造函数,传递节点名称参数,这一点和python一致,但语法不同。this指向自己的指针。二、修改CMakeLists.txt。

qq_37163702的博客 159

线程与定时器:周期性任务实现方式的差异与选型(C++ 版)

本文系统对比线程与定时器实现周期性任务的差异。线程方案依赖 sleep 循环,存在精度差、资源开销大、管理复杂等问题;而定时器(如 POSIX 定时器、select/poll 超时机制)可实现高精度、低开销的周期调度。通过最小堆结合 select 可单线程统一管理多个定时器,支持“I/O + 定时”混合调度。最终推荐在高性能场景下使用定时器或协程,避免多线程带来的负担。

lzg_na的博客 255

C++11】初始化列表 && 右值引用与移动语义

C++11初始化列表以及右值引用与移动语义,其中还涉及引用折叠、完美转发等内容

2402_89719293的博客 294

部署yolov8-pose到orangepi4pro实现跌倒检测

上面的2100首先就不是16字节对齐的,就算对其强制做了硬件Padding(Stride 变成 2112),那么得到的数据就是带有空洞的,下游算子拿到带有空洞的数据时,根本无法做跨通道的连续 Slice(切片)和 Concat(拼接)如果驱动在每个算子之间自动插入“内存解包(Unpack)与重排(Repack)”的代码,会吃掉大量的内部 SRAM 带宽,性能会暴跌数倍。输出的scale和zero_point在后续c++进行处理的时候很重要,能帮助我们完美的还原出原始的浮点数数据,然后进行后处理。

qq_40131760的博客 160

C++学习<1>程序分区

本文介绍了C++的四大程序分区,并对每个区进行解释。

2303_77568009的博客 66

03 nav2_common——全栈共用工具箱

宏统一了 30+ 个包的编译规矩(C++17 + Release + 零警告容忍);四个 Python 工具专门解决"配置文件在运行时怎么灵活传递"的问题——特别是多机器人场景下,让同一份参数文件能适配不同命名空间的机器人。

weixin_43907136的博客 103

C++篇:类和对象(下)

本文深入探讨C++类与对象的核心概念,涵盖初始化列表的必要性、初始化顺序由声明顺序决定、explicit关键字防止隐式类型转换、static成员变量与函数的共享特性及使用场景、友元函数与类突破封装的机制及其单向性与不可传递性,以及内部类作为外部类友元的特性。文章强调封装的重要性,指出友元虽解决特殊访问需求但应慎用,最终总结各知识点的内在关联,助力理解面向对象编程的深层逻辑。

久谦的博客 190

C++语言学习

C++作为“底层之王”,凭借零开销抽象与内存完全掌控,支撑游戏引擎、金融交易、浏览器及嵌入式系统等高性能领域。其标准库提供STL、智能指针、并发支持等核心组件,开发中推荐使用现代C++风格。通过栈(自动管理)与堆(手动/智能指针管理)的合理运用,结合类型转换、指针与结构体等机制,实现高效、安全的系统级编程。

heni6560的博客 340
上一篇: .NET实现发送邮件
下一篇: sqlsever 科学计数法 转标准值
anmei1912
博客等级 码龄10年 54粉丝 182原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值