面试题解析
1. 简述一下一个引用对象的生命周期?
- new 创建对象并分配内存
- 对象初始化
- 对象操作、使用
- 资源清理(非托管资源)
- GC 垃圾回收
2. 创建下面对象实例,需要申请多少内存空间?
1 | public class User |
44字节内存空间。
详细信息参考:DotNet基础-GC与内存管理
3. 什么是垃圾?
一个变量如果在其生存期内的某一时刻已经不再被引用,那么,这个对象就有可能成为垃圾
4. GC是什么,简述一下GC的工作方式?
GC 是垃圾回收(Garbage Collect)的缩写,是 .NET 核心机制的重要部分。她的基本工作原理就是遍历托管堆中的对象,标记哪些被使用对象(哪些没人使用的就是所谓的垃圾),然后把可达对象转移到一个连续的地址空间(也叫压缩),其余的所有没用的对象内存被回收掉。
5. GC进行垃圾回收时的主要流程是?
(1)标记:先假设所有对象都是垃圾,根据应用程序根Root遍历堆上的每一个引用对象,生成可达对象图,对于还在使用的对象(可达对象)进行标记(其实就是在对象同步索引块中开启一个标示位)。
(2)清除:针对所有不可达对象进行清除操作,针对普通对象直接回收内存,而对于实现了终结器的对象(实现了析构函数的对象)需要单独回收处理。清除之后,内存就会变得不连续了,就是步骤3的工作了。
(3)压缩:把剩下的对象转移到一个连续的内存,因为这些对象地址变了,还需要把那些 Root 跟指针的地址修改为移动后的新地址。
6. GC在哪些情况下回进行回收工作?
- 内存不足溢出时(0代对象充满时)
- Windwos 报告内存不足时,CLR 会强制执行垃圾回收
- CLR 卸载 AppDomian,GC 回收所有
- 调用 GC.Collect
- 其他情况,如主机拒绝分配内存,物理内存不足,超出短期存活代的存段门限
7. using() 语法是如何确保对象资源被释放的?如果内部出现异常依然会释放资源吗?
using() 只是一种语法形式,其本质还是 try…finally 的结构,可以保证 Dispose 始终会被执行。
8. 解释一下C#里的析构函数?为什么有些编程建议里不推荐使用析构函数呢?
C# 里的析构函数其实就是终结器 Finalize,因为长得像 C++ 里的析构函数而已。
有些编程建议里不推荐使用析构函数要原因在于:第一是 Finalize 本身性能并不好;其次很多人搞不清楚 Finalize 的原理,可能会滥用,导致内存泄露,因此就干脆别用了
9. Finalize() 和 Dispose() 之间的区别?
Finalize() 和 Dispose() 都是 .NET 中提供释放非托管资源的方式,他们的主要区别在于执行者和执行时间不同:
- finalize 由垃圾回收器调用;dispose 由对象调用。
- finalize 无需担心因为没有调用 finalize 而使非托管资源得不到释放,而 dispose 必须手动调用。
- finalize 不能保证立即释放非托管资源,Finalizer 被执行的时间是在对象不再被引用后的某个不确定的时间;而 dispose 一调用便释放非托管资源。
- 只有 class 类型才能重写 finalize ,而结构不能;类和结构都能实现 IDispose 。
另外一个重点区别就是终结器会导致对象复活一次,也就说会被 GC 回收两次才最终完成回收工作,这也是不建议开发人员使用终结器的主要原因。
10. Dispose和Finalize方法在何时被调用?
- Dispose 一调用便释放非托管资源;
- Finalize 不能保证立即释放非托管资源,Finalizer 被执行的时间是在对象不再被引用后的某个不确定的时间;
11. .NET中的托管堆中是否可能出现内存泄露的现象?
是的,可能会。比如:
- 不正确的使用静态字段,导致大量数据无法被GC释放;
- 没有正确执行 Dispose(),非托管资源没有得到释放;
- 不正确的使用终结器 Finalize(),导致无法正常释放资源;
- 其他不正确的引用,导致大量托管对象无法被 GC 释放;
12. 在托管堆上创建新对象有哪几种常见方式?
- new 一个对象;
- 字符串赋值,如 string s1=”abc”;
- 值类型装箱;
参考: