29

最近一个关于.NET 中字符串文字的问题引起了我的注意。我知道字符串文字是实习的,因此具有相同值的不同字符串引用同一个对象。我也知道可以在运行时实习字符串:

string now = DateTime.Now.ToString().Intern(); 

显然,在运行时实习的字符串驻留在堆上,但我假设文字被放置在程序的数据段中(并在我对上述问题的回答中这么说)。但是我不记得在任何地方看到过这个。我认为是这种情况,因为我会这样做,并且ldstrIL 指令用于获取文字并且似乎没有发生分配的事实似乎支持了我。

长话短说,字符串字面量在哪里?它是在堆上、数据段上还是我没有想到的某个地方?


编辑:如果字符串文字确实驻留在堆上,它们是什么时候分配的?

4

7 回答 7

109

.NET 中的字符串是引用类型,因此它们总是在堆上(即使它们被实习)。您可以使用诸如 WinDbg 之类的调试器来验证这一点。

如果你有以下课程

   class SomeType {
      public void Foo() {
         string s = "hello world";
         Console.WriteLine(s);
         Console.WriteLine("press enter");
         Console.ReadLine();
      }
   }

你调用Foo()一个实例,你可以使用 WinDbg 来检查堆。

引用很可能存储在小程序的寄存器中,因此最简单的方法是通过执行!dso. 这为我们提供了相关字符串的地址:

0:000> !dso
OS Thread Id: 0x1660 (0)
ESP/REG  Object   Name
002bf0a4 025d4bf8 Microsoft.Win32.SafeHandles.SafeFileHandle
002bf0b4 025d4bf8 Microsoft.Win32.SafeHandles.SafeFileHandle
002bf0e8 025d4e5c System.Byte[]
002bf0ec 025d4c0c System.IO.__ConsoleStream
002bf110 025d4c3c System.IO.StreamReader
002bf114 025d4c3c System.IO.StreamReader
002bf12c 025d5180 System.IO.TextReader+SyncTextReader
002bf130 025d4c3c System.IO.StreamReader
002bf140 025d5180 System.IO.TextReader+SyncTextReader
002bf14c 025d5180 System.IO.TextReader+SyncTextReader
002bf15c 025d2d04 System.String    hello world             // THIS IS THE ONE
002bf224 025d2ccc System.Object[]    (System.String[])
002bf3d0 025d2ccc System.Object[]    (System.String[])
002bf3f8 025d2ccc System.Object[]    (System.String[])

现在使用!gcgen来找出实例在哪一代:

0:000> !gcgen 025d2d04 
Gen 0

它处于零代——即它刚刚被分配。谁在扎根?

0:000> !gcroot 025d2d04 
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1660
ESP:2bf15c:Root:025d2d04(System.String)
Scan Thread 2 OSTHread 16b4
DOMAIN(000E4840):HANDLE(Pinned):6513f4:Root:035d2020(System.Object[])->
025d2d04(System.String)

ESP 是我们Foo()方法的堆栈,但请注意我们也有一个object[]。那是实习生表。让我们来看看。

0:000> !dumparray 035d2020
Name: System.Object[]
MethodTable: 006984c4
EEClass: 00698444
Size: 528(0x210) bytes
Array: Rank 1, Number of elements 128, Type CLASS
Element Methodtable: 00696d3c
[0] 025d1360
[1] 025d137c
[2] 025d139c
[3] 025d13b0
[4] 025d13d0
[5] 025d1400
[6] 025d1424
...
[36] 025d2d04  // THIS IS OUR STRING
...
[126] null
[127] null

我稍微减少了输出,但你明白了。

总之:字符串在堆上 - 即使它们被实习。实习表保存对堆上实例的引用。即在 GC 期间不收集实习字符串,因为实习表将它们作为根。

于 2008-12-16T20:22:05.710 回答
12

在 Java 中(来自Java 词汇表):

在 Sun 的 JVM 中,内部字符串(包括字符串文字)存储在称为 perm gen 的特殊 RAM 池中,JVM 还加载类并存储本地编译的代码。但是,intered String 的行为与它们存储在普通对象堆中的行为没有什么不同。

于 2008-12-16T20:38:19.843 回答
3

如果我错了,请纠正我,但在 Java 和 .NET 中并非所有对象都驻留在堆上?

于 2008-12-16T20:21:27.850 回答
1

在 .Net 中,“实习”时的字符串文字存储在称为“实习表”的特殊数据结构中。这与堆和堆栈是分开的。然而,并不是所有的字符串都被保留了......我很确定那些不是存储在堆上的。

不了解Java

于 2008-12-16T20:23:21.620 回答
1

我在 MSDN 的网站上发现了这个关于ldstrIL 指令的内容:

ldstr指令将对象引用(类型 O)推送到表示存储在元数据中的特定字符串文字的新字符串对象。该ldstr指令分配必要的内存量并执行将字符串文字从文件中使用的形式转换为运行时所需的字符串格式所需的任何格式转换。

公共语言基础结构 (CLI) 保证引用两个具有相同字符序列的元数据标记的 ldstr 指令的结果返回完全相同的字符串对象(称为“字符串实习”的过程)。

这意味着字符串文字实际上存储在 .NET 的堆中(与mmyers指出的Java 不同)。

于 2008-12-17T07:08:04.037 回答
0

在 Java 中,像所有对象一样的字符串都驻留在堆中。只有局部原始变量(整数、字符和对对象的引用)驻留在堆栈中。

于 2008-12-16T20:44:54.760 回答
-1

Java 中的实习字符串位于一个单独的池中,称为字符串池。该池由 String 类维护并驻留在普通堆上(不是上面提到的用于存储类数据的 Perm 池)。

据我了解,并非所有字符串都是实习的,但调用 myString.intern() 会返回一个字符串,该字符串由字符串池保证。

另请参阅: http ://www.javaranch.com/journal/200409/ScjpTipLine-StringsLiterally.html 和 javadoc http://java.sun.com/j2se/1.5.0/docs/api/java/lang/String .html#intern()

于 2008-12-17T13:53:37.680 回答