一个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中的Session,Application,无非就是想用static做一个池化技术和AppDomain级的本地缓存,所以我的应用场景也无非是这些了。

  • 面试官:您会几种实现单例的方式?

        解析:既然面试官想和你扯static,就是想看看你会不会用 static cctor静态构                        造器构建单例!

  • 码农:实不相瞒,不管是用懒汉式还是饿汉式,大体上也就这几种 双检锁static cctorLazy<T>, 不知道您想让我细说哪一种?

  • 面试官:那就说一下静态构造函数为什么可以实现单例?

        解析:可能觉得码农回答的有点拽,问深一点看看是不是唬人的。

  • 码农:说到单例,每一个人都会提到在多线程场景下的并发问题导致多个单例的尴尬,所以有了给代码加上各种花哨的锁,比如刚才我提到的双检索,所以说没有锁。。。这个问题是搞不定的,换句话说 静态构造函数 也是用了锁机制。

  • 面试官:你确定用到了锁?有证据吗?

         解析:有戏了,对你产生感兴趣了,愿听其详。

  • 码农:既然要证据,那我先构思一段如下代码:

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!NtReadFile+0x14:
00007ff8`8d2eaa64 c3              ret
0:000> !dumpstack
OS Thread Id: 0x4ac0 (0)
Current frame: ntdll!NtReadFile+0x14
Child-SP         RetAddr          Caller, Callee
000000c119bfdcd0 00007ff817090957 (MethodDesc 00007ff816f85aa8 +0x37 ConsoleApp6.Person..cctor()), calling (MethodDesc 00007ff8741140b8 +0 System.Console.ReadLine())
000000c119bfdd10 00007ff8765e6c93 clr!CallDescrWorkerInternal+0x83
000000c119bfdd18 00007ff87660a51c clr!ListLockEntry::FinishDeadlockAwareEnter+0x40, calling clr!GetThread
000000c119bfdd50 00007ff8765e6b79 clr!CallDescrWorkerWithHandler+0x4e, calling clr!CallDescrWorkerInternal
000000c119bfdd80 00007ff87390d663 clrjit+0x1d663, calling clrjit+0x1be60
000000c119bfdd90 00007ff87660c56b clr!DispatchCallDebuggerWrapper+0x1f, calling clr!CallDescrWorkerWithHandler
000000c119bfddf0 00007ff87660c535 clr!DispatchCallSimple+0x93, calling clr!DispatchCallDebuggerWrapper
000000c119bfde40 00007ff87660a5b9 clr!MethodTable::EnsureInstanceActive+0x110, calling clr!DomainFile::EnsureLoadLevel
000000c119bfde90 00007ff87660bf65 clr!MethodTable::RunClassInitEx+0x111, calling clr!DispatchCallSimple
000000c119bfdec0 00007ff88d350119 ntdll!RtlDebugFreeHeap+0x2a9, calling ntdll!RtlLeaveCriticalSection
000000c119bfdee0 00007ff88d2b77a2 ntdll!RtlInitializeCriticalSection+0xa2, calling ntdll!_security_check_cookie
000000c119bfdf80 00007ff87660a51c clr!ListLockEntry::FinishDeadlockAwareEnter+0x40, calling clr!GetThread
000000c119bfdfc0 00007ff87660c15c clr!MethodTable::DoRunClassInitThrowing+0x3b9, calling clr!MethodTable::RunClassInitEx
000000c119bfe810 00007ff8765f08b4 clr!ListLockEntry::`scalar deleting destructor'+0xd4, calling clr!operator delete
000000c119bfff10 00007ff88d044034 KERNEL32!BaseThreadInitThunk+0x14, calling KERNEL32!guard_dispatch_icall_nop
000000c119bfff40 00007ff88d2c3691 ntdll!RtlUserThreadStart+0x21, 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对象是在托管堆上分配的,不要搞混淆了。

三:后续

面试官看了看手表,已经快一个小时了,此时面试官心里有了答案,按照职场潜规则,万不可录取,不然我的位置往哪搁呢?

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

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

相关文章

数据结构整理中。。。

目录栈队列链表单向链表双向链表向链表中插入&#xff08;写入&#xff09;数据单向链表单向循环链表双向循环链表从链表中删除数据单向&#xff08;循环&#xff09;链表双向循环链表哈希表哈希函数冲突拉链法闭散列法并查集启发式合并&#xff08;按秩合并&#xff09;带权并…

.NET开发者省份分布排名

什么叫.NET开发者省份分布排名呢&#xff1f; 顾名思义&#xff0c;这几个词大家都认识&#xff0c;.NET开发者都集中在城市&#xff0c;涵盖一线城市到五线城市。排名的方法非常简单粗暴&#xff0c;就是根据本公众号&#xff08;dotnet跨平台&#xff09;的省份订阅读者数量排…

创建型模式——单例模式

一、 实验目的与要求 1.练习使用单例模式。设计相关的模拟场景并进行实施&#xff0c;验证模式特性&#xff0c;掌握其优缺点。 2.实验结束后&#xff0c;对相关内容进行总结。 二、实验内容 1.模式应用场景说明 在山区或者边远地区火车站往往只有一个窗口在买票&#xff0c;但…

Sql Server之旅——终点站 nolock引发的三级事件的一些思考

曾今有件事情让我记忆犹新&#xff0c;那年刚来携程不久&#xff0c;马上就被安排写一个接口&#xff0c;供企鹅公司调用他们员工的差旅信息&#xff0c;然后我就三下五除二的给写好了&#xff0c;上线之后&#xff0c;大概过了一个月。。。DBA那边报告数据库出现大量锁超时&am…

创建型模式——原型模式

一、 实验目的与要求 1.练习使用单一模式。设计相关的模拟场景并进行实施&#xff0c;验证模式特性&#xff0c;掌握其优缺点。 2.实验结束后&#xff0c;对相关内容进行总结。 二、实验内容 1.模式应用场景说明 原型模式&#xff1a;在需要一个类的大量对象的时候&#xff0c;…

现在就是.Net最好的时代!我赞成,谁反对?

2020年.NET Core逆袭冲榜&#xff0c;多榜直接冠军&#xff01;Build2020&#xff0c;发布多款产品赋能.NET开发者&#xff01;截止5月&#xff0c;腾讯&#xff0c;阿里&#xff0c;特斯拉等大厂都在招聘.NET&#xff01;这些征兆&#xff0c;都预示着.NET的春天即将到来&…

如何给Blazor.Server加个API鉴权?

&#xff08;Ant Design of Blazor为努力而生&#xff09;书接上文&#xff0c;上次我们说到了最终选用Blazor.Server来实现了我们的MVP项目&#xff0c;额其实就是博客的增删改查&#xff0c;不过运行还是很爽的&#xff0c;不过是一个小demo&#xff0c;脑子里一直有个声音&a…

结构型模式——桥接模式

一、 实验目的与要求 1.练习使用桥接模式。设计相关的模拟场景并进行实施&#xff0c;验证模式特性&#xff0c;掌握其优缺点。 2.实验结束后&#xff0c;对相关内容进行总结。 二、实验内容 1.模式应用场景说明 相信大家都看过罗老师买奶茶的情节&#xff0c;现实生活中也是一…

TypeScript+vue使用与迁移经验总结

源宝导读&#xff1a;ERP平台的前端底层使用了Vue作为组件的基础架构&#xff0c;而使用了TypeScript语言进行组件的封装与开发。本文将简要介绍平台在使用TypeScript和Vue框架进行老功能重构时的经验总结。一、背景下面主要探讨是以下三个方面&#xff1a;目前项目中使用到的v…

结构型模式——适配器模式

一、 实验目的与要求 1.练习使用适配器模式。设计相关的模拟场景并进行实施&#xff0c;验证模式特性&#xff0c;掌握其优缺点。 2.实验结束后&#xff0c;对相关内容进行总结。 二、实验内容 1.模式应用场景说明 现在喜欢上网的年轻人越来越多&#xff0c;而家里面的电脑满足…

[号外] Blazor wasm 其实也挺快!

之前第一篇的时候&#xff0c;因为没有用任意配置&#xff0c;导致wasm加载很慢&#xff0c;我就感觉不会是这样的&#xff0c;为了不误导小盆友&#xff0c;所以还是趁着周末研究了一波&#xff0c;做了相关的调整&#xff0c;经过测试&#xff0c;速度基本可观了&#xff0c;…

WinUI 3 试玩报告

1. 什么是 WinUI 3#在微软 Build 2020 开发者大会上&#xff0c;WinUI 团队宣布可公开预览的 WinUI 3 Preview 1&#xff0c;它让开发人员可以在 Win32 中使用 WinUI。WinUI 3 Preview 1 包含新的 VisualStudio 项目模板&#xff0c;可以创建面向 .NET 5 的 C# 和 C/Win32 项目…

记一次EF Core连接MySql、Oracle

点击上方“Dotnet9”添加关注哦上上个月写的一篇文章&#xff0c;今天有同事问我使用EF Core连接MySql和Oracel的问题&#xff0c;我把这篇文章直接甩给了他。下面是正文&#xff1a;这几天研究了EF Core对MySql、Oracle的操作&#xff0c;包括连接、简单查询等&#xff0c;操作…

基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(五)

系列文章使用 abp cli 搭建项目给项目瘦身&#xff0c;让它跑起来完善与美化&#xff0c;Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API&#xff0c;包装返回模型再说Swagger&#xff0c;分组、描述、小绿锁接入GitHub&#xff0c;用JWT保护你的API异常处理和…

Codeforces Round #719 (Div. 3)/ Codeforces Round #720 (Div. 2)

A. Do Not Be Distracted! 题意&#xff1a; 一件事情一但开始&#xff0c;只能做完才能做别的事&#xff0c;当出现一件事不连续出现时&#xff0c;教师会怀疑 题目&#xff1a; Polycarp has 26 tasks. Each task is designated by a capital letter of the Latin alphab…

dotNET Core 3.X 使用 Autofac 来增强依赖注入

在上一篇《dotNET Core 3.X 依赖注入》中简单介绍了 dotNET Core 框架本身的依赖注入功能&#xff0c;大部分情况下使用框架的依赖注入功能就可以满足了&#xff0c;在一些特殊场景下&#xff0c;我们就需要引入第三方的注入框架。为什么要使用 Autofac&#xff1f;如果您在之前…

[JS-DOM]DOM概述

DOM&#xff1a; * 概念&#xff1a; Document Object Model 文档对象模型* 将标记语言文档的各个组成部分&#xff0c;封装为对象。可以使用这些对象&#xff0c;对标记语言文档进行CRUD的动态操作* W3C DOM 标准被分为 3 个不同的部分&#xff1a;* 核心 DOM - 针对任何结构…

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(一)

系列文章使用 abp cli 搭建项目给项目瘦身&#xff0c;让它跑起来完善与美化&#xff0c;Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API&#xff0c;包装返回模型再说Swagger&#xff0c;分组、描述、小绿锁接入GitHub&#xff0c;用JWT保护你的API异常处理和…

2021年度训练联盟热身训练赛第一场 H题On Average They‘re Purple(BFS)

题意&#xff1a; 给你一些联通关系&#xff0c;问Bob先选择一些路径&#xff08;1~n&#xff09;联通&#xff0c;Alice在路径上染色&#xff0c;Bob的目的是选择一些路径使得染色变化最小&#xff0c;对于Alice来说&#xff0c;需要使得在Bob选择的&#xff08;1−n1-n1−n&…

使用请求头认证来测试需要授权的 API 接口

使用请求头认证来测试需要授权的 API 接口Intro有一些需要认证授权的接口在写测试用例的时候一般会先获取一个 token&#xff0c;然后再去调用接口&#xff0c;其实这样做的话很不灵活&#xff0c;一方面是存在着一定的安全性问题&#xff0c;获取 token 可能会有一些用户名密码…