【C#学习笔记】内存管理

在这里插入图片描述

文章目录

  • 分配内存
  • 释放内存
  • GC
    • 标记清除算法
    • 分代算法
  • .NET的GC机制有这样两个问题:


官方文档

自动内存管理

自动内存管理是CLR在托管执行过程中提供的服务之一。 公共语言运行时的垃圾回收器为应用程序管理内存的分配和释放。 对开发人员而言,这就意味着在开发托管应用程序时不必编写执行内存管理任务的代码。 自动内存管理可解决常见问题,例如,忘记释放对象并导致内存泄漏,或尝试访问已释放对象的内存。

分配内存

总的来说,C#内存是分为四个区块的,分别是

  • 全局数据区:存放全局变量,静态数据,常量
  • 代码区:存放所有程序代码
  • 栈区:存放为运行而分配的局部变量,参数,返回数据,返回地址等
  • 堆区:即自由存储区

在进程中,代码执行的两大区域,是栈和堆。在栈中,代码以栈的形式依次入栈出栈执行,每个线程会维护一个属于自己的线程栈,而栈区的内存地址从上往下增加:
摘自一文读懂C#的 堆、栈、值类型、引用类型
深入理解C#中的堆(Heap)与栈(Stack),一次性全都掌握!
C#垃圾回收机制详解
在这里插入图片描述

相信对于代码在栈中的执行,不少计算机专业的都相当清楚。而在堆中却不是这样,初始化新进程时,运行时会为进程保留一个连续的地址空间区域。 这个保留的地址空间被称为托管堆。

托管堆是专门用于C#的自动内存管理的,它由CLR进行管理。

托管堆维护着一个指针,用它指向将在堆中分配的下一个对象的地址。最初,该指针设置为指向托管堆的基址。与栈不同,堆是从下往上的,因此所有自由空间都在已用空间的上面。而与栈不同的最大特点就是,我们往堆中存取时是自由存取的,可以以任意顺序进行存取和移除。
在这里插入图片描述
(乱七八糟的堆)

记得我在之前曾经讲过,C#的变量类型存在两种,第一种值类型,它是直接存储在栈上的,且使用值类型赋值,则需要完全复制一个副本存在堆栈上。而我们的操作都是在栈上实现的,因为栈上的操作速度快,内存分配和释放简单。

而引用类型则方便许多,一方面,引用类型的本体是存储在堆中的,在堆中的存取更加自由。另一方面,我们会往栈中内存入栈存储了堆地址变量。当我们想要访问引用类型时,只需通过栈中的地址就可以找到它。但是却不能直接访问堆,因为数据存储太随意,如果没有栈中的指针,我们根本不知道它在堆中的什么位置。

在这里插入图片描述
但是使用堆引发一个问题:在栈中,由于程序执行完毕,各种入栈的变量会自动释放。但是堆首先存取随意,如果程序员忘了释放用完的变量,就会浪费内存。其次堆中的变量就算释放了,那么被释放的变量对应内存就空在那里了。如果空着的连续内存太小我们根本用不了,就会产生内存碎片。因此C#使用托管堆,由GC机制帮助我们自动管理这个堆。

应用程序创建第一个引用类型时,将为托管堆的基址中的类型分配内存。
应用程序创建下一个对象时,垃圾回收器在紧接第一个对象后面的地址空间内为它分配内存。
只要地址空间可用,垃圾回收器就会继续以这种方式为新对象分配空间。

从托管堆中分配内存要比非托管内存分配速度快。 由于运行时通过为指针添加值来为对象分配内存,所以这几乎和从堆栈中分配内存一样快。
另外,由于连续分配的新对象在托管堆中是连续存储,所以应用程序可以快速访问这些对象。

总结:栈主要用于存储局部变量和方法调用信息,它的操作速度较快;而堆主要用于存储动态分配的对象,它的操作速度较慢但能够支持更长的生命周期和对象共享。对于数据较小、生命周期短暂的变量,可以考虑使用栈;对于较大的对象或需要长时间存活的对象,需要使用堆。


释放内存

让我们了解一下GC机制是怎么释放内存的。

.NET 的垃圾回收器管理应用程序的内存分配和释放。 每当有对象新建时,公共语言运行时都会从托管堆为对象分配内存。 只要托管堆中有地址空间,运行时就会继续为新对象分配空间。 不过,内存并不是无限的。 垃圾回收器最终必须执行垃圾回收来释放一些内存。 垃圾回收器的优化引擎会根据所执行的分配来确定执行回收的最佳时机。 执行回收时,垃圾回收器会在托管堆中检查应用程序不再使用的对象,然后执行必要的操作来回收其内存。

当满足以下条件之一时将发生垃圾回收:

  • 系统具有低的物理内存。 内存大小是通过操作系统的内存不足通知或主机指示的内存不足检测出来的。

  • 由托管堆上已分配的对象使用的内存超出了可接受的阈值。 随着进程的运行,此阈值会不断地进行调整。

  • 调用 GC.Collect 方法。 几乎在所有情况下,你都不必调用此方法,因为垃圾回收器会持续运行。 此方法主要用于特殊情况和测试。

GC

总体来看C#的GC和Lua的GC其实差不多

引用跟踪:GC的第一步是通过引用跟踪,确定哪些对象仍然被活动的引用所引用。GC会从栈上的根对象开始,根对象包括方法中的局部变量和静态变量。然后,从根对象开始,跟踪引用链,找出所有的可达对象。

根对象标记:在引用跟踪的过程中,GC会将所有可达的对象标记为活动对象。这些活动对象将被保留下来,不会被清理。

标记清除算法

对象标记:GC接下来会遍历堆中的所有对象,从根对象可达的对象开始,将它们标记为活动对象。GC使用一种叫做“标记-清除(mark and sweep)”的算法来标记对象。首先所有的堆中的对象全部默认为垃圾,接着通过遍历栈找到引用的对象,给这些被引用的对象打上标记。最后将所有的垃圾内存释放(或者标记为空闲状态)。

压缩阶段:当清理完垃圾之后,堆中的内存会变得零零散散的,为了避免内存碎片,接着就会把所有存活的对象移动到堆的底部。

在这里插入图片描述

分代算法

分代算法的原理基于统计学,简单来说活动时间越长的对象使用率越高,则死亡率越低,越不需要进行清理。

分代算法的假设前提条件:
1、大量新创建的对象生命周期都比较短,而较老的对象生命周期会更长
2、对部分内存进行回收比基于全部内存的回收操作要快
3、新创建的对象之间关联程度通常较强。heap分配的对象是连续的,关联度较强有利于提高CPU cache的命中率

.NET将heap分成3个代龄区域: Gen 0、Gen 1、Gen 2
在这里插入图片描述
每次迭代时使用标记清除算法去除垃圾。而堆将被分为3个代龄区域,相应的GC有3种方式: # Gen 0 collections, # Gen 1 collections, #Gen 2 collections

当Gen0区域内存达到阈值将触发# Gen 0 collections后,存活的对象将进入Gen1区域;

当Gen1区域内存达到阈值将触发 # Gen 1 collections,将Gen0存活对象放入Gen1,Gen1存活对象放入Gen2。

当Gen2区域内存达到阈值将触发 # Gen 2 collections,将Gen0存活对象放入Gen1,Gen1存活对象放入Gen2,Gen2存活对象位置不变。

Gen 0和Gen 1比较小,这两个代龄加起来总是保持在16M左右;Gen2的大小由应用程序确定,可能达到几G,因此0代和1代GC的成本非常低,2代GC称为fullGC,通常成本很高。粗略的计算0代和1代GC应当能在几毫秒到几十毫秒之间完成,Gen 2 heap比较大时fullGC可能需要花费几秒时间。大致上来讲.NET应用运行期间2代、1代和0代GC的频率应当大致为1:10:100。

和Lua一样,当对象被GC清理的时候,会调用一个终结器函数。

.NET的GC机制有这样两个问题:

首先,GC并不是能释放所有的资源。它不能自动释放非托管资源。

第二,GC并不是实时性的,这将会造成系统性能上的瓶颈和不确定性。

GC并不是实时性的,这会造成系统性能上的瓶颈和不确定性。所以有了IDisposable接口,IDisposable接口定义了Dispose方法,这个方法用来供程序员显式调用以释放非托管资源。使用using 语句可以简化资源管理。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二次开发了个寂寞之HttpRunnerManager接口测试管理平台

文章目录 一、背景1、二次开发1.1、首页1.2、项目列表1.3、用例列表1.4、新增用例1.5、测试套件1.6、查看报告 二、总结 一、背景 自入职起,就在公司内部引入开源接口测试平台,选一个大家勉强看得懂源码的开源项目,方便后续的二次开发&#x…

【每日一题】—— C. Mocha and Hiking(Codeforces Round 738 (Div. 2))

🌏博客主页:PH_modest的博客主页 🚩当前专栏:每日一题 💌其他专栏: 🔴 每日反刍 🟡 C跬步积累 🟢 C语言跬步积累 🌈座右铭:广积粮,缓称…

Kotlin~Visitor访问者模式

概念 将数据结构和操作分离,使操作集合可以独立于数据结构变化。 角色介绍 Visitor:抽象访问者,为对象结构每个具体元素类声明一个访问操作。Element:抽象元素,定义一个accept方法ConcreteElement:具体元…

docker 资源限制

目录 1、CPU使用率 2、CPU共享比例 3、CPU周期限制 4、CPU核心限制 5、CPU 配额控制参数的混合案例 6、内存限制 7、Block IO 的限制 8、限制bps 和iops docker资源限制 Docker容器技术底层是通过Cgroup(Control Group 控制组)实现容器对物理资…

Android 性能调优之bitmap的优化

背景 Android开发中,加载图片过多、过大很容易引起OutOfMemoryError异常,即我们常见的内存溢出。因为Android对单个应用施加内存限制,默认分配的内存只有几M(具体视不同系统而定)。而载入的图片如果是JPG之类的压缩格…

【unity】Pico VR 开发笔记(视角移动)

【unity】Pico VR 开发笔记(视角移动) 视角移动是简单的基础功能,这里区别于头显定位获得的小范围位移,是长距离不影响安全边界的位移方式。的常见的位移方式有两种,其一是触发后瞬间传送到指定位置,其次是…

IDEA用Gradle构建项目时,lombok插件无效的解决办法

Lombok 可用来帮助开发人员消除 Java 的重复代码,尤其是对于简单的 Java 对象(POJO),比如说getter/setter/toString等方法的编写。它通过注解实现这一目的。 正确使用姿势 一、安装Lombok插件 菜单栏File -> Settings ->…

死锁的发生原因和怎么避免

项目场景: 提示:这里简述项目相关背景: 例如:项目场景:示例:通过蓝牙芯片(HC-05)与手机 APP 通信,每隔 5s 传输一批传感器数据(不是很大) 问题描述 死锁,简单来说就是两个或者两个以上的线程在…

翻转卡片游戏(力扣)

题目 在桌子上有 n 张卡片,每张卡片的正面和背面都写着一个正数(正面与背面上的数有可能不一样)。 我们可以先翻转任意张卡片,然后选择其中一张卡片。 如果选中的那张卡片背面的数字 x 与任意一张卡片的正面的数字都不同&#…

【C语言进阶】数据的存储----整型篇

​ 🍁 博客主页:江池俊的博客 💫收录专栏:C语言——探索高效编程的基石 💻 其他专栏:数据结构探索 ​💡代码仓库:江池俊的代码仓库 🎪 社区:GeekHub 🍁 如果觉…

Liunx环境下git的详细使用(gitee版)

Liunx环境下git的详细使用(gitee版) 1.git是什么2.git操作2.1在gitee创建一个仓库2.2.gitignore2.3.git 3.git三板斧3.1add3.2 commit3.3push 4.git其他命令4.1查看当前仓库状态4.2查看提交日志4.3修改git里面文件名称4.4删除文件4.5修改远端仓库内容 1.…

postgresql表膨胀处理之pgcompacttable部署及使用

环境: 1)redhat-release:CentOS Linux release 7.6.1810 (Core) 2)database version:postgresql 14.6 一、添加pgstattuple pgcompacttable工具使用过程中需要依赖pgstattuple,因此需先添加pgstattuple…

【SEO基础】百度权重是什么意思及网站关键词应该怎么选?

百度权重是什么意思及网站关键词应该怎么选? 正文共:3253字 20图 预计阅读时间:9分钟 ​ 1.什么是网站权重? 这段时间和一些朋友聊到网站权重以及关键词,发现蛮多人对于这两个概念的认知还是存在一些错误的&#xf…

数组的使用(逆序、冒泡)

内存连续数据类型相同从0开始索引 找出数组中的最大值 #include <iostream> #include <stdlib.h> //随机数所在文件 using namespace std;int main() {int arr[5]{104,134,145,129,89};//初始化没有填的为0 int max0;for(int i0;i<5;i){if(arr[i]>max){ma…

203. 移除链表元素

203. 移除链表元素 题目方法1递归方法2迭代 题目 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 方法1递归 class Solution { public:ListNode* removeElements(ListNode* head, in…

RabbitMQ快速入门

文章目录 1、RabbitMQ的概述1.1、什么是消息队列&#xff1f;1.2、为什么要使用消息队列&#xff1f;1.3、RabbitMQ的特点&#xff1a; 2、RabbitMQ的安装2.1 下载与安装2.2 常用命令 3、RabbitMQ消息发送和接受3.1 消息发送和接受机制3.2 AMQP的消息路由3.3 Exchange(交换机)的…

Datax 数据同步-使用总结(一)

1&#xff0c;实时同步&#xff1f; datax 通常做离线数据同步使用。 目前能想到的方案 利用 linux 的定时任务时间戳的方式做增量同步。 2&#xff0c;同步速度快不快&#xff1f; 单表同步速度还是挺快的 但是如果遇到复杂的 sql 查询&#xff0c;其同步效率&#xff0c…

中国信通院发布《高质量数字化转型产品及服务全景图(2023)》

2023年7月27日&#xff0c;由中国信息通信研究院主办的2023数字生态发展大会暨中国信通院铸基计划年中会议在北京成功召开。 本次大会发布了中国信通院《高质量数字化转型产品及服务全景图&#xff08;2023&#xff09;》&#xff0c;中新赛克海睿思受邀出席本次大会并成功入选…

viewerjs 如何新增下载图片功能(npm包补丁)

文章目录 先实现正常的效果实现下载图片改变viewerjs的build函数源码改变之后&#xff0c;执行npm i 之后node_modules源码又变回了原样 1、viwerjs所有功能都很完善&#xff0c;但唯独缺少了图片的下载 2、需求&#xff1a;在用viwerjs旋转图片后&#xff0c;可以直接下载旋转…

Dockerfile构建Tomcat镜像

准备apache包和jdk并解压 [rootlocalhost tomcat]# ll 总用量 196728 -rw-r--r--. 1 root root 9690027 7月 17 2020 apache-tomcat-8.5.40.tar.gz -rw-r--r--. 1 root root 674 8月 2 20:19 Dockerfile -rw-r--r--. 1 root root 191753373 7月 17 2020 jdk-8u191-…