Session,Session,Session!(请耐心阅读………………)
地球人都知道,asp.net中有三种方式存放我们的session objects。In Proc模式,在cache中存放对象。StateServer在State Service中存放,最后一种是存放在SQL Server中。对于In Proc模式,太多的session对象,意味着高内存占用;对于后两者,意味着序列化和反序列化的性能损失。
Session存放的东西太多,不一定意味着性能的问题,但这依赖于你往session里面存放的东西。
让我们假设一个场景,你开发了一个网上商店,有很少的人在用,所以你只用了一台web server,使用了In Proc模式,你的程序对每个用户的订单,都存放了一些dataset。
突然,你的程序出名了!一个大公司来找你,于是,你增加了N台web server,同时把Session State也修改成了SQL Server模式。
问题来了!
asp.net进程,内存占用很好(800MB – 1GB),有时候会提示OutOfMemory异常,或者提示.net进程被recycled了。dump的大小为1,473,913 bytes,所以,大概就是1.4G。首先,我们看看托管堆上面都有些啥东西?
0:023> !eeheap -gc
Number of GC Heaps: 2
------------------------------
Heap 0 (0x000b7198)
generation 0 starts at 0x022104d4
generation 1 starts at 0x022037c0
generation 2 starts at 0x02170030
ephemeral segment allocation context: none
segment begin allocated size
0x2170000 0x2170030 0x224a4e0 0xda4b0(894,128)
Large object heap starts at 0x0a170030
segment begin allocated size
0x0a170000 0x0a170030 0x0acf0b20 0x00b80af0(12,061,424)
0x0d490000 0x0d490030 0x0e3d2450 0x00f42420(16,000,032)
0x12010000 0x12010030 0x12f52460 0x00f42430(16,000,048)
0x13010000 0x13010030 0x13f52460 0x00f42430(16,000,048)
0x15010000 0x15010030 0x15f52460 0x00f42430(16,000,048)
0x1a010000 0x1a010030 0x1af52460 0x00f42430(16,000,048)
…
0x71ca0000 0x71ca0030 0x72be2470 0x00f42440(16,000,064)
0x748b0000 0x748b0030 0x757f2470 0x00f42440(16,000,064)
0x7d0e0000 0x7d0e0030 0x7d881250 0x007a1220(8,000,032)
Heap Size 0x2d5b4e10(760,958,480)
------------------------------
Heap 1 (0x000ede88)
generation 0 starts at 0x06249b58
generation 1 starts at 0x0623e190
generation 2 starts at 0x06170030
ephemeral segment allocation context: none
segment begin allocated size
0x6170000 0x6170030 0x6283b64 0x113b34(1,129,268)
Large object heap starts at 0x0b170030
segment begin allocated size
0x0b170000 0x0b170030 0x0b9f1c90 0x00881c60(8,920,160)
0x0c3e0000 0x0c3e0030 0x0d322460 0x00f42430(16,000,048)
0x0e490000 0x0e490030 0x0f3d2460 0x00f42430(16,000,048)
0x11010000 0x11010030 0x11f52460 0x00f42430(16,000,048)
0x14010000 0x14010030 0x14f52450 0x00f42420(16,000,032)
0x16010000 0x16010030 0x16f52480 0x00f42450(16,000,080)
0x17010000 0x17010030 0x17b81b60 0x00b71b30(12,000,048)
0x18010000 0x18010030 0x18b81b60 0x00b71b30(12,000,048)
0x19010000 0x19010030 0x19f52460 0x00f42430(16,000,048)
0x1b010000 0x1b010030 0x1bf52460 0x00f42430(16,000,048)
…
0x61010000 0x61010030 0x61f52470 0x00f42440(16,000,064)
0x62db0000 0x62db0030 0x63cf2470 0x00f42440(16,000,064)
0x657e0000 0x657e0030 0x66722470 0x00f42440(16,000,064)
0x685c0000 0x685c0030 0x69502470 0x00f42440(16,000,064)
0x6e110000 0x6e110030 0x6ec81b70 0x00b71b40(12,000,064)
0x72ca0000 0x72ca0030 0x73be2470 0x00f42440(16,000,064)
0x77ff0000 0x77ff0030 0x78f32470 0x00f42440(16,000,064)
0x7e0e0000 0x7e0e0030 0x7f022470 0x00f42440(16,000,064)
Heap Size 0x286a4124(678,052,132)
------------------------------
GC Heap Size 0x55c58f34(1,439,010,612)
!eeheap –gc告诉我们两个事情:
a) the GC Heap大小在1.4G左右,和上面的总内存占用非常接近。这说明大部分内存占用,都在托管堆上。
b) 大部分内存占用都在LargeObjects上面(>85000bytes的东西)
很自然的,我们要看看在heap里面的LO到底都是啥东西?
0:023> !dumpheap -min 85000 -stat
Using our cache to search the heap.
Statistics:
MT Count TotalSize Class Name
0x000eda20 1 920,144 Free
0x01b2209c 33 132,000,528 System.Object[]
0x01b226b0 163 1,304,001,956 System.Int32[]
Total 197 objects, Total size: 1,436,922,628
大部分内存被Int32数组占用了,还有132M被一个对象数组占用了。注意啊!这里的132M和那个1.3G,都只是object本身的大小,而不包括存储在Object数组里面的对象的大小。如果要看详细情况,我们需要跑一下!objsize才可以。
0:023> !dumpheap -min 85000
Using our cache to search the heap.
Address MT Size Gen
0x0b170030 0x000eda20 920,144 -1 Free
0x0a920210 0x01b2209c 4,000,016 -1 System.Object[]
0x247b1250 0x01b2209c 4,000,016 -1 System.Object[]
0x277b1250 0x01b2209c 4,000,016 -1 System.Object[]
0x287b1250 0x01b2209c 4,000,016 -1 System.Object[]
…
0x51af0030 0x01b226b0 8,000,012 -1 System.Int32[]
0x52291250 0x01b226b0 8,000,012 -1 System.Int32[]
0x53b40950 0x01b226b0 8,000,012 -1 System.Int32[]
0x56620030 0x01b226b0 8,000,012 -1 System.Int32[]
0x57620030 0x01b226b0 8,000,012 -1 System.Int32[]
0x5a440030 0x01b226b0 8,000,012 -1 System.Int32[]
0x5abe1250 0x01b226b0 8,000,012 -1 System.Int32[]
0x5c660030 0x01b226b0 8,000,012 -1 System.Int32[]
0x5ce01250 0x01b226b0 8,000,012 -1 System.Int32[]
0x61010030 0x01b226b0 8,000,012 -1 System.Int32[]
0x617b1250 0x01b226b0 8,000,012 -1 System.Int32[]
0x62db0030 0x01b226b0 8,000,012 -1 System.Int32[]
0x63551250 0x01b226b0 8,000,012 -1 System.Int32[]
0x657e0030 0x01b226b0 8,000,012 -1 System.Int32[]
0x65f81250 0x01b226b0 8,000,012 -1 System.Int32[]
注意Gen这一列,这些object都没有被GC,为啥?随便找一个,看看吧!
0:023> !gcroot 0x72ca0030
Scan Thread 16 (0xbd8)
ESP:1a3f5e0:Root:0x6280d38(System.Web.HttpContext)->
0x62809ec(System.Web.Hosting.ISAPIWorkerRequestInProcForIIS6)->
0x61b7030(System.Web.HttpWorkerRequest/EndOfSendNotification)->
0x61b47d4(System.Web.HttpRuntime)->
0x61b4ca0(System.Web.Caching.CacheMultiple)->0x61b4cc4(System.Object[])->
0x61b4cdc(System.Web.Caching.CacheSingle)->
0x61b4dac(System.Web.Caching.CacheExpires)->0x61b4ff8(System.Object[])->
0x61b5ab8(System.Web.Caching.ExpiresBucket)->
0x222e63c(System.Web.Caching.ExpiresEntry[])->
0x220292c(System.Web.Caching.CacheEntry)->
0x22028fc(System.Web.SessionState.InProcSessionState)->
0x2202690(System.Web.SessionState.SessionDictionary)->
0x220273c(System.Collections.Hashtable)->
0x2202770(System.Collections.Hashtable/bucket[])->
0x2202810(System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)->
0x72ca0030(System.Int32[])
Scan Thread 20 (0x89c)
Scan Thread 22 (0xa5c)
Scan HandleTable 0xdc9d8
Scan HandleTable 0xea6e8
Scan HandleTable 0x1531e8
看这条链,我们发现Int32被Cache root了(看GC的原理就知道这句话啥意思了),看上面的InProcSessionState,我们知道现在是InProc模式。
继续看……
0:023> !dumpaspnetcache -stat
Going to dump the ASP.NET Cache.
MT Count TotalSize Class Name
0x0211cc9c 1 20 System.Web.Security.FileSecurityDescriptorWrapper
0x020c242c 2 56 System.Web.UI.ParserCacheItem
0x0206c66c 5 260 System.Web.Configuration.HttpConfigurationRecord
0x0c2e7014 1 316 System.Web.Mobile.MobileCapabilities
0x79b94638 4 376 System.String
0x0c2eaeb4 151 7,248 System.Web.SessionState.InProcSessionState
Total 164 objects, Total size: 8,276
好,我们找到了151个Session对象。
0:023> .foreach (obj {!dumpheap -mt 0x0c2eaeb4 -short}){!objsize ${obj}}
sizeof(0x22028fc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2202a10) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2202cfc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2202fe8) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x22032d4) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x22035c0) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2203a38) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2203d24) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2204010) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
…
sizeof(0x6248080) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6248334) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x62485e8) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x624a84c) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x624d7b8) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6250724) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6253690) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x62565fc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6259568) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x625c4d4) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x625f440) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x62623ac) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6265318) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6268284) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x626b1b8) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
看到了!151个session对象,每个拿到了8-12M左右的数据。就是这个鸟玩意搞的内存有问题啦!
随便找一个,看看什么在里面???
0:023> !do 0x626b1b8
Name: System.Web.SessionState.InProcSessionState
MethodTable 0x0c2eaeb4
EEClass 0x0c1c5660
Size 48(0x30) bytes
GC Generation: 0
mdToken: 0x02000132 (c:\windows\assembly\gac\system.web\1.0.5000.0__b03f5f7f11d50a3a\system.web.dll)
FieldDesc*: 0x0c2eae0c
MT Field Offset Type Attr Value Name
0x0c2eaeb4 0x40009f2 0x4 CLASS instance 0x06269f74 dict
0x0c2eaeb4 0x40009f3 0x8 CLASS instance 0x00000000 staticObjects
0x0c2eaeb4 0x40009f4 0xc System.Int32 instance 20 timeout
0x0c2eaeb4 0x40009f5 0x18 System.Boolean instance 0 isCookieless
0x0c2eaeb4 0x40009f6 0x10 System.Int32 instance 0 streamLength
0x0c2eaeb4 0x40009f7 0x19 System.Boolean instance 0 locked
0x0c2eaeb4 0x40009f8 0x1c VALUETYPE instance start at 0x0626b1d4 utcLockDate
0x0c2eaeb4 0x40009f9 0x14 System.Int32 instance 1 lockCookie
0x0c2eaeb4 0x40009fa 0x24 VALUETYPE instance start at 0x0626b1dc spinLock
每个InProcSessionState对象都有一个叫做_entriesArray的dict成员,装有实际的session对象列表。就是上面偏移为0x04的那行。
0:023> !do 0x06269f74
Name: System.Web.SessionState.SessionDictionary
MethodTable 0x0c2e0c54
EEClass 0x0c1c1308
Size 44(0x2c) bytes
GC Generation: 0
mdToken: 0x0200013b (c:\windows\assembly\gac\system.web\1.0.5000.0__b03f5f7f11d50a3a\system.web.dll)
FieldDesc*: 0x0c2e0b30
MT Field Offset Type Attr Value Name
0x0206b338 0x4000a8b 0x24 System.Boolean instance 0 _readOnly
0x0206b338 0x4000a8c 0x4 CLASS instance 0x06269fb8 _entriesArray
0x0206b338 0x4000a8d 0x8 CLASS instance 0x06269fa0 _hashProvider
0x0206b338 0x4000a8e 0xc CLASS instance 0x06269fac _comparer
0x0206b338 0x4000a8f 0x10 CLASS instance 0x0626a020 _entriesTable
0x0206b338 0x4000a90 0x14 CLASS instance 0x00000000 _nullKeyEntry
0x0206b338 0x4000a91 0x18 CLASS instance 0x00000000 _keys
0x0206b338 0x4000a92 0x1c CLASS instance 0x00000000 _serializationInfo
0x0206b338 0x4000a93 0x20 System.Int32 instance 4 _version
0x0c2e0c54 0x4000a0f 0x25 System.Boolean instance 1 _dirty
0x0c2e0c54 0x4000a0e 0 CLASS shared static s_immutableTypes
>> Domain:Value 0x000dad08:NotInit 0x00104f30:0x021be0dc <<
同样,还是看_entriesArray,偏移为0x04的那行
0:023> !do 0x06269fb8
Name: System.Collections.ArrayList
MethodTable 0x79ba2ee4
EEClass 0x79ba3020
Size 24(0x18) bytes
GC Generation: 0
mdToken: 0x02000100 (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)
FieldDesc*: 0x79ba3084
MT Field Offset Type Attr Value Name
0x79ba2ee4 0x4000362 0x4 CLASS instance 0x06269fd0 _items
0x79ba2ee4 0x4000363 0xc System.Int32 instance 3 _size
0x79ba2ee4 0x4000364 0x10 System.Int32 instance 3 _version
0x79ba2ee4 0x4000365 0x8 CLASS instance 0x00000000 _syncRoot
哦,Name是ArrayList,好,我们继续看0x04对应的那个_items吧!
0:023> !do -v 0x06269fd0
Name: System.Object[]
MethodTable 0x01b2209c
EEClass 0x01b22018
Size 80(0x50) bytes
GC Generation: 0
Array: Rank 1, Type CLASS
Element Type: System.Object
Content: 16 items
------ Will only dump out valid managed objects ----
Address MT Class Name
0x0626a81c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
0x0626a82c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
0x0626a83c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
----------
对于每个对象,我们打印出0x04位置的Name和0x08位置的Size。
0:023> .foreach (obj {!do -v 0x06269fd0 -short}){.echo ***;!do poi(${obj}+0x4);!do poi(${obj}+0x8);!objsize ${obj}}
***
String: somestring
String: this is a string i stored in session scope
sizeof(0x626a81c) = 160 ( 0xa0) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
***
String: alargeintarray
Name: System.Int32[]
MethodTable 0x01b226b0
EEClass 0x01b22638
Size 8000012(0x7a120c) bytes
GC Generation: 3
Array: Rank 1, Type System.Int32
Element Type: System.Int32
Content: 2,000,000 items
sizeof(0x626a82c) = 8,000,076 (0x7a124c) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
***
String: sometimesbig
Name: System.Object[]
MethodTable 0x01b2209c
EEClass 0x01b22018
Size 4000016(0x3d0910) bytes
GC Generation: 3
Array: Rank 1, Type CLASS
Element Type: System.Object
Content: 1,000,000 items
sizeof(0x626a83c) = 4,000,076 (0x3d094c) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
到这里,我们看到,一个很小的字符串:this is a string i stored in session scope。在Session的 somestring里面。然后是在Session的alargeintarray有一个8M的Int32数组,最后,是一个4M的sometimesbig在session的sometimesbig。
下面如何解决?就看我们的业务逻辑和代码实现啦!和偶无关了,嗬嗬!