关于 CLR 的 GC堆,相信大家都知道有 SOH(小对象堆)
和 LOH(大对象堆)
,而且也知道它们的分割线是 85000byte
,当然这是一个默认值,也可以根据具体情况修改,这里要提醒一点的是,LOH 上都是大于 85000byte
的对象吗?这是一个很有意思的问题,具体是不是,可以用 windbg 看一看便知,刚好手里有一个待分析的dump。.
首先用 !eeheap -gc
找到 segment 上的 LOH 逻辑边界。
0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0000016ba0930298
generation 1 starts at 0x0000016ba08e60f0
generation 2 starts at 0x0000016ba0151000
ephemeral segment allocation context: none
segment begin allocated size
0000016ba0150000 0000016ba0151000 0000016ba0cc7a20 0xb76a20(12020256)
Large object heap starts at 0x0000016bb0151000
segment begin allocated size
0000016bb0150000 0000016bb0151000 0000016bb0221bc8 0xd0bc8(854984)
Total Size: Size: 0xc475e8 (12875240) bytes.
------------------------------
GC Heap Size: Size: 0xc475e8 (12875240) bytes.
从输出看,当前 LOH 区间段为 0000016bb0151000 0000016bb0221bc8
,接下来用 !dumpheap
把这个区间段的所有内容给导出来。
0:000> !dumpheap 0000016bb0151000 0000016bb0221bc8
Address MT Size
0000016bb0151000 0000016b9e4d7a70 24 Free
0000016bb0151018 0000016b9e4d7a70 30 Free
0000016bb0151038 00007ffac5185e70 9744
0000016bb0153648 0000016b9e4d7a70 30 Free
0000016bb0153668 00007ffac5185e70 1048
0000016bb0153a80 0000016b9e4d7a70 30 Free
0000016bb0153aa0 00007ffac5185e70 8184
0000016bb0155a98 0000016b9e4d7a70 30 Free
0000016bb0155ab8 00007ffac5185e70 16344
0000016bb0159a90 0000016b9e4d7a70 30 Free
0000016bb0159ab0 00007ffac5185e70 32664
0000016bb0161a48 0000016b9e4d7a70 30 Free
0000016bb0161a68 00007ffac5185e70 2072
0000016bb0162280 0000016b9e4d7a70 30 Free
0000016bb01622a0 00007ffac5185e70 4120
0000016bb01632b8 0000016b9e4d7a70 30 Free
0000016bb01632d8 00007ffac5185e70 65304
0000016bb01731f0 0000016b9e4d7a70 30 Free
0000016bb0173210 00007ffac5185e70 32664
0000016bb017b1a8 0000016b9e4d7a70 30 Free
0000016bb017b1c8 00007ffac5185e70 16408
0000016bb017f1e0 0000016b9e4d7a70 30 Free
0000016bb017f200 00007ffac5196c08 319368
0000016bb01cd188 0000016b9e4d7a70 390 Free
0000016bb01cd310 00007ffac5185e70 8216
0000016bb01cf328 0000016b9e4d7a70 30 Free
0000016bb01cf348 00007ffac5185e70 16344
0000016bb01d3320 0000016b9e4d7a70 191118 Free
0000016bb0201db0 00007ffac5185e70 130584
Statistics:
MT Count TotalSize Class Name
0000016b9e4d7a70 15 191892 Free
00007ffac5196c08 1 319368 System.Int64[]
00007ffac5185e70 13 343696 System.Object[]
Total 29 objects
从 Statistics 列看,当前有 Free
, Int64[]
和 Object[]
三大类对象,下面简要分析下。
-
Free
这个表示 空间块
,简单来说就是 segment 段上对象和对象之间的间隙,这个间隙大概分为两种,要么是被GC标记为Free的废对象,要么是挑选到合适的free块后所剩余下来的空间。
CLR 内部使用一个 FreeList
进行管理,当 alloc 对象到 LOH 时,会优先从 FreeList
上选择 最佳 free 块给对象,所以这里的 free < 85000byte
情有可原。
-
Object[]
有很多小于 85000byte
的 object[],这些数组常用于CLR内部目的,比如你所看到的 static ,string驻留池,缓存的反射信息 等等,它们是不能被 GC 所回收的,那如何实现呢?这就需要用到句柄表,而句柄表的底层又是由 CLR 内部 LargeHeapHandleTable
结构所管理的,接下来随便抽一个。
0:000> !gcroot 0000016bb0153668
HandleTable:
0000016b9e7317e8 (pinned handle)
-> 0000016bb0153668 System.Object[]
Found 1 unique roots (run '!GCRoot -all' to see all roots).
0:000> !mdt -e:2 -count:10 0000016bb0153668
0000016bb0153668 (System.Object[], Elements: 128)
[0] 0000016ba0151420
[1] 0000016ba0152828 "StationDemo"
[2] 0000016ba0152858 "Do not open more"
[3] 0000016ba0152898 "Program"
[4] 0000016ba01528c0 "程序异常:"
[5] 0000016ba01528e8 ",###+ "
[6] 0000016ba0152910 "Program exception:"
[7] 0000016ba0156c98 "Security Exception (ControlAppDomain LinkDemand) while trying to register Shutdown handler with the AppDomain. LoggerManager.Shutdown() will not be called automatically when the AppDomain exits. It must be called programmatically."
[8] 0000016ba0156e80 "log4net.RepositorySelector"
[9] 0000016ba0156ed0 "Exception while resolving RepositorySelector Type ["
expand next 10 items expand all 128 items increase depth
上面这些输出看样子就是一些 驻留池字符串。
接下来的一个问题是还有其他的小于 85000byte
的情况吗?还真有,比如 32bit
下的 double[1000]
就属于大对象,不可思议把,为了验证,来段代码看看吧。
public class Program
{
public static void Main()
{
double[] nums = new double[1000];
for (int i = 0; i < nums.Length; i++)
{
nums[i] = i;
}
Console.WriteLine("添加结束!");
Console.ReadLine();
}
}
接下来用 windbg 看一下。
0:000> !DumpObj /d 039d1020
Name: System.Double[]
MethodTable: 05cb4b08
EEClass: 05cb4aac
Size: 8012(0x1f4c) bytes
Array: Rank 1, Number of elements 1000, Type Double (Print Array)
Fields:
None
0:000> !objsize 039d1020
sizeof(039D1020) = 8012 (0x1f4c) bytes (System.Double[])
0:000> !gcwhere 039d1020
Address Gen Heap segment begin allocated size
039D1020 3 0 039D0000 039D1000 039D2F70 0x1f4c(8012)
可以很清楚的看到,当前的 double[]
大小才 8k 就上了 LOH,有点意思,对吧。