昨天看群里在讨论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

四: 总结

驻留池技术是个很

非常简单的string驻留池,你对它真的了解吗的更多相关文章

  1. C#中的string驻留池

    刚开始学习C#的时候,就听说CLR对于String类有一种特别的内存管理机制:有时候,明明声明了两个String类的对象,但是他们偏偏却指向同一个实例.如下: String s1 = "He ...

  2. Java中String类型的不可变性和驻留池

    一 基本概念 可变类和不可变类(Mutable and Immutable Objects)的初步定义: 可变类:当获得这个类的一个实例引用时,可以改变这个实例的内容. 不可变类:不可变类的实例一但创 ...

  3. String特性之 “字符串驻留池”

    1. 字符串驻留池,就是一块与堆区并行的存放字符串对象的内存区,JVM的驻留池机制规定: 在池中创建一个String对象,第二行会先在池中寻找是否有值与"abc"相同的String ...

  4. JAVA 字符串驻留池

    一切从String str = new String("abc")说起...    这行代码形式上很简单,其实很复杂.有一个常见的Java笔试题就是问上面这行代码创建了几个Stri ...

  5. string 驻留机制

    string 驻留机制   string s1 = "abc"; string s2 = "ab"; string s3 = s2 + "c" ...

  6. 一个简单的MySql数据库连接池的实现

    package cn.hc.connectionPool; import java.io.IOException; import java.io.InputStream; import java.sq ...

  7. .Net字符串驻留池

    在.Net中,对于相同的字符串,.Net会将它们指向同一个地址,它们是相同的实例..Net中的字符串并不会更新,当更改一个字符串变量时,由于字符串的不可变性,.Net实际上是新创建一个字符串,而将变量 ...

  8. C# 字符串驻留池

    在.Net中,对于相同的字符串,.Net会将它们指向同一个地址,它们是相同的实例..Net中的字符串并不会更新,当更改一个字符串变量时,由于字符串的不可变性,.Net实际上是新创建一个字符串,而将变量 ...

  9. java基础进阶一:String源码和String常量池

    作者:NiceCui 本文谢绝转载,如需转载需征得作者本人同意,谢谢. 本文链接:http://www.cnblogs.com/NiceCui/p/8046564.html 邮箱:moyi@moyib ...

随机推荐

  1. Python python 五种数据类型--字典

    # 定义一个字典 var1 = {'a':20,'b':40}; var2 = dict(); print(type(var1)) print(type(var2)) # 长度 length = le ...

  2. [noip模拟]难缠的值周生<宽搜>

    难缠的值周生 [问题描述] 小 P 上学总是迟到,迟到了以后常常会被值周生发现.被值周生发现就会给他所在的班级扣分,被扣了分不免要挨班主任的训,这令小 P 很不爽.不过,聪明的他经过观察发现,值周生通 ...

  3. CodeForces 687A NP-Hard Problem

    Portal:http://codeforces.com/problemset/problem/687/A 二分图染色 好模板题 有SPJ 值得注意的是,因为C++的奇妙的运算机制 若在vector变 ...

  4. Python3安装Crypto加密包

    Python3安装Crypto加密包 下载链接 加密包地址 步骤 下载加密包,解压加密包到Python安装目录下Lib\site-packages目录中,尝试在Pycharm中导入 from Cryp ...

  5. B. The Monster and the Squirrel

    B. The Monster and the Squirrel Ari the monster always wakes up very early with the first ray of the ...

  6. Java 程序该怎么优化?(命令篇)

    灵魂拷问,JDK 提供的命令,除了 java.javac,你还用过哪些命令呢? 灵魂再拷问,若你写的 Java 程序,出现了性能问题,该怎么去排查呢? Java 作为编程语言中的战斗机,JDK 默认已 ...

  7. Light of future-冲刺Day 3

    目录 1.SCRUM部分: 每个成员进度 SCRUM 会议的照片 签入记录 代码运行截图 发布界面 用户浏览界面 管理员浏览界面 2.PM 报告: 时间表 燃尽图 任务总量变化曲线 每名成员的贡献比 ...

  8. 1068 Find More Coins (30分)(dp)

    Eva loves to collect coins from all over the universe, including some other planets like Mars. One d ...

  9. 一篇blog带你了解java中的锁

    前言 最近在复习锁这一块,对java中的锁进行整理,本文介绍各种锁,希望给大家带来帮助. Java的锁 乐观锁 乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人 ...

  10. 面试官再问你 HashMap 底层原理,就把这篇文章甩给他看

    前言 HashMap 源码和底层原理在现在面试中是必问的.因此,我们非常有必要搞清楚它的底层实现和思想,才能在面试中对答如流,跟面试官大战三百回合.文章较长,介绍了很多原理性的问题,希望对你有所帮助~ ...