首页 科技正文

洛阳市人才网:异常简朴的string驻留池,你对它真的领会吗

admin 科技 2020-04-29 40 0

昨天看群里在讨论C#中的string驻留池,炒的火热,几轮下来理论一堆堆,但是在证据提供上都对照尴尬。虽然这器械很基础,但对照好的回覆也不是那么容易,这篇我就以我能力局限之内跟人人分享一下

一:无处不在的池

开发这么多年,信赖人人对‘池’ 这个观点都耳熟能详了,连接池,线程池,工具池,另有这里的驻留池,池的存在就是为了复用为了共享,独乐乐不如众乐乐,究竟一个字符串的天生和销毁既虚耗空间又虚耗时间,还不如先养着。

1. 说说征象

通常我们臆想中是这么以为的,界说几个字符串变量,堆上就会分配几个string工具,实在这底层有一种叫驻留池手艺可以做到若是两个字符串内容相同,那就在堆上只分配一个string工具,然后将引用地址分配给两个字符串变量,这样就可以大大降低了内存使用,若是用代码示意就是下面这样。


        public static void Main(string[] args)
        {
            var str1 = "nihao";
            var str2 = "nihao";

            var b = string.ReferenceEquals(str1, str2);
            Console.WriteLine(b);
        }

----------- output -----------
True

2. 实现原理

那怎么做到的呢? 实在CLR在运行时挪用JIT把你的MSIL代码转成机械代码的时刻会发现你的元数据中界说了相同内容的字符串工具,CLR就会把你的字符串放入它私有的的内部字典中,其中key就是字符串内容,value就是分配在堆上的字符串引用地址,这个字典就是所谓的驻留池,若是不是很明了,我来画一张图。

3. windbg验证

可以用windbg看一下栈中的str1和str2是否都指向了堆上工具的地址。

~0s -> !clrstack -l 在主线程的线程栈上找到变量str1和str2


0:000> ~0s
ntdll!ZwReadFile+0x14:
00007ff8`fea4aa64 c3              ret
0:000> !clrstack -l
OS Thread Id: 0x1c1c (0)
        Child SP               IP Call Site

000000ac0b7fed00 00007ff889e608e9 *** WARNING: Unable to verify checksum for ConsoleApp2.exe
ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 30]
    LOCALS:
        0x000000ac0b7fed38 = 0x0000024a21f22d48
        0x000000ac0b7fed30 = 0x0000024a21f22d48

000000ac0b7fef48 00007ff8e9396c93 [GCFrame: 000000ac0b7fef48] 

从上面代码的 LOCALS 的 0x000000ac0b7fed38 = 0x0000024a21f22d480x000000ac0b7fed30 = 0x0000024a21f22d48可以看到两个局部变量的引用地址都是 0x0000024a21f22d48,说明指向的都是一个堆工具,接下来再把堆上的内容打出来。


0:000> !do 0x0000024a21f22d48
Name:        System.String
MethodTable: 00007ff8e7a959c0
EEClass:     00007ff8e7a72ec0
Size:        36(0x24) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      nihao
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff8e7a985a0  4000281        8         System.Int32  1 instance                5 m_stringLength
00007ff8e7a96838  4000282        c          System.Char  1 instance               6e m_firstChar
00007ff8e7a959c0  4000286       d8        System.String  0   shared           static Empty
                                 >> Domain:Value  0000024a203d41c0:NotInit  <<

可以看到,果然是System.String工具,这就和我的图是相符的。

二 驻留池的验证

1. String下的驻留池验证方式

很遗憾的是水平有限,由于驻留池既不在堆中也不在栈上,现在还不知道怎么用windbg去打印CLR中驻留池字典内容,不外也可以通过 string.Intern 去验证。

        //
        // Summary:
        //     Retrieves the system's reference to the specified System.String.
        //
        // Parameters:
        //   str:
        //     A string to search for in the intern pool.
        //
        // Returns:
        //     The system's reference to str, if it is interned; otherwise, a new reference
        //     to a string with the value of str.
        //
        // Exceptions:
        //   T:System.ArgumentNullException:
        //     str is null.
        [SecuritySafeCritical]
        public static String Intern(String str);

从注释中可以看到,这个方式的意思就是:若是你界说的str在驻留池中存在,那么就返回驻留池中掷中内容的堆上引用地址,若是不存在,将新字符串插入驻留池中再返回堆上引用,先上一下代码:


        public static void Main(string[] args)
        {
            var str1 = "nihao";
            var str2 = "nihao";

            //验证nihao是否在驻留池中,若是存在那么str3 和 str1,str2一样的引用
            var str3 = string.Intern("nihao");

            //验证新的字符串内容是否进入驻留池中
            var str4 = string.Intern("cnblogs");
            var str5 = string.Intern("cnblogs");

            Console.ReadLine();
        }

接下来划分验证一下str3是否也是和str1和str2一样的引用,以及str5是否存在驻留池中。


ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 37]
    LOCALS:
        0x00000047105fea58 = 0x0000018537312d48
        0x00000047105fea50 = 0x0000018537312d48
        0x00000047105fea48 = 0x0000018537312d48
        0x00000047105fea40 = 0x0000018537312d70
        0x00000047105fea38 = 0x0000018537312d70

从五个变量地址中可以看到,nihao已经被str1,str2,str3共享,cnblogs也进入了驻留池中实现了共享。

2. 运行期相同string是否进入驻留池

这里面有一个坑,前面讨论的相同字符串都是在编译期就知道的,但运行时中的相同字符串是否也会进入驻留池呢? 这是一个让人充满好奇的话题,可以试一下,在程序运行时接受IO输入内容hello,看看是否和str1,str2共享引用地址。


        public static void Main(string[] args)
       {
           var str1 = "nihao";
           var str2 = "nihao";

           var str3 = Console.ReadLine();

           Console.WriteLine("输入完成!");
           Console.ReadLine();
       }

0:000> !clrstack -l
000000f6d35fee50 00007ff889e7090d *** WARNING: Unable to verify checksum for ConsoleApp2.exe
ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 33]
   LOCALS:
       0x000000f6d35fee98 = 0x000002cb1a552d48
       0x000000f6d35fee90 = 0x000002cb1a552d48
       0x000000f6d35fee88 = 0x000002cb1a555f28
0:000> !do 0x000002cb1a555f28
Name:        System.String
MethodTable: 00007ff8e7a959c0
EEClass:     00007ff8e7a72ec0
Size:        36(0x24) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      nihao
Fields:
             MT    Field   Offset                 Type VT     Attr            Value Name
00007ff8e7a985a0  4000281        8         System.Int32  1 instance                5 m_stringLength
00007ff8e7a96838  4000282        c          System.Char  1 instance               6e m_firstChar
00007ff8e7a959c0  4000286       d8        System.String  0   shared           static Empty
                                >> Domain:Value  000002cb18ad39f0:NotInit  <<


从上面内容可以看到,从Console.ReadLine接收到的引用地址是 0x000002cb1a555f28 ,虽然是相同内容,但却没有使用驻留池,这是由于驻留池在JIT静态剖析期就已经剖析完成了,也就无法享受复用之优,若是还想复用的话,在 Console.ReadLine() 包一层 string.Intern即可,如下所示:


        public static void Main(string[] args)
        {
            var str1 = "nihao";
            var str2 = "nihao";

            var str3 = string.Intern(Console.ReadLine());

            Console.WriteLine("输入完成!");
            Console.ReadLine();
        }

ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 33]
    LOCALS:
        0x0000008fac1fe9c8 = 0x000001ff46582d48
        0x0000008fac1fe9c0 = 0x000001ff46582d48
        0x0000008fac1fe9b8 = 0x000001ff46582d48

可以看到这个时刻str1,str2,str3共享一个内存地址 0x000001ff46582d48

四: 总结

驻留池手艺是个很的器械,很好的解决字符串在堆上的重复分配问题,大大减小了堆的内存占用,但也要明了运行期的IO输入无法共享驻留池的解决方案。

好了,本篇就说到这里,希望对你有辅助!

如您有更多问题与我互动,扫描下方进来吧~

,

18sunbet

欢迎进入18sunbet!Sunbet 申博提供申博开户(sunbet开户)、SunbetAPP下载、Sunbet客户端下载、Sunbet代理合作等业务。

版权声明

本文仅代表作者观点,
不代表本站Sunbet的立场。
本文系作者授权发表,未经许可,不得转载。

评论