[No0000147]深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing)理解堆与栈4/4
前言
简介
绘图Graphing
- 全局/静态 指针。通过以静态变量的方式保持对象的引用,来确保对象不会被GC回收。
- 栈里的指针。为了程序的执行,我们不想扔掉那些程序线程始终需要的对象。
- CPU寄存器指针。托管堆里任何被CPU内存地址指向的对象都需要被保留。
GC垃圾清理Compacting
托管堆之外的终止化队列Finalization Queue和终止化-可达队列Freachable Queue
- class Sample
- {
- ~Sample()
- {
- // FINALIZER: CLEAN UP HERE 终结器:在这里清理
- }
- }
译外话:析构函数会被内部转换成终结器override Finializer()
- public class ResourceUser : IDisposable
- {
- #region IDisposable Members
- public void Dispose()
- {
- // 在这里清理!!!
- }
- #endregion
- }
实现IDisposable接口的对象可以使用using关键字:
- using (ResourceUser rec = new ResourceUser())
- {
- // 具体实现。。。
- } // 这里调用DISPOSE方法
变量rec的作用域是大括号内,大括号外不可访问。
静态变量
- class Olympics
- {
- public static Collection<Runner> TryoutRunners;
- }
- class Runner
- {
- private string _fileName;
- private FileStream _fStream;
- public void GetStats()
- {
- FileInfo fInfo = new FileInfo(_fileName);
- _fStream = _fileName.OpenRead();
- }
- }
如果你初始化了TryoutRunners,那么它将永远不会被GC清理,因为有静态指针一直指向初始化的对象。一旦调用了Runner里GetStats()方法,因为GetStats()里面没有文件关闭操作,它将永远被打开也不会被GC清理。我们可以看到程序的崩溃即将来临。
总结
- 清理。不要打开资源而不关闭它。关闭所有你打开的连接。尽可能快的清理所有非托管资源。一般规则:使用非托管对象,初始化越晚越好,清理越早越好。
- 不要过度引用。合理使用引用对象。如果某一个对象还存在没有被GC清理,所有它引用的对象都将不会被GC清理,如此递归下去。。。当我们完成使用一个引用对象时,把它设为NULL(视你的情况而定,注意不要产生空引用异常)。当引用少了,GC开始创建清理关系图graphing时过程就简单一些了,进而提高程序性能。
- 谨慎使用终结器Finalizaer或析构函数。能使用IDisposible代替就使用IDisposible。
- 保持对象及其成员的紧凑。如果声明一个对象并且它由多个子对象组成,尽可能的把它们放在一起初始化,好让它们所在的内存空间紧凑。GC复制这样的一大块内存比复制分散的内存碎片要容易。
Even though with the .NET framework we don't have to actively worry about memory management and garbage collection (GC), we still have to keep memory management and GC in mind in order to optimize the performance of our applications. Also, having a basic understanding of how memory management works will help explain the behavior of the variables we work with in every program we write. In this article we'll look into Garbage Collection (GC) and some ways to keep our applications running efficiently.
Graphing
Let's look at this from the GC's point of view. If we are responsible for "taking out the trash" we need a plan to do this effectively. Obviously, we need to determine what is garbage and what is not (this might be a bit painful for the pack-rats out there).
In order to determine what needs to be kept, we'll first make the assumption that everything not being used is trash (those piles of old papers in the corner, the box of junk in the attic, everything in the closets, etc.) Imagine we live with our two good friends: Joseph Ivan Thomas (JIT) and Cindy Lorraine Richmond (CLR). Joe and Cindy keep track of what they are using and give us a list of things they need to keep. We'll call the initial list our "root" list because we are using it as a starting point. We'll be keeping a master list to graph where everything is in the house that we want to keep. Anything that is needed to make things on our list work will be added to the graph (if we're keeping the TV we don't throw out the remote control for the TV, so it will be added to the list. If we're keeping the computer the keyboard and monitor will be added to the "keep" list).
This is how the GC determines what to keep as well. It receives a list of "root" object references to keep from just-in-time (JIT) compiler and common language runtime (CLR) (Remember Joe and Claire?) and then recursively searches object references to build a graph of what should be kept.
Roots consist of:
- Global/Static pointers. One way to make sure our objects are not garbage collected by keeping a reference to them in a static variable.
- Pointers on the stack. We don't want to throw away what our application's threads still need in order to execute.
- CPU register pointers. Anything in the managed heap that is pointed to by a memory address in the CPU should be preserved (don't throw it out).
In the above diagram, objects 1, 3, and 5 in our managed heap are referenced from a root 1 and 5 are directly referenced and 3 is found during the recursive search. If we go back to our analogy and object 1 is our television, object 3 could be our remote control. After all objects are graphed we are ready to move on to the next step, compacting.
Compacting
Now that we have graphed what objects we will keep, we can just move the "keeper objects" around to pack things up.
Fortunately, in our house we don't need to clean out the space before we put something else there. Since Object 2 is not needed, as the GC we'll move Object 3 down and fix the pointer in Object 1.
Next, as the GC, we'll copy Object 5 down
Now that everything is cleaned up we just need to write a sticky note and put it on the top of our compacted heap to let Claire know where to put new objects.
Knowing the nitty-gritty of CG helps in understanding that moving objects around can be very taxing. As you can see, it makes sense that if we can reduce the size of what we have to move, we'll improve the whole GC process because there will be less to copy.
What about things outside the managed heap?
As the person responsible for garbage collection, one problem we run into in cleaning house is how to handle objects in the car. When cleaning, we need to clean everything up. What if the laptop is in the house and the batteries are in the car?
There are situations where the GC needs to execute code to clean up non-managed resources such as files, database connections, network connections, etc. One possible way to handle this is through a finalizer.
class Sample
{
~Sample()
{
// FINALIZER: CLEAN UP HERE
}
}
During object creation, all objects with a finalizer are added to a finalization queue. Let's say objects 1, 4, and 5 have finalizers and are on the finalization queue. Let's look at what happens when objects 2 and 4 are no longer referenced by the application and ready for garbage collection.
Object 2 is treated in the usual fashion. However, when we get to object 4, the GC sees that it is on the finalization queue and instead of reclaiming the memory object 4 owns, object 4 is moved and it's finalizer is added to a special queue named freachable.
There is a dedicated thread for executing freachable queue items. Once the finalizer is executed by this thread on Object 4, it is removed from the freachable queue. Then and only then is Objet 4 ready for collection.
So Object 4 lives on until the next round of GC.
Because adding a finalizer to our classes creates additional work for GC it can be very expensive and adversely affect the performance garbage collection and thus our program. Only use finalizers when you are absolutely sure you need them.
A better practice is to be sure to clean up non-managed resources. As you can imagine, it is preferable to explicitly close connections and use the IDisposable interface for cleaning up instead of a finalizer where possible.
IDisposaible
Classes that implement IDisposable perform clean-up in the Dispose() method (which is the only signature of the interface). So if we have a ResouceUser class instead of using a finalizer as follows:
public class ResourceUser
{
~ResourceUser() // THIS IS A FINALIZER
{
// DO CLEANUP HERE
}
}
We can use IDisposable as a better way to implement the same functionality:
public class ResourceUser : IDisposable
{
#region IDisposable Members
public void Dispose()
{
// CLEAN UP HERE!!!
}
#endregion
}
IDisposable in integrated with the using keyword. At the end of the using block Dispose() is called on the object declared in using(). The object should not be referenced after the using block because it should be essentially considered "gone" and ready to be cleaned up by the GC.
public static void DoSomething()
{
ResourceUser rec = new ResourceUser();
using (rec)
{
// DO SOMETHING
} // DISPOSE CALLED HERE
// DON'T ACCESS rec HERE
}
I like putting the declaration for the object in the using block because it makes more sense visabally and rec is no longer available outside of the scope of the using block. Whis this pattern is more in line with the intention of the IDisposible interface, it is not required.
public static void DoSomething()
{
using (ResourceUser rec = new ResourceUser())
{
// DO SOMETHING
} // DISPOSE CALLED HERE
}
By using using() with classes that implement IDisposible we can perform our cleanup without putting additional overhead on the GC by forcing it to finalize our objects.
Static Variables: Watch Out!
class Counter
{
private static int s_Number = 0;
public static int GetNextNumber()
{
int newNumber = s_Number;
// DO SOME STUFF
s_Number = newNumber + 1;
return newNumber;
}
}
If two threads call GetNextNumber() at the same time and both are assigned the same value for newNumber before s_Num}
If two threads call GetNextNumber() at the same time and both are assigned the same value for newNumber before s_Number is incremented they will return the same result!word is one way to ensure only one thread can access a block of code at a time. As a best practice, you should lock as little code as possible because threads have to wait in a queue to execute the code in the lock() block and it can be inefficient.
class Counter
{
private static int s_Number = 0;
public static int GetNextNumber()
{
lock (typeof(Counter))
{
int newNumber = s_Number;
// DO SOME STUFF
newNumber += 1;
s_Number = newNumber;
return newNumber;
}
}
}
Static Variables: Watch Out... Number 2!
The next thing we have to watch out for objects referenced by static variables. Remember, how anything that is referenced by a "root" is not cleaned up. Here's one of the ugliest examples I can come up with:
class Olympics
{
public static Collection<Runner> TryoutRunners;
}
class Runner
{
private string _fileName;
private FileStream _fStream;
public void GetStats()
{
FileInfo fInfo = new FileInfo(_fileName);
_fStream = _fileName.OpenRead();
}
}
Because the Runner Collection is static for the Olympics class, not only will objects in the collection will not be released for garbage collection (they are all indirectly referenced through a root), but as you probably noticed, every time we run GetStats() the stream is opened to the file. Because it is not closed and never released by GC this code is effectively a disaster waiting to happen. Imagine we have 100,000 runners trying out for the Olympics. We would end up with that many non-collectable objects each with an open resource. Ouch! Talk about poor performance!
Singleton
One trick to keep things light is to keep only one instance of a class in memory at all times. To do this we can use the GOF Singleton Pattern.
One trick to keep things light is to keep only one instance of a utility class in memory at all times. One easy way to do this we can use the GOF Singleton Pattern. Singletons should be used with caution because they are really "global variables" and cause us much headached and "strange" behavior in multi-threaded applications where different threads could be altering the state of the object. If we are using the singleton pattern (or any global variable) we should be able to justify it (in other words... don't do it without a good reason).
public">{
private static Earth _instance = new Earth();
private Earth() { }
public static Earth GetInstance() { return _instance; }
}
We have a private constructor so only Earth can execute it's constructor and make an Earth. We have a static instance of Earth and a static method to get the instance. This particular implementation is thread safe because the CLR ensures thread safe creation of static variables. This is the most elegant way I have found to implement the singleton pattern in C#.
In Conclusion...
So to wrap up, some things we can do to improve GC performance are:
- Clean up. Don't leave resources open! Be sure to close all connections that are opened and clean up all non-managed objects as soon as possible. As a general rule when using non-managed objects, instantiate as late as possible and clean up as soon as possible.
- Don't overdo references. Be reasonable when using references objects. Remember, if our object is alive, all of it's referenced objects will not be collected (and so on, and so on). When we are done with something referenced by class, we can remove it by either setting the reference to null. One trick I like to do is setting unused references to a custom light weight NullObject to avoid getting null reference exceptions. The fewer references laying about when the GC kicks off, the less pressure the mapping process will be.
- Easy does it with finalizers. Finalizers are expensive during GC we should ONLY use them if we can justify it. If we can use IDisposible instead of a finalizer, it will be more efficient because our object can be cleaned up in one GC pass instead of two.
- Keep objects and their children together. It is easier on the GC to copy large chunks of memory together instead of having to essentially de-fragment the heap at each pass, so when we declare a object composed of many other objects, we should instantiate them as closely together as possible.
Next time we'll look even more closely at the GC process and look into ways to check under the hood as your program executes to discover problems that may need to be cleaned up.
Until then,
-Happy coding
[No0000147]深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing)理解堆与栈4/4的更多相关文章
- [No0000145]深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing)理解堆与栈2/4
前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程 ...
- [No0000144]深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing)理解堆与栈1/4
前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程 ...
- [No0000146]深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing)理解堆与栈3/4
前言 虽然在.Net Framework 中我们不必考虑内在管理和垃圾回收(GC),但是为了优化应用程序性能我们始终需要了解内存管理和垃圾回收(GC).另外,了解内存管理可以帮助我们理解在每一个程 ...
- Java 堆内存与栈内存异同(Java Heap Memory vs Stack Memory Difference)
--reference Java Heap Memory vs Stack Memory Difference 在数据结构中,堆和栈可以说是两种最基础的数据结构,而Java中的栈内存空间和堆内存空间有 ...
- java - Stack栈和Heap堆的区别
首先分清楚Stack,Heap的中文翻译:Stack—栈,Heap—堆. 在中文里,Stack可以翻译为“堆栈”,所以我直接查找了计算机术语里面堆和栈开头的词语: 堆存储 ...
- 内存,堆,栈,heap,stack,data
1. 基本类型占一块内存. 引用类型占两块. 2. 类是静态概念. 函数中定义的基本类型变量和对象的引用类型变量都在函数的栈内存. 局部变量存在栈内存. new创建的对象和数组,存在堆内存. java ...
- iOS:堆(heap)和栈(stack)的理解
Objective-C的对象在内存中是以堆的方式分配空间的,并且堆内存是由你释放的,即release 栈由编译器管理自动释放的,在方法中(函数体)定义的变量通常是在栈内,因此如果你的变量要跨函数的话就 ...
- iOS中的堆(heap)和栈(stack)的理解
操作系统iOS 中应用程序使用的计算机内存不是统一分配空间,运行代码使用的空间在三个不同的内存区域,分成三个段:“text segment “,“stack segment ”,“heap segme ...
- stm32 堆和栈(stm32 Heap & Stack)【worldsing笔记】
关于堆和栈已经是程序员的一个月经话题,大部分有是基于os层来聊的. 那么,在赤裸裸的单片机下的堆和栈是什么样的分布呢?以下是网摘: 刚接手STM32时,你只编写一个 int main() ...
随机推荐
- C语言判断文件夹或者文件是否存在的方法【转】
C语言判断文件夹或者文件是否存在的方法 方法一:access函数判断文件夹或者文件是否存在 函数原型: int access(const char *filename, int mode); 所 ...
- 你被R语言的=和<-搞昏了头吗
学习R有一周了,心中一直有一个困惑,关于= 和 <-,今晚决定搞定它! 迄今为止用到最多的函数是matrix() 和c(),就用他们说起! 之前学了四五门语言,对于=赋值已经成了惯性,下面是 ...
- linux下zip包处理
先来看例子: zip命令可以用来将文件压缩成为常用的zip格式.unzip命令则用来解压缩zip文件. 1. 我想把一个文件abc.txt和一个目录dir1压缩成为yasuo.zip: # zip - ...
- JS中的PadLeft、PadRight,位数不足,自动补位,String扩展方法
类似C#中的 PadLeft.PadRight方法 //方法一 function FillZero(p) { return new Array(3 - (p + '').length + 1).joi ...
- json简介及JsonCpp用法
[时间:2017-04] [状态:Open] [关键词:数据交换格式,json,jsoncpp,c++,json解析,OpenSource] json简介 本文仅仅是添加我个人对json格式的理解,更 ...
- Kaggle 自行车租赁预测比赛项目实现
作者:大树 更新时间:01.20 email:59888745@qq.com 数据处理,机器学习 回主目录:2017 年学习记录和总结 .caret, .dropup > .btn > . ...
- Linux 下配置Tomcat的虚拟路径
如果你的Linux服务器下,不止一个tomcat的时候,这个时候,你就会发现,每次去发布项目很麻烦,还需要到webapps下面去看,繁琐的很,这里就用到了,Tomcat的虚拟路径,制定一个目录,作为t ...
- Java知多少(31)static关键字以及Java静态变量和静态方法
static 修饰符能够与变量.方法一起使用,表示是“静态”的. 静态变量和静态方法能够通过类名来访问,不需要创建一个类的对象来访问该类的静态成员,所以static修饰的成员又称作类变量和类方法.静态 ...
- 【转】Spring Framework灰度发布
今天简单介绍下SpringFramework微服务中几种服务发布策略以及实现方式.我接触过的有蓝绿.滚筒和灰度发布. 蓝绿发布: 简单说就像美帝选总统投票一样,非蓝即绿一刀切,这个其实也是传统软件架构 ...
- Linux-C实现GPRS模块发送短信
“GSM模块,是将GSM射频芯片.基带处理芯片.存储器.功放器件等集成在一块线路板上,具有独立的操作系统.GSM射频处理.基带处理并提供标准接口的功能模块.GSM模块根据其提供的数据传输速率又可以分为 ...