.Net 垃圾回收机制原理(二)

英文原文:Jeffrey Richter

编译:赵玉开

链接http://www.php.cn/

上一篇文章介绍了.Net 垃圾回收的基本原理和垃圾回收执行Finalize方法的内部机制;这一篇我们看下弱引用对象,代,多线程垃圾回收,大对象处理以及和垃圾回收相关的性能计数器。
让我们从弱引用对象说起,弱引用对象可以减轻大对象带来的内存压力。
弱引用(Weak References)
当程序的根对象指向一个对象时,这个对象是可达的,垃圾回收器不能回收它,这称为对对象的强引用。和强引用相对的是弱引用,当一个对象上存在弱引用时,垃圾回收器可以回收此对象,但是也允许程序访问这个对象。这是怎么回事儿呢?请往下看。

如果一个对象上仅存在弱引用,并且垃圾回收器在运行,这个对象就会被回收,之后如果程序中要访问这个对象,访问就会失败。另一方面,要使用弱引用的对象,程序必须先对这个对象进行强引用,如果程序在垃圾回收器回收这个对象之前对对象进行了强引用,这样(有了强引用之后)垃圾回收器就不能回收此对象了。这有点绕,让我们用一段代码来说明一下:

void Method() {//创建对象的强引用Object o = new Object(); // 用一个短弱引用对象弱引用o.WeakReference wr = new WeakReference(o);o = null; // 移除对象的强引用o = wr.Target; //尝试从弱引用对象中获得对象的强引用if (o == null) {// 如果对象为空说明对象已经被垃圾回收器回收掉了} else {// 如果垃圾回收器还没有回收此对象就可以继续使用对象了}}

为什么需要弱对象呢?因为,有一些数据创建起来很容易,但是却需要很多内存。例如:你有一个程序,这个程序需要访问用户硬盘上的所有文件夹和文件名;你可以在程序第一次需要这个数据时访问用户磁盘生成一次数据,数据生成之后你就可以访问内存中的数据来得到用户文件数据,而不是每次都去读磁盘获得数据,这样做可以提升程序的性能。

问题是这个数据可能相当大,需要相当大的内存。如果用户去操作程序的另外一部分功能了,这块相当大的内存就没有占用的必要了。你可以通过代码删除这些数据,但是如果用户马上切换到需要这块数据的功能上,你就必须重新从用户的磁盘上构建这个数据。弱引用为这种场景提供了一种简单有效的方案。
当用户切换到其他功能时,你可以为这个数据创建一个弱引用对象,并把对这个数据的强引用解除掉。这样如果程序占用的内存很低,垃圾回收操作就不会触发,弱引用对象就不会被回收掉;这样当程序需要使用这块数据时就可以通过一个强引用来获得数据,如果成功得到了对象引用,程序就没有必要再次读取用户的磁盘了。

WeakReference类型提供了两个构造函数:

WeakReference(object target);WeakReference(object target, bool trackResurrection);

target参数显然就是弱引用要跟踪的对象了。trackResurrection参数表示当对象的Finalize方法执行之后是否还要跟踪这个对象。默认这个参数是false。有关对象的复活请参考这里。

方便起见,不跟踪复活对象的弱引用称为“短弱引用”;而要跟踪复活对象的的弱引用称为“长弱引用”。如果对象没有实现Finalize方法,那么长弱引用和短弱引用是完全一样的。强烈建议你尽量避免使用长弱引用。长弱引用允许你使用复活的对象,而复活对象的行为可能是不可以预知的。
一旦你使用WeakReference引用了一个对象,建议你将这个对象的所有强用都设置为null;如果强引用存在的话,垃圾回收器是永远都不可能回收弱引用指向的对象的。
当你要使用弱引用目标对象时,你必须为目标对象创建一个强引用,这很简单,只要用object a = weekRefer.Target;就可以了,然后你必须判断a是否为空,弱不为空才可以继续使用,弱为空就表示对象已经被垃圾回收器回收了,得通过其他方法重新获得此对象。
弱引用的内部实现
从前文中的描述中我们可以推断出弱引用对象肯定和一般对象的处理是不一样的。一般情况下如果一个对象引用了另一个对象就是强引用,垃圾回收器就不能回收被引用的对象,而WeakReference对象却不是这样子,它引用的对象是有可能被回收的。
要完全理解弱对象是如何工作的,我们还需要看一下托管堆。托管堆上有两个内部数据结构他们的唯一作用是管理弱引用:我们可以把它们称作长弱引用表和短弱引用表;这两个表存放托管堆上的弱引用目标对象指针。
程序运行之初,这两个表都是空的。当你创建一个WeakReference对象时,这个对象并不是分配到托管堆上的,而是在弱对象表中创建一个空槽(Empty Slot)。短弱引用对象被放在短弱对象表中,长弱引用对象被放在长弱引用表中。
一旦发现空槽,空槽的值会被设置成弱引用目标对象的地址;显然长短弱对象表中的对象是不会当作应用程序的根对象的。垃圾回收器不会回收长短弱对象表中的数据。
让我们来看下垃圾回收执行时发生了什么:
1. 垃圾回收器构建一个可达对象图,构建步骤请参考上文
2. 垃圾回收器扫描短弱对象表,如果弱对象表中指向的对像没有在可达对象图中,那么这个对像就被标识为垃圾对象,然后短对象表中的对象指针被设置为空
3. 垃圾回收器扫描终结队列(参考上文),如果队列中的对象不在可达对象图中,这个对象从终结队列中移动到Freachable队列中,这时候,这个对象又被标识为可达对象,不再是垃圾了
4. 垃圾回收器扫描长弱引用表。如果表中的对象不在可达对象图中(可达对象图中包括在Freachable队列中对象),将长引用对象表中对应的对象指针设置为null
5. 垃圾回收器移动可达对象
一旦你理解了垃圾回收器的工作过程,就很容易理解弱引用是如何起作用了。访问WeakReference的Target属性导致系统返回弱对象表中的目标对象指针,如果是null,表示对象已经被回收了。
短弱引用不跟踪复活,这意味着垃圾回收器可以在扫描终结队列之前检查弱引用表中指向的对象是否是垃圾对象。
而长弱引用跟踪复活对象,这意味着垃圾回收器必须在确认对象回收之后才可以将弱引用表中的指针设置为null。
代:
提起.Net的垃圾回收,c++或者c程序员可能就会想,这么管理内存会不会出现性能问题呢。GC的开发人员一直在调整垃圾回收器提升它的性能。代就是一种为了降低垃圾回收对性能影响的机制。垃圾回收器在工作时会假定如下说法是成立的:
1. 一个对象越新,那么这个对象的生命周期就越短
2. 一个对象越老,那么这个对象的生命周期就越长
3. 新对象之间通常更可能和新对象之间存在引用关系
4. 压缩堆的一部分要比压缩整个堆要快
当然大量研究证明以上几个假设在很多程序上是成立的。那就让我们来谈谈这几个假设是如何影响垃圾回收器工作的吧。
在程序初始化时,托管堆上没有对象。这时候新添到托管堆上的对象是的代是0.如下图所示,0代对象是最年轻的对象,他们从来没有经过垃圾回收器的检查。

.Net 垃圾回收机制原理(二)

图1 托管堆上的0代对象

现在如果堆上添加了更多的对象,堆填满时就会触发垃圾回收。当垃圾回收器分析托管堆时,会构建一个垃圾对象(图2中浅紫色块)和非垃圾对象的图。所有没有被回收的对象会被移动压缩到堆的最底端。这些没有被回收掉的对象就成为了1代对象,如图2所示

.Net 垃圾回收机制原理(二)

图2 托管堆上的0代1代对象

当堆上分配了更多的对象时,新对象被放在了0代区。如果0代堆填满了,就会触发一次垃圾回收。这时候活下来的对象成为1代对象被移动到堆的底部;再此发生垃圾回收后1代对象中存活下来的对象会提升为2代对象并被移动压缩。如图3所示:

.Net 垃圾回收机制原理(二)

图3 托管堆上的0、1、2代对象

2代对象是目前垃圾回收器的最高代,当再次垃圾回收时,没有回收的对象的代数依然保持2.
垃圾回收分代为什么可以优化性能
如前所述,分代回收可以提高性能。当堆填满之后会触发垃圾回收,垃圾回收器可以只选择0代上的对象进行回收,而忽略更高代堆上的对象。然而,由于越年轻的对象生命周期越短,因此,回收0代堆可以回收相当多的内存,而且回收所耗的性能也比回收所有代对象要少得多。
这是分代垃圾回收的最简单优化。分代回收不需要便利整个托管堆,如果一个根对象引用了一个高代对象,那么垃圾回收器可以忽略高代对象和其引用对象的遍历,这会大大减少构建可达对象图的时间。
如果回收0代对象没有释放出足够的内存,垃圾回收器会尝试回收1代和0代堆;如果仍然没有获得足够的内存,那么垃圾回收器会尝试回收2,1,0代堆。具体会回收那一代对象的算法不是确定的,微软会持续做算法优化。
多数堆(像c-runtime堆)只要找到足够的空闲内存就分配给对象。因此,如果我连续分配多个对象时,这些对象的地址空间可能会相差几M。然而在托管堆上,连续分配的对象的内存地址是连续的。
前面的假设中还提到,新对象之间更可能存在相互引用关系。因此新对象分配到连续的内存上,你可以获得就近引用的性能优化(you gain performance from locality of reference)。这样的话很可能你的对象都在CPU的缓存中,这样CPU的很多操作就不需要去存取内存了。
微软的性能测试显示托管堆的分配速度比标准的win32 HeapAlloc方法还要快。这些测试也显示了200MHz的Pentium的CPU做一次0代回收时间可以小于1毫秒。微软的优化目的是让垃圾回收耗用的时间小于一次普通的页面错误。
使用System.GC类控制垃圾回收
类型System.GC运行开发人员直接控制垃圾回收器。你可以通过GC.MaxGeneration属性获得GC的最高代数,目前最高代是定值2.

你可以调用GC.Collect()方法强制垃圾回收器做垃圾回收,Collect方法有两个重载:

void GC.Collect(Int32 generation)void GC.Collect()

第一个方法允许你指定要回收那一代。你可以传0到GC.MaxGeneration的数字做参数,传0只做0代堆的回收,传1会回收1代和0代堆,而传2会回收整个托管堆。而无参数的方法调用GC.Collect(GC.MaxGeneration)相当于整个回收。

在通常情况下,不应该去调用GC.Collect方法;最好让垃圾回收器按照自己的算法判断什么时候该调用Collect方法。尽管如此,如果你确信比运行时更了解什么时候该做垃圾回收,你就可以调用Collect方法去做回收。比如说程序可以在保存数据文件之后做一次垃圾回收。比如你的程序刚刚用完一个长度为10000的大数组,你不再需要他了,就可以把它设置为null然后执行垃圾回收,缓解内存的压力。
GC还提供了WaitForPendingFinalizers方法。这个方法简单的挂起执行线程,知道Freachable队列中的清空之后,执行完所有队列中的Finalize方法之后才继续执行。
GC还提供了两个方法用来返回某个对象是几代对象,他们是

Int32 GC.GetGeneration(object o);Int32 GC.GetGeneration(WeakReference wr)

第一个方法返回普通对象是几代,第二个方法返回弱引用对象的代数。

下面的代码可以帮助你理解代的意义:

private static void GenerationDemo() {// Let's see how many generations the GCH supports (we know it's 2)Display("Maximum GC generations: " + GC.MaxGeneration);// Create a new BaseObj in the heapGenObj obj = new GenObj("Generation");// Since this object is newly created, it should be in generation 0obj.DisplayGeneration(); // Displays 0// Performing a garbage collection promotes the object's generationGC.Collect();obj.DisplayGeneration(); // Displays 1GC.Collect();obj.DisplayGeneration(); // Displays 2GC.Collect();obj.DisplayGeneration(); // Displays 2 (max generation)obj = null; // Destroy the strong reference to this objectGC.Collect(0); // Collect objects in generation 0GC.WaitForPendingFinalizers(); // We should see nothingGC.Collect(1); // Collect objects in generation 1GC.WaitForPendingFinalizers(); // We should see nothingGC.Collect(2); // Same as Collect()GC.WaitForPendingFinalizers(); // Now, we should see the Finalize // method runDisplay(-1, "Demo stop: Understanding Generations.", 0);}class GenObj{public void DisplayGeneration(){Console.WriteLine(“my generation is ” + GC.GetGeneration(this));}~GenObj(){Console.WriteLine(“My Finalize method called”);}}

垃圾回收机制的多线程性能优化

在前面的部分,我解释了GC的算法和优化,然后讨论的前提都是在单线程情况下的。而在真实的程序中,很可能是多个线程一起工作,多个线程一起操纵托管堆上的对象。当一个线程触发了垃圾回收,其他所有的线程都应该暂停访问任何引用对象(包括他们自己栈上引用的对象),因为垃圾回收器有可能要移动对象,修改对象的内存地址。
因此当垃圾回收器开始回收时,所有执行托管代码的线程必须挂起。运行时有几种不同的机制可以安全的挂起线程来执行垃圾回收。这一块的内部机制我不打算详细说明。但是微软会持续修改垃圾回收的机制来降低垃圾回收带来的性能损耗。
下面几段描述了垃圾回收器在多线程情况下是如何工作的:
完全中断代码执行 当垃圾回收开始执行时,挂起所有应用程序线程。垃圾回收器随后将线程挂起的位置记录到一个just-in-time(JIT)编译器生成的表中,垃圾回收器负责将线程挂起的位置记录在表中,记录当前正在访问的对象,以及对象存放的位置(变量中,CPU寄存器中,等等)
劫持:垃圾回收器可以修改线程的栈让返回地址指向一个特殊的方法,当当前执行的方法返回时,这个特殊的方法将会执行,挂起线程,这种改变线程执行路径的方式称为劫持线程。当垃圾回收完成之后,线程会重新返回到之前执行的方法上。
安全点: 当JIT编译器编译一个方法时,可以在某个点插入一段代码判断GC是否挂起,如果是,线程就挂起等待垃圾回收完成,然后线程重新开始执行。JIT编译器插入检查GC代码的位置被称作“安全点”
请注意,线程劫持允许正在执行非托管代码的线程在垃圾回收过程中执行。如果非托管代码不访问托管堆上的对象时这是没有问题的。如果这个线程当前执行非托管代码然后返回执行托管代码,这个线程将会被劫持,直到垃圾回收完成之后再继续执行。
除了我刚提到的集中机制之外,垃圾回收器还有其他改进来增强多线程程序中的对象内存分配和回收。
同步释放分配(Synchronization-free Allocations):在一个多线程系统中,0代堆被分成几个区域,一个线程使用一个区域。这允许多线程同时分配对象,并不需要一个线程独占堆。
可伸缩回收(Scalable Collections):在多线程系统中运行执行引擎的服务器版本(MXSorSvr.dll).托管堆会被分成几个不同的区域,一个CPU一个区域。当回收初始化时,每个CPU执行一个回收线程,各个线程回收各自的区域。而工作站版本的执行引擎(MXCorWks.dll)不支持这个功能。
大对象回收
这一块就不翻译了,有一篇专门的文章谈这件事儿
监视垃圾回收
如果你安装了.Net framework你的性能计数器(开始菜单—管理工具—性能 进入)中就会有.Net CLR Memory一项,你可以从实例列表中选择某个程序进行观察,如下图所示。

.Net 垃圾回收机制原理(二)

这些性能指标的具体含义如下:

性能计数器

说明

# Bytes in all Heaps(所有堆中的字节数)

显示以下计数器值的总和:“第 0 级堆大小”计数器、“第 1 级堆大小”计数器、“第 2 级堆大小”计数器和“大对象堆大小”计数器。此计数器指示在垃圾回收堆上分配的当前内存(以字节为单位)。

# GC Handles(GC 处理数目)

显示正在使用的垃圾回收处理的当前数目。垃圾回收处理是对公共语言运行库和托管环境外部的资源的处理。

# Gen 0 Collections(第 2 级回收次数)

显示自应用程序启动后第 0 级对象(即最年轻、最近分配的对象)被垃圾回收的次数。

当第 0 级中的可用内存不足以满足分配请求时发生第 0 级垃圾回收。此计数器在第 0 级垃圾回收结束时递增。较高级的垃圾回收包括所有较低级的垃圾回收。当较高级(第 1 级或第 2 级)垃圾回收发生时此计数器被显式递增。

此计数器显示最近的观察所得值。_Global_ 计数器值不准确,应该忽略。

# Gen 1 Collections(第 2 级回收次数)

显示自应用程序启动后对第 1 级对象进行垃圾回收的次数。

此计数器在第 1 级垃圾回收结束时递增。较高级的垃圾回收包括所有较低级的垃圾回收。当较高级(第 2 级)垃圾回收发生时此计数器被显式递增。

此计数器显示最近的观察所得值。_Global_ 计数器值不准确,应该忽略。

# Gen 2 Collections(第 2 级回收次数)

显示自应用程序启动后对第 2 级对象进行垃圾回收的次数。此计数器在第 2 级垃圾回收(也称作完整垃圾回收)结束时递增。

此计数器显示最近的观察所得值。_Global_ 计数器值不准确,应该忽略。

# Induced GC(引发的 GC 的数目)

显示由于对 GC.Collect 的显式调用而执行的垃圾回收的峰值次数。让垃圾回收器对其回收的频率进行微调是切实可行的。

# of Pinned Objects(钉住的对象的数目)

显示上次垃圾回收中遇到的钉住的对象的数目。钉住的对象是垃圾回收器不能移入内存的对象。此计数器只跟踪被进行垃圾回收的堆中的钉住的对象。例如,第 0 级垃圾回收导致仅枚举第 0 级堆中钉住的对象。

# of Sink Blocks in use(正在使用的接收块的数目)

显示正在使用的同步块的当前数目。同步块是为存储同步信息分配的基于对象的数据结构。同步块保留对托管对象的弱引用并且必须由垃圾回收器扫描。同步块不局限于只存储同步信息;它们还可以存储 COM interop 元数据。该计数器指示与同步基元的过度使用有关的性能问题。

# Total committed Bytes(提交字节的总数)

显示垃圾回收器当前提交的虚拟内存量(以字节为单位)。提交的内存是在磁盘页面文件中保留的空间的物理内存。

# Total reserved Bytes(保留字节的总数)

显示垃圾回收器当前保留的虚拟内存量(以字节为单位)。保留内存是为应用程序保留(但尚未使用任何磁盘或主内存页)的虚拟内存空间。

% Time in GC(GC 中时间的百分比)

显示自上次垃圾回收周期后执行垃圾回收所用运行时间的百分比。此计数器通常指示垃圾回收器代表该应用程序为收集和压缩内存而执行的工作。只在每次垃圾回收结束时更新此计数器。此计数器不是一个平均值;它的值反映了最近观察所得值。

Allocated Bytes/second(每秒分配的字节数)

显示每秒在垃圾回收堆上分配的字节数。此计数器在每次垃圾回收结束时(而不是在每次分配时)进行更新。此计数器不是一段时间内的平均值;它显示最近两个样本中观测的值的差除以取样间隔时间所得的结果。

Finalization Survivors(完成时存留对象数目)

显示因正等待完成而从回收后保留下来的进行垃圾回收的对象的数目。如果这些对象保留对其他对象的引用,则那些对象也保留下来,但此计数器不对它们计数。“从第 0 级提升的完成内存”和“从第 1 级提升的完成内存”计数器表示因完成而保留下来的所有内存。

此计数器不是累积计数器;它在每次垃圾回收结束时由仅在该特定回收期间存留对象的计数更新。此计数器指示由于完成应用程序可能导致系统开销过高。

Gen 0 heap size(第 2 级堆大小)

显示在第 0 级中可以分配的最大字节数;它不指示在第 0 级中当前分配的字节数。

当自最近回收后的分配超出此大小时发生第 0 级垃圾回收。第 0 级大小由垃圾回收器进行微调并且可在应用程序执行期间更改。在第 0 级回收结束时,第 0 级堆的大小是 0 字节。此计数器显示调用下一个第 0 级垃圾回收的分配的大小(以字节为单位)。

此计数器在垃圾回收结束时(而不是在每次分配时)进行更新。

Gen 0 Promoted Bytes/Sec(从第 1 级提升的字节数/秒)

显示每秒从第 0 级提升到第 1 级的字节数。内存在从垃圾回收保留下来后被提升。此计数器是每秒创建的在相当长时间保留下来的对象的指示符。

此计数器显示在最后两个样本(以取样间隔持续时间来划分)中观察到的值之间的差异。

Gen 1 heap size(第 2 级堆大小)

显示第 1 级中的当前字节数;此计数器不显示第 1 级的最大大小。不直接在此代中分配对象;这些对象是从前面的第 0 级垃圾回收提升的。此计数器在垃圾回收结束时(而不是在每次分配时)进行更新。

Gen 1 Promoted Bytes/Sec(从第 1 级提升的字节数/秒)

显示每秒从第 1 级提升到第 2 级的字节数。在此计数器中不包括只因正等待完成而被提升的对象。

内存在从垃圾回收保留下来后被提升。不会从第 2 级进行任何提升,因为它是最旧的一级。此计数器是每秒创建的非常长时间保留下来的对象的指示符。

此计数器显示在最后两个样本(以取样间隔持续时间来划分)中观察到的值之间的差异。

Gen 2 heap size(第 2 级堆大小)

显示第 2 级中当前字节数。不直接在此代中分配对象;这些对象是在以前的第 1 级垃圾回收期间从第 1 级提升的。此计数器在垃圾回收结束时(而不是在每次分配时)进行更新。

Large Object Heap size(大对象堆大小)

显示大对象堆的当前大小(以字节为单位)。垃圾回收器将大于 20 KB 的对象视作大对象并且直接在特殊堆中分配大对象;它们不是通过这些级别提升的。此计数器在垃圾回收结束时(而不是在每次分配时)进行更新。

Promoted Finalization-Memory from Gen 0(从第 1 级提升的完成内存)

显示只因等待完成而从第 0 级提升到第 1 级的内存的字节数。此计数器不是累积计数器;它显示在最后一次垃圾回收结束时观察到的值。

Promoted Finalization-Memory from Gen 1(从第 1 级提升的完成内存)

显示只因等待完成而从第 1 级提升到第 2 级的内存的字节数。此计数器不是累积计数器;它显示在最后一次垃圾回收结束时观察到的值。如果最后一次垃圾回收就是第 0 级回收,此计数器则重置为 0。

Promoted Memory from Gen 0(从第 1 级提升的内存)

显示在垃圾回收后保留下来并且从第 0 级提升到第 1 级的内存的字节数。此计数器中不包括那些只因等待完成而提升的对象。此计数器不是累积计数器;它显示在最后一次垃圾回收结束时观察到的值。

Promoted Memory from Gen 1(从第 1 级提升的内存)

显示在垃圾回收后保留下来并且从第 1 级提升到第 2 级的内存的字节数。此计数器中不包括那些只因等待完成而提升的对象。此计数器不是累积计数器;它显示在最后一次垃圾回收结束时观察到的值。如果最后一次垃圾回收就是第 0 级回收,此计数器则重置为 0。

这个表来自MSDN

 以上就是.Net 垃圾回收机制原理(二)的内容,更多相关内容请关注PHP中文网(www.php.cn)! 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1432327.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月17日 06:28:54
下一篇 2025年12月10日 18:38:21

相关推荐

  • .Net 垃圾回收机制原理(一)

    英文原文:Jeffrey Richter 编译:赵玉开 链接:http://www.php.cn/ 有了Microsoft.Net clr中的垃圾回收机制程序员不需要再关注什么时候释放内存,释放内存这件事儿完全由GC做了,对程序员来说是透明的。尽管如此,作为一个.Net程序员很有必要理解垃圾回收是如…

    2025年12月17日 好文分享
    000
  • .NET中异常处理的最佳实践(译)

    原文地址:点击打开链接 本文翻译自CodeProject上的一篇文章,原文地址。 目录 介绍 做最坏的打算 提前检查 不要信任外部数据 可信任的设备:摄像头、鼠标以及键盘  “写操作”同样可能失效 安全编程 不要抛出“new Exception()” 不要将重要的异常信息存储在Message属性中 …

    2025年12月17日
    000
  • .NET中的六个重要概念:栈、堆、值类型、引用类型、装箱和拆箱

    内容导读 •概述 •当你声明一个变量背后发生了什么?•堆和栈•值类型和引用类型•哪些是值类型,哪些是引用类型?•装箱和拆箱•装箱和拆箱的性能问题一、概述本文会阐述六个重要的概念:堆、栈、值类型、引用类型、装箱和拆箱。本文首先会通过阐述当你定义一个变量之后系统内部发生的改变开始讲解,然后将关注点转移到…

    2025年12月17日 好文分享
    000
  • .Net 垃圾回收和大对象处理

    英文原文:Maoni Stephens,编译:赵玉开(@玉开Sir) CLR垃圾回收器根据所占空间大小划分对象。大对象和小对象的处理方式有很大区别。比如内存碎片整理 —— 在内存中移动大对象的成本是昂贵的,让我们研究一下垃圾回收器是如何处理大对象的,大对象对程序性能有哪些潜在的影响。 大对象堆和垃圾…

    2025年12月17日 好文分享
    000
  • C# 中 10 个你真的应该学习(和使用!)的功能

    如果你开始探索c#或决定扩展你的知识,那么你应该学习这些有用的语言功能,这样做有助于简化代码,避免错误,节省大量的时间。    1)async / await 使用async / await-pattern允许在执行阻塞操作时解除UI /当前线程的阻塞。async / await-pattern的工…

    好文分享 2025年12月17日
    000
  • 关于.NET异常处理的思考(上)

    在项目开发中,对于系统和代码的稳定性和容错性都是有对应的要求。实际开发项目中的代码与样例代码的区别,更多的是在代码的运行的稳定性、容错性、扩展性的比较。 因为对于实现一个功能来说,实现功能的核心代码是一样的,可能只是在写法上优化而已,但是在实现某一个操作上使用的类来说,这一点是绝大多数时候是一样的。…

    好文分享 2025年12月17日
    000
  • .NET异常设计原则

    异常是使用.net时必然会遇到的问题,但是,有太多的开发人员没有从api设计的角度考虑这个问题。在大部分工作中,他们自始至终都知道需要捕获什么异常以及哪些异常需要写入全局日志。如果你设计了可以让你正确使用异常的api,则可以显著减少修复缺陷的时间。 谁的错? 异常设计背后的基本理论始于这样一个问题,…

    好文分享 2025年12月17日
    000
  • .NET编程之线程池内幕

    本文通过对.net4.5的threadpool源码的分析讲解揭示.net线程池的内幕,并总结threadpool设计的好与不足。 线程池的作用 线程池,顾名思义,线程对象池。Task和TPL都有用到线程池,所以了解线程池的内幕有助于你写出更好的程序。由于篇幅有限,在这里我只讲解以下核心 概念: 线程…

    好文分享 2025年12月17日
    000
  • .NET下几个服务框架介绍

    简介 在公司的服务多了以后,为了调用上的方便,同时为了以后的服务治理,一般都会使用一些服务框架,这里主要介绍我知道的几个服务框架,简析一下这些服务框架的基本概念。 可投入生产环境使用的 以下两个服务框架,我已经见过有公司投入到生产环境,所以对于稳定性,应该不需要有太大的担心。    ServiceS…

    2025年12月17日
    000
  • .NET多线程编程—并发集合

    并发集合 1 为什么使用并发集合? 原因主要有以下几点: System.Collections和System.Collections.Generic名称空间中所提供的经典列表、集合和数组都不是线程安全的,若无同步机制,他们不适合于接受并发的指令来添加和删除元素。 在并发代码中使用上述经典集合需要复杂…

    2025年12月17日
    000
  • .NET编程中Word/Excel 在线预览

    前言 近日项目中做到一个功能,需要上传附件后能够在线预览。之前也没做过这类似的,于是乎就查找了相关资料,.net实现office文件预览大概有这几种方式: 使用Microsoft的Office组件将文件直接转换为html文件(优点:代码实现最简单,工作强度最小。缺点:效果极差) 使用Microsof…

    2025年12月17日
    000
  • 记一次.NET代码重构(上)

    需求:是这样的,要开发一个短信发送的模板,不同客户可能会使用不同的模板,而不同的客户使用的变量参数也是不同的。 之前为了应急,线上已经完成了一个短信模板发送短信的功能,短信模板表也创建了,而且在表中已经新增了一条记录。我只需要做一个短信模板的增删改查界面就可以了,看上去我的任务挺简单的,老司机应该知…

    2025年12月17日 好文分享
    000
  • 记一次.NET代码重构(下)

    public override bool TryGetMember(GetMemberBinder binder, out object result){ if (!_dictionary.TryGetValue(binder.Name, out result)) { result = null; …

    2025年12月17日
    000
  • 为 Jenkins 配置 .NET 持续集成环境

    去年年底,得益于公司引入 jenkins,让我们在持续集成方面迈出了第一步,本文不赘述如何安装 jenkins,主要关注点在于配置 .net 环境。另外本文是在 windows 环境下安装的 jenkins 进行操作。 一、安装环境 首先我们需要先准备几个安装包,将它们安装到 Windows 上: …

    2025年12月17日
    000
  • c#.net中const和readonly的区别

    (1) readonly和const都是用来标示常量的。(2) 初始化赋值不同。const修饰的常量必须在声明的同时赋值。例如: public class Class1 { public const int MaxValue = 10; //正确声明 public const MInValue; /…

    好文分享 2025年12月17日
    000
  • XML中如何生成XML报表模板_XML生成XML报表模板的方法与示例

    利用XSLT、编程语言或模板引擎可生成XML报表模板:1. XSLT将源XML转换为结构化报表;2. Python等语言通过DOM操作动态构建XML;3. Jinja2等模板引擎支持变量与逻辑控制,实现灵活输出。 在XML中生成XML报表模板,实际上是指利用XML的结构化特性设计一个可复用的数据模板…

    2025年12月17日
    000
  • XML中如何删除指定节点_XML删除指定节点的方法与技巧

    使用DOM、XPath、SAX/StAX或工具库可删除XML指定节点。DOM适合中小文件,通过removeChild()删除目标节点;XPath支持复杂条件精准定位;SAX/StAX流式处理适用于大文件;工具库如ElementTree提供简洁API。选择方法需考虑文件大小与性能需求。 在处理XML文…

    2025年12月17日
    000
  • XML中如何校验XML节点顺序_XML校验XML节点顺序的方法与技巧

    答案:使用XSD的xs:sequence可严格校验XML节点顺序,如FirstName→LastName→Age;若顺序错乱则校验失败。 在XML处理过程中,校验节点顺序是确保数据结构符合预期的重要环节。特别是在与外部系统交互、接口对接或数据导入导出时,严格的节点顺序可能影响解析结果或业务逻辑。虽然…

    2025年12月17日
    000
  • XML中如何处理空白节点_XML处理空白节点的操作步骤

    正确处理XML空白节点需根据解析器设置或编程逻辑过滤非重要空白。例如Java DOM可设setIgnoringElementContentWhitespace(true),Python可预处理移除,.NET可通过PreserveWhitespace=false控制,默认保留空白;也可通过DTD/XS…

    2025年12月17日
    000
  • XML中如何合并XML片段_XML合并XML片段的操作方法与技巧

    正确合并XML片段需先创建统一根节点,再通过编程语言的XML库或XSLT将各片段导入,确保编码、命名空间和属性唯一性,避免字符串拼接以防止结构错误。 在处理XML数据时,经常需要将多个XML片段合并成一个完整的文档。这种操作常见于配置文件整合、数据聚合或服务间通信场景。正确地合并XML片段不仅能保证…

    2025年12月17日
    000

发表回复

登录后才能评论
关注微信