沈阳百度网站的优点,嵌入式培训机构,企业展厅布展设计,个人简介代码网页制作一#xff1a;背景1. 讲故事最近也是奇怪#xff0c;在社区里看到好几篇文章聊static 的玩法以及怎么拿这个和面试官扯半个小时#xff0c;有点意思#xff0c;点进去看都是java版的#xff0c;这就没意思了#xff0c;怎么也得有一篇和面试官扯C# 中的 static用法撒背景1. 讲故事最近也是奇怪在社区里看到好几篇文章聊static 的玩法以及怎么拿这个和面试官扯半个小时有点意思点进去看都是java版的这就没意思了怎么也得有一篇和面试官扯C# 中的 static用法撒既然没有人开这个头那我就献丑了。。。下面以QA的方式记述大家可以代入一下能回答几个问题。二QA环节面试官请问您都是在什么场景下用static的 解析可能面试官潜意识的想问问你会不会使用本地缓存。码农先不说我的场景纵观C#的底层FCL源码你会发现很多的 static修饰的集合如ThreadPool:[SecurityCritical]private static bool QueueUserWorkItemHelper(WaitCallback callBack, object state, ref StackCrawlMark stackMark, bool compressStack){QueueUserWorkItemCallback callback new QueueUserWorkItemCallback(callBack, state, compressStack, ref stackMark);ThreadPoolGlobals.workQueue.Enqueue(callback, forceGlobal: true);result true;}
其中的 workQueue 就是一个静态队列不仅如此还有Quartz底层自研的线程池还有web中的SessionApplication无非就是想用static做一个池化技术和AppDomain级的本地缓存所以我的应用场景也无非是这些了。面试官您会几种实现单例的方式 解析既然面试官想和你扯static就是想看看你会不会用 static cctor静态构 造器构建单例!码农实不相瞒不管是用懒汉式还是饿汉式大体上也就这几种 双检锁, static cctor, LazyT, 不知道您想让我细说哪一种面试官那就说一下静态构造函数为什么可以实现单例 解析可能觉得码农回答的有点拽问深一点看看是不是唬人的。码农说到单例每一个人都会提到在多线程场景下的并发问题导致多个单例的尴尬所以有了给代码加上各种花哨的锁比如刚才我提到的双检索,所以说没有锁。。。这个问题是搞不定的换句话说 静态构造函数 也是用了锁机制。面试官你确定用到了锁有证据吗 解析有戏了对你产生感兴趣了愿听其详。码农既然要证据那我先构思一段如下代码class Program{static void Main(string[] args){Person person new Person();Console.ReadLine();}}class Person{static Person(){Console.WriteLine(正在处理静态函数);Console.ReadLine();}}然后抓一个dump文件用windbg 看一下主线程的托管和非托管堆栈。 C#0:000 ~0s
ntdll!NtReadFile0x14:
00007ff88d2eaa64 c3 ret
0:000 !dumpstack
OS Thread Id: 0x4ac0 (0)
Current frame: ntdll!NtReadFile0x14
Child-SP RetAddr Caller, Callee
000000c119bfdcd0 00007ff817090957 (MethodDesc 00007ff816f85aa8 0x37 ConsoleApp6.Person..cctor()), calling (MethodDesc 00007ff8741140b8 0 System.Console.ReadLine())
000000c119bfdd10 00007ff8765e6c93 clr!CallDescrWorkerInternal0x83
000000c119bfdd18 00007ff87660a51c clr!ListLockEntry::FinishDeadlockAwareEnter0x40, calling clr!GetThread
000000c119bfdd50 00007ff8765e6b79 clr!CallDescrWorkerWithHandler0x4e, calling clr!CallDescrWorkerInternal
000000c119bfdd80 00007ff87390d663 clrjit0x1d663, calling clrjit0x1be60
000000c119bfdd90 00007ff87660c56b clr!DispatchCallDebuggerWrapper0x1f, calling clr!CallDescrWorkerWithHandler
000000c119bfddf0 00007ff87660c535 clr!DispatchCallSimple0x93, calling clr!DispatchCallDebuggerWrapper
000000c119bfde40 00007ff87660a5b9 clr!MethodTable::EnsureInstanceActive0x110, calling clr!DomainFile::EnsureLoadLevel
000000c119bfde90 00007ff87660bf65 clr!MethodTable::RunClassInitEx0x111, calling clr!DispatchCallSimple
000000c119bfdec0 00007ff88d350119 ntdll!RtlDebugFreeHeap0x2a9, calling ntdll!RtlLeaveCriticalSection
000000c119bfdee0 00007ff88d2b77a2 ntdll!RtlInitializeCriticalSection0xa2, calling ntdll!_security_check_cookie
000000c119bfdf80 00007ff87660a51c clr!ListLockEntry::FinishDeadlockAwareEnter0x40, calling clr!GetThread
000000c119bfdfc0 00007ff87660c15c clr!MethodTable::DoRunClassInitThrowing0x3b9, calling clr!MethodTable::RunClassInitEx
000000c119bfe810 00007ff8765f08b4 clr!ListLockEntry::scalar deleting destructor0xd4, calling clr!operator delete
000000c119bfff10 00007ff88d044034 KERNEL32!BaseThreadInitThunk0x14, calling KERNEL32!guard_dispatch_icall_nop
000000c119bfff40 00007ff88d2c3691 ntdll!RtlUserThreadStart0x21, calling ntdll!guard_dispatch_icall_nop
仔细看上面的代码你会发现有很多处 ListLockEntry这就和锁扯上了关系哈这算证据不面试官小伙子windbg玩的挺溜那请回答一下静态变量是存在哪的有什么证据吗 解析转变思路开始证据先行了????????????。码农犹记得 CLR via C# 中说静态变量是存放在类型对象中这就好办了我去挖一下不就可以了哈其实CLR内部用了两个数据结构来表示 类型对象 和 对象类型一个叫做 EEClass一个叫做 方法表下面我定义一个 lockMe 的静态变量代码如下 class Person{public static object lockMe new object();static Person(){Console.WriteLine(正在处理静态函数);Console.ReadLine();}}
然后祭出杀器 windbg 用 name2ee 找到Person的EEClass将它打出来。
0:000 !name2ee ConsoleApp6.exe!ConsoleApp6.Person
Module: 00007ff816fb4140
Assembly: ConsoleApp6.exe
Token: 0000000002000003
MethodTable: 00007ff816fb5ae8
EEClass: 00007ff816fb2558
Name: ConsoleApp6.Person0:000 !DumpClass /d 00007ff816fb2558
Class Name: ConsoleApp6.Person
mdToken: 0000000002000003
File: C:\dream\Csharp\ConsoleApp1\ConsoleApp6\bin\x64\Debug\ConsoleApp6.exe
Parent Class: 00007ff873f52f68
Module: 00007ff816fb4140
Method Table: 00007ff816fb5ae8
Vtable Slots: 4
Total Method Slots: 6
Class Attributes: 0
Transparency: Critical
NumInstanceFields: 0
NumStaticFields: 1MT Field Offset Type VT Attr Value Name
00007ff873f75dd8 4000001 8 System.Object 0 static 0000020ae5c42d90 lockMe
可以看到最后一行的 lockMe就是那本书中所说的类型对象存储的静态字段。面试官那既然 static 属于类型对象为什么GC不回收它呢 解析开启三连击看你沉浮有多深码农为什么GC不回收它这里我有两个个人观点1 clr的底层机制决定的clr在启动gc组件进行回收前会先在堆中找几类root对象从而开启标记引用链之路常见的root对象有第一个方法的局部变量这个JIT在编译方法的时候最清楚它通过维护一个表给GC参谋。第二个static变量这是天然的root根与AppDomain共存亡。第三个其他乱七八糟的root根。2 static地址是在启动堆而不是在托管堆理应不受GC管控这句话的证据在哪里呢在 C# via CLR 那本书中说JIT开始编译方法内代码的时候会判断当前的类型Pereson是否已经在AppDomain中加载了如果没有很显然会抛异常如果有此类型那就从程序集的元数据中找到该类型的所有描述构建Person的 EEClass数据结构。使用 ILDasm 查看程序集中关于构建EEClass的Person元数据。可以看到确实有 lockMe 的元数据表示有了这些EEClass就可以构建出来然后JIT编译器可以将其分配在加载堆和AppDomain绑定接下来的问题是怎么去看是在加载堆用什么命令去看当然是windbg啦,用 !eeheap -loader 即可。
0:000 !eeheap -loader
Loader Heap:
--------------------------------------
System Domain: 00007ff877002af0
LowFrequencyHeap: 00007ff816f80000(3000:3000) Size: 0x3000 (12288) bytes.
HighFrequencyHeap: 00007ff816f84000(9000:1000) Size: 0x1000 (4096) bytes.
StubHeap: 00007ff816f8d000(3000:2000) Size: 0x2000 (8192) bytes.
Total size: Size: 0xa000 (40960) bytes.
--------------------------------------
Shared Domain: 00007ff877002520
LowFrequencyHeap: 00007ff816f80000(3000:3000) Size: 0x3000 (12288) bytes.
HighFrequencyHeap: 00007ff816f84000(9000:1000) Size: 0x1000 (4096) bytes.
StubHeap: 00007ff816f8d000(3000:2000) Size: 0x2000 (8192) bytes.
Total size: Size: 0xa000 (40960) bytes.
--------------------------------------
Domain 1: 000001246cae21f0
LowFrequencyHeap: 00007ff816f90000(3000:3000) Size: 0x3000 (12288) bytes.
HighFrequencyHeap: 00007ff816f93000(a000:3000) Size: 0x3000 (12288) bytes.
StubHeap: Size: 0x0 (0) bytes.
Total size: Size: 0x6000 (24576) bytes.
--------------------------------------
Total LoaderHeap size: Size: 0x1a000 (106496) bytes.从上图中可以看到C#应用程序会有三个应用程序域 System Domain,Shared Domain, Domain1每一个AppDomain都有自己的私有加载堆我们的 Person 类型不出意外就是在 Domain 1 上了哈如果你好奇可以看看这个AppDomain都有啥。
0:000 !DumpDomain /d 000001246cae21f0
--------------------------------------
Domain 1: 000001246cae21f0
LowFrequencyHeap: 000001246cae29e8
HighFrequencyHeap: 000001246cae2a78
StubHeap: 000001246cae2b08
Stage: OPEN
SecurityDescriptor: 000001246cae4870
Name: ConsoleApp6.exe
Assembly: 000001246cb7f990 [C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader: 000001246cb7fae0
SecurityDescriptor: 000001246cb7e230Module Name
00007ff873f51000 C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dllAssembly: 000001246cb954c0 [C:\dream\Csharp\ConsoleApp1\ConsoleApp6\bin\x64\Debug\ConsoleApp6.exe]
ClassLoader: 000001246cb95610
SecurityDescriptor: 000001246cb933f0Module Name
00007ff816f94140 C:\dream\Csharp\ConsoleApp1\ConsoleApp6\bin\x64\Debug\ConsoleApp6.exe
程序集下就是 Module如你看到的 ConsoleApp6.exe就是一个module哈还可以继续dump module看元数据啥的。总之你让我找到lockme在启动堆上的地址目前还没这个能力不过要知道的是lockMe 引用的object地址是在启动堆上分配而object对象是在托管堆上分配的不要搞混淆了。三后续面试官看了看手表已经快一个小时了此时面试官心里有了答案按照职场潜规则万不可录取不然我的位置往哪搁呢