过早的给方法中 引用对象 设为 null 可被 GC提前回收吗?
经常在代码中看到有人将 null 赋值给引用类型,来达到让 GC 提前回收的目的,这样做真的有用吗?今天我们就来研究一下。
为了方便讲解,来一段测试代码,提前将 test1=null ,然后调用 GC.Collect() 看看是否能提前回收。
平台采用: .net5
public class Program
{
static void Main(string[] args)
{
ProcessRequest();
}
static void ProcessRequest()
{
var test1 = new Test() { a = 10 };
Console.WriteLine($"query.a={test1.a}");
var test2 = new Test() { a = 11 };
Console.WriteLine($"query.a={test2.a}");
//提前释放
test1 = null;
var test3 = new Test() { a = 12 };
Console.WriteLine($"query.a={test3.a}");
GC.Collect();
Console.WriteLine("垃圾回收啦!");
Console.ReadLine();
}
}
public class Test
{
public int a;
}
接下来我们从 Debug 和 Release 两种模式下观察。
一:Debug 模式
要找到这个答案,我们用 windbg 附加一下,找到 test1 然后用 !gcroot 查看下引用即可。
0:000> !clrstack -a
OS Thread Id: 0x4dd0 (0)
Child SP IP Call Site
0057F2A4 79863539 System.Console.ReadLine() [/_/src/System.Console/src/System/Console.cs @ 463]
0057F2AC 04c405d1 ConsoleApp1.Program.ProcessRequest() [D:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 37]
LOCALS:
0x0057F2D4 = 0x00000000
0x0057F2D0 = 0x0283cd54
0x0057F2CC = 0x0283cd90
0:000> !dumpheap -type Test
Address MT Size
0283a7c0 04c39008 12
0283cd54 04c39008 12
0283cd90 04c39008 12
0:000> !gcroot 0283a7c0
Thread 4dd0:
0057F2AC 04C405D1 ConsoleApp1.Program.ProcessRequest() [D:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 37]
ebp+14: 0057f2c8
-> 0283A7C0 ConsoleApp1.Test
Found 1 unique roots (run '!gcroot -all' to see all roots).
是不是很惊讶,test1 虽被赋 null,但并没有被 GC.Collection 所回收,原因在于 test1 被栈中的 ebp+14 位置所持有?那这个位置是咋回事? 我们反编译下代码看看,简化后如下:
0:000> !U 04C405D1
Normal JIT generated code
ConsoleApp1.Program.ProcessRequest()
ilAddr is 0268205C pImport is 052FB030
Begin 04C40488, size 154
D:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 22:
04c404aa b90890c304 mov ecx,4C39008h (MT: ConsoleApp1.Test)
04c404af e8182c9afb call 005e30cc (JitHelp: CORINFO_HELP_NEWSFAST)
04c404b4 8945ec mov dword ptr [ebp-14h],eax
04c404b7 8b4dec mov ecx,dword ptr [ebp-14h]
04c404ba ff152890c304 call dword ptr ds:[4C39028h] (ConsoleApp1.Test..ctor(), mdToken: 06000004)
04c404c0 8b4dec mov ecx,dword ptr [ebp-14h]
04c404c3 c741040a000000 mov dword ptr [ecx+4],0Ah
04c404ca 8b4dec mov ecx,dword ptr [ebp-14h]
04c404cd 894df8 mov dword ptr [ebp-8],ecx
D:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 29:
04c4055c 33c9 xor ecx,ecx
04c4055e 894df8 mov dword ptr [ebp-8],ecx
虽然 !gcroot 上显示的是 ebp+14,反向就是 ebp-14,仔细看上面的汇编代码,可以发现 test1 实例被放在了 ebp-14 和 ebp-8 两个栈位置,而 test1=null 只是抹去了 ebp-8 的栈单元,所以它能被回收的时机只能是等 ProcessRequest() 方法销毁之后,这也就是 Debug 模式下的 方法作用域,应该是为了 Debug 调试用的,从 gcinfo 上也可以看出来,ebp-14 是禁止被GC跟踪的内部用途的栈单元。
0:000> !U -gcinfo 04C405D1
Normal JIT generated code
ConsoleApp1.Program.ProcessRequest()
ilAddr is 0268205C pImport is 052FCA58
Begin 04C40488, size 154
D:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 21:
[EBP-08H] an untracked local
[EBP-0CH] an untracked local
[EBP-10H] an untracked local
[EBP-14H] an untracked local
[EBP-18H] an untracked local
[EBP-1CH] an untracked local
[EBP-20H] an untracked local
[EBP-24H] an untracked local
[EBP-28H] an untracked local
[EBP-2CH] an untracked local
[EBP-30H] an untracked local
二:Release 模式
大家或许都知道 Release 是一种高度优化的激进模式,我也很好奇在这种模式下 compile 或者 JIT 会做出怎么样的优化。
1. 编译器层面的优化
要寻找这个答案,我们用 ILSpy 打开生成的 IL代码,简化后如下:
.method private hidebysig static
void ProcessRequest () cil managed
{
// Method begins at RVA 0x2058
// Code size 144 (0x90)
.maxstack 3
.locals init (
[0] class ConsoleApp1.Test test1,
[1] class ConsoleApp1.Test test2,
[2] class ConsoleApp1.Test test3
)
IL_0050: ldnull
IL_0051: stloc.0
} // end of method Program::ProcessRequest
从 idnull 上来看,没有做任何优化,居然直接翻译了,哎。。。
2. JIT优化
查看 JIT 层面的优化,只能看最终的汇编代码 和 托管堆 啦。
0:000> !dumpheap -type Test
Address MT Size
02eaab38 02634b10 12
02ead344 02634b10 12
02ead380 02634b10 12
Statistics:
MT Count TotalSize Class Name
02634b10 3 36 ConsoleApp1.Test
Total 3 objects
0:000> !U /d 0262549d
Normal JIT generated code
ConsoleApp1.Program.ProcessRequest()
ilAddr is 025B2058 pImport is 04AFB108
Begin 02625370, size 131
D:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 22:
02625370 55 push ebp
02625371 8bec mov ebp,esp
0262538a b9104b6302 mov ecx,2634B10h (MT: ConsoleApp1.Test)
0262538f e83cddfefd call 006130d0 (JitHelp: CORINFO_HELP_NEWSFAST)
02625394 8945f0 mov dword ptr [ebp-10h],eax
02625397 8b4df0 mov ecx,dword ptr [ebp-10h]
0262539a e871f9ffff call 02624d10
0262539f 8b4df0 mov ecx,dword ptr [ebp-10h]
026253a2 c741040a000000 mov dword ptr [ecx+4],0Ah
026253a9 8b4df0 mov ecx,dword ptr [ebp-10h]
026253ac 894dfc mov dword ptr [ebp-4],ecx
D:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 29:
02625430 33c9 xor ecx,ecx
02625432 894dfc mov dword ptr [ebp-4],ecx
从汇编代码看,Release 模式下也是采用双栈保存的,也就是 方法级作用域。
二:可以得出结论了吗?
至少在 .NET5 平台, Release 和 Debug 模式下的 test1 = null; 是没有任何区别的,其实这里有个问题 , .NET5 下没区别,不代表其他平台下也没有问题,毕竟不同的 JIT 会作用不同的抉择,接下来我们将同样的代码搬到 .NET Framework 4.5 下看看情况。
1. .NET Framework 4.5 平台
- Debug 模式
我们直接看托管代码
0:006> !dumpheap -type Test
Address MT Size
02564bfc 00754ddc 12
02564c70 00754ddc 12
Statistics:
MT Count TotalSize Class Name
00754ddc 2 24 ConsoleApp2.Test
Total 2 objects
居然是 2 个了,那为什么会这样呢? 我们还是看下汇编。
0:000> !U /d 023509a6
Normal JIT generated code
ConsoleApp2.Program.ProcessRequest()
Begin 02350880, size 187
D:\net5\ConsoleApp2\ConsoleApp2\Program.cs @ 21:
023508b1 b9dc4da200 mov ecx,0A24DDCh (MT: ConsoleApp2.Test)
023508b6 e839286cfe call 00a130f4 (JitHelp: CORINFO_HELP_NEWSFAST)
023508bb 8945ec mov dword ptr [ebp-14h],eax
023508be 8b4dec mov ecx,dword ptr [ebp-14h]
023508c1 ff15fc4da200 call dword ptr ds:[0A24DFCh] (ConsoleApp2.Test..ctor(), mdToken: 06000004)
023508c7 8b45ec mov eax,dword ptr [ebp-14h]
023508ca c740040a000000 mov dword ptr [eax+4],0Ah
023508d1 8b45ec mov eax,dword ptr [ebp-14h]
023508d4 8945f8 mov dword ptr [ebp-8],eax
D:\net5\ConsoleApp2\ConsoleApp2\Program.cs @ 28:
0235097b 33d2 xor edx,edx
0235097d 8955f8 mov dword ptr [ebp-8],edx
0:000> dp ebp-14h L1
0019f4e8 02472358
0:000> !do 02472358
Name: ConsoleApp2.Test
MethodTable: 00a24ddc
EEClass: 00a21330
Size: 12(0xc) bytes
File: D:\net5\ConsoleApp2\ConsoleApp2\bin\Debug\ConsoleApp2.exe
Fields:
MT Field Offset Type VT Attr Value Name
637342a8 4000001 4 System.Int32 1 instance 10 a
0:000> dp 0019f4e8 L1
0019f4e8 02472358
0:000> !do 02472358
Free Object
Size: 24(0x18) bytes
大家可以仔细看看输出内容,虽然也是两个 栈位置 存放着 test1,但GC做了不同的处理,它无视 ebp-14 还牵引着 test1 的事实 ,直接将它标记为 free,这就有点意思了。
- Release 模式
我们直接用 !dumpheap -type Test 看托管堆。
0:006> !dumpheap -type Test
Address MT Size
Statistics:
MT Count TotalSize Class Name
Total 0 objects
居然发现,不仅 test1 没有了,test2,test3 都没有了。。。这就是所谓的 激进式回收。
三:结论
1. .NET5 平台下
Release 和 Debug 模式下设置 test1=null 没有任何效果。
2. .NET Framework 4.5 平台下
Debug 模式下有效果,可以起到 提前回收 的目的。
Release模式下无效果,GC会自动激进的回收所有后续未使用到的引用对象。
3. 个人结论
总的来说,为了更好的平台兼容性,如果想提前回收,设置 test1 = null; 是有一定效果的。

过早的给方法中 引用对象 设为 null 可被 GC提前回收吗?的更多相关文章
- Powermockito 针对方法中new 对象的模拟,以及属性中new 对象的模拟
PowerMocker 是一个功能逆天的mock 工具. 一,Powermockito 针对方法中new 对象的模拟 // 如何才能mock掉 WeChatConfigUtil 这个类,让 weCha ...
- 在JVM中,新生代和旧生代有何区别?GC的回收方式有几种?server和client有和区别?
在JVM中,新生代和旧生代有何区别?GC的回收方式有几种?server和client有和区别? 2014-04-12 12:09 7226人阅读 评论(0) 收藏 举报 分类: J2SE(5) 一 ...
- java方法中把对象置null,到底能不能加速垃圾回收
今天逛脉脉,看见匿名区有人说java中把对做置null,这种做法很菜,不能加速垃圾回收,但是我看到就觉得呵呵了,我是觉得可以加速置null对象回收的. 测试的过程中,费劲的是要指定一个合理的测试堆大小 ...
- 浅谈String中的==和对象中引用对象类型的==
@Test public void test02() { StringBuffer sb = new StringBuffer(); sb.append('a'); sb.append(11); Sy ...
- 用powermock 方法中new对象
在单元测试中有时需要对方法体内new出来的对象进行方法隔离,powermock提供了这个功能,下面是一个段样例代码: UserBean user = mock(UserBean.class, RETU ...
- ①创建项目testpackage ②在pack2.B中添加方法f ③在类A中添加如下三个成员变量:int型的私有变量i float型的变量f double型的公有变量d 在pack1.B的main方法中为对象a的成员变量f和d分别赋值为2和3 在pack2.C的main方法中为对象a的成员变量d赋值为3
package pack1; public class A { private int i; float f; public double d; public float getF() { retur ...
- Set重写hashCode和equals方法实现引用对象去重
运作原理: 首先判断hashCode是否相同,如果不同,直接判定为两个不同的对象.如果hashCode相同,再去比较equals是否一样,如果一样,则为同一个对象.如果不一样,则是两个不同对象. 那么 ...
- JVM 中的对象及引用
JVM中对象的创建过程 对象的内存分配 虚拟机遇到一条 new 指令时,首先检查是否被类加载器加载,如果没有,那必须先执行相应的类加载过程. 类加载就是把 class 加载到 JVM 的运行时数据区的 ...
- Andorid Binder进程间通信---Binder本地对象,实体对象,引用对象,代理对象的引用计数
本文參考<Android系统源码情景分析>,作者罗升阳. 一.Binder库(libbinder)代码: ~/Android/frameworks/base/libs/binder --- ...
随机推荐
- Spring根据路径前缀获取不同Resource
相关文章:https://www.jianshu.com/p/5bab9e03ab92 官方文档:https://docs.spring.io/spring/docs/current/spring-f ...
- vue中的全局组件和局部组件的应用
1全局组件 vue框架: https://www.vue-js.com/?tab=all 组件树 solt 内置分发组件 应用见局部组件 2局部组件 2.1局部组件的使用:生子 - 挂子 - 用子 & ...
- 学习openstack(二)
OpenStack企业私有云实践 1. 实验架构 OpenStack我们把它当作是一个全新的"Linux"系统来学习,有的时候我们也把OpenStack称之为一个新的Lin ...
- idea中Git的配置和Github上推拉项目
1.去官网下载Git,并且安装它.安装步骤较为简单,此处就不再赘述. 2.在idea中配置Git信息 Git路径就是你电脑中安装Git的位置,找到git.exe文件(正常情况下,idea都会自动给你匹 ...
- ACM - 最短路 - CodeForces 295B Greg and Graph
CodeForces 295B Greg and Graph 题解 \(Floyd\) 算法是一种基于动态规划的算法,以此题为例介绍最短路算法中的 \(Floyd\) 算法. 我们考虑给定一个图,要找 ...
- 实用的 CSS — 贝塞尔曲线(cubic-bezier)
欢迎移步我的博客阅读:<实用的 CSS - 贝塞尔曲线(cubic-bezier)> 前言 在了解 cubic-bezier 之前,你需要对 CSS3 中的动画效果有所认识,它是 anim ...
- Android 遮罩层效果--制作圆形头像
(用别人的代码进行分析) 不知道在开发中有没有经常使用到这种效果,所谓的遮罩层就是给一张图片不是我们想要的形状,这个时候我们就可以使用遮罩效果把这个图片变成我们想要的形状,一般使用最多就是圆形的效果, ...
- 解决 Tomcat 控制台输出乱码(Tomcat Localhost Log / Tomcat Catalina Log 乱码)
1. 按下图修改 先找到你的 Tomcat 安装目录,然后进入conf文件夹,找到 logging.properties,并打开它,然后把所有 UTF-8 格式的编码改成 GBK即可,具体操作如下图
- IE zoom
zoom是IE浏览器特有的属性,它可以设置或检索对象的缩放比例(它的中文解释是:放大),它的作用通常可以概括为三个方面: 1.hasLayout 2.清除浮动 3.清除div的垂直外边距合并问题 什么 ...
- 什么是3D建模?
一.3D建模是什么 将所见所想用立体三维的方式通过计算机技术表现出来,这就是3D建模,比如你看到了一个可爱呆萌的卡通人物又或是华丽酷炫的变形金刚,总之就是你认为美好的立体实物 你想将它用计算机技术完美 ...