昨天看群里在讨论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. Java构造器(构造方法/constructor)

    我们先来看一下什么是构造器: 1.构造器也叫构造方法或构造函数,分为有参构造器和无参构造器: 2.构造器也是一种方法,只不过是一种特殊的方法,它会在对象创建的时候被调用: 3.构造器最大的作用就是在创 ...

  2. 2020.4.4号全国疫情哀悼日网页变灰色前端是如何实现的?-pink老师

    今天是4.4疫情哀悼日,纪念疫情期间牺牲的烈士和逝世同胞,因此大部分网站颜色都变灰色了,我们前端是如何实现的呢? 核心原理,使用css3的滤镜效果即可,filter grayscale 将整个界面变为 ...

  3. SpringBoot学习笔记(十一:使用MongoDB存储文件 )

    @ 目录 一.MongoDB存储文件 1.MongoDB存储小文件 2.MongoDB存储大文件 2.1.GridFS存储原理 2.2.GridFS使用 2.2.1.使用shell命令 2.2.2.使 ...

  4. CentOS6.10下安装mysql-5.7.24

    卸载原有mysql 因为mysql数据库在Linux上实在是太流行了 所以目前下载的主流Linux系统版本基本上都集成了mysql数据库在里面 我们可以通过如下命令来查看我们的操作系统上是否已经安装了 ...

  5. 不可被忽视的操作系统( FreeRTOS )【2】

    本文章总结基于官方FreeRTOS手册,测试系统为ESP32的IDF 4.0 本篇续上一篇<不可被忽视的操作系统( FreeRTOS )[1]> 其中上一篇主要内容为: FreeRTOS介 ...

  6. 让你第一次认识到Java的内存管理

    发现之前写的可读性不好,这次准备试试换风格,去掉长篇大论,觉得这个风格好的,麻烦点个赞啦 清理.JVM的妙处 大家以后都是程序员,假设你很不幸,需要自己交钱租房子. 你作为一个小穷人,租的房子到期了( ...

  7. 操作系统-1-存储管理之LFU页面置换算法(leetcode460)

    LFU缓存 题目:请你为 最不经常使用(LFU)缓存算法设计并实现数据结构.它应该支持以下操作:get 和 put. get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1 ...

  8. 监听窗口大小变化,改变画面大小-[Three.js]-[onResize]

    如果没有监听窗口变化,将会出现一下情况: ![](https://img2018.cnblogs.com/blog/1735896/202001/1735896-20200102081845027-2 ...

  9. stylus--安装及使用方法

    stylus介绍 Stylus 是一个CSS的预处理框架,2010年产生,来自Node.js社区,主要用来给Node项目进行CSS预处理支持,所以 Stylus 是一种新型语言,可以创建健壮的.动态的 ...

  10. Scheme语言实例入门--怎样写一个“新型冠状病毒感染风险检测程序”

    小学生都能用的编程语言 2020的春季中小学受疫情影响,一直还没有开学,孩子宅在家说想做一个学校要求的研究项目,我就说你做一个怎么样通过编程来学习数学的小项目吧,用最简单的计算机语言来解决小学数学问题 ...