关于Java和C ++中的内存管理

时间:2009-01-14 00:05:07

标签: java c++ memory-management

好吧,我已经完成了一项任务,基本上可以找出内存分配如何适用于我将使用的任何语言。经过一些研究,我有一些问题和怀疑,我希望得到一些见解。例如:

我读了here,Java确切地指定了堆栈内容的组织方式。查看JVM spec structure,它基本上表示堆栈包含框架,并且框架通过正确分配变量和函数来包含类中的内容。也许我在这里遗漏了一些东西,但我不明白这与C ++的作用有何不同。我问,因为第一个链接说Java的堆栈内容规范避免了编译器的不兼容性。

另外,我还没有找到内存段如何精确组织在一起。例如,我知道内存分为全局变量,调用堆栈,堆和C ++代码,但我不知道堆的地址是否高于堆栈,或者是否取决于实现。我也想知道Java程序是否具有更多,以及它是如何布局的。我想有一个标准,因为JVM必须知道它在哪里使用它,虽然我想它可以只有指针并将其余部分留给操作系统。我想,至少必须有一个事实上的标准。

另一件我不理解的是运行时常量池。它应该是“类文件中的constant_pool表的每类或每接口运行时表示”,但我不认为我理解它的作用。它似乎有一个标签来表明有问题的结构是什么类型的?然后是结构的名称(由程序员给出或由底层系统分配?)然后看起来它的其余部分随着标签所描述的不同而变化(线程,数组等)。

如果我对运行时常量池的解释是正确的,那么为什么它们和堆栈帧一样必要?是因为堆栈帧只处理堆栈段,运行时常量池还必须有堆分配内存的指针吗?

2 个答案:

答案 0 :(得分:6)

  

查看JVM规范结构,它基本上表示堆栈包含框架,并且框架通过正确分配变量和函数来包含类中的任何内容。也许我在这里遗漏了一些东西,但我不明白这与C ++的作用有何不同。我问,因为第一个链接说Java的堆栈内容规范避免了编译器的不兼容性。

在实践中,C ++编译器遵循相同的基本策略。然而,标准委员会并未将其视为语言问题。相反,C ++编译器遵循这个系统,因为这是大多数CPU和操作系统的设计方式。不同的平台不同意数据是传递给堆栈上的函数还是通过寄存器(RISC机器)传递,无论堆栈是增长还是减少,是否有不同的调用约定允许“正常”调用使用堆栈而其他人使用某些签名else(例如,__fastcallnaked),是否存在nested functionstail call support之类的内容。

事实上,符合标准的C ++编译器可以编译成类似于Scheme VM的东西,其中“堆栈”是非常不同的,因为Scheme要求实现支持尾调用和延续。我从来没有见过这样的东西,但这是合法的。

The "compiler incompatibilities" are most obvious if you try to write a garbage collector

  

当前函数及其所有调用者的所有局部变量都在[“the”堆栈中,但请考虑ucontext.hWindows Fibers]。对于每个平台(意思是OS + CPU +编译器),有一种方法可以找出[“堆栈”]的位置。 Tamarin这样做,然后它在GC期间扫描所有内存以查看当地人指向的位置。 ...

     

这个魔法存在于一个宏MMGC_GET_STACK_EXTENTS中,在头文件MMgc / GC.h中定义。 ...... [T]这是每个平台的单独实现。

     

在任何给定时刻,某些本地人可能在CPU寄存器中而不在堆栈中。为了解决这个问题,宏使用几行汇编代码将所有寄存器的内容转储到堆栈中。这样MMgc就可以扫描堆栈,它会看到所有局部变量。


此外,Java中的 objects 通常不会在堆栈上分配。而是引用它们。 int,double,booleans和其他原始类型确实在堆栈上分配。在C ++中,任何东西都可以在堆栈上分配,它有自己的优缺点列表。

  

另一件我不理解的是运行时常量池。它应该是“类文件中constant_pool表的每个类或每个接口的运行时表示”,但我不认为我理解它的作用。

考虑:

String s = "Hello World";
int i = "Hello World".length();
int j = 5;

s,i和j都是变量,每个变量都可以在程序的某个后期更改。但是,“Hello World”是String类型的对象,无法更改,5是无法更改的int,“Hello World”.length()可以在编译时确定,始终返回11.这些常量是可以在它们上调用有效的对象和方法(好吧,至少在String上),因此需要在某处分配它们。但它们永远无法改变。如果这些常量属于某个类,则它们将分配在每个类的常量池中。其他不属于类的常量数据(如main()线程的ID)在每个运行时常量池中分配(“运行时”,在本例中为“JVM实例”)。

C ++标准有一些关于类似技术的语言,但实现方式是二进制格式(ELF,a.out,COFF,PE等)。标准期望作为整数数据类型(bool,int,long等)或c样式字符串的常量实际上保存在二进制文件的常量部分中,而其他常量数据(双精度数,浮点数,类)可能存储作为变量以及一个标志,表示“变量”不可修改(使用整数和c样式的字符串常量存储它们也是可以接受的,但许多二进制格式不能使它成为一个选项)。

一般来说,当一次打开一个以上的程序副本时,可以共享二进制文件的“常量数据部分”(因为常量数据在程序的每个副本中都是相同的)。 On ELF this section is called the .rodata section

答案 1 :(得分:3)

你到底要完成的任务是什么?

Java和C ++之间的主要区别在于Java是由VM收集的垃圾,而在C ++中,程序直接在机器上执行,内存通过OS服务进行管理。

关于堆栈,框架只是C ++编译器所做的“官方”和标准形式。当你从一个调用移动到另一个调用时,C ++编译器只是将堆栈中的东西放在堆栈中。在Java中,术语是框架,并且因为编译的Java代码应该在任何平台上运行,所以对于如何发生这些代码有非常明确的标准。在C ++中,每个编译器都可以不同地处理堆栈(例如,甚至是字大小的性质)。

在Java中,一切都在管理所有内容的VM中运行,尽管它会将一些内容委托给环境。换句话说,您无法访问JVM放置数据和代码的位置,您的代码甚至可能永远不会成为真正的“代码段”。换句话说,这无法真正得到回答。在C ++中,一切都在硬件上运行,因此您将拥有堆栈段,数据段等。查看有关C ++的信息。

在C ++中,类在运行时没有在内存中表示;实际上,您可以将C ++编译为C,然后将结果编译为汇编。在Java中,所有内容都在运行时表示,因此您可以向对象询问它所属的类以及支持的方法。因此,每个类文件都有一个“常量池”,其中出现表示方法名称,字段名称等字符串的字符串。实际的类定义是指池。换句话说,这与堆栈帧几乎没有关系。堆栈帧是存储方法参数,局部变量和返回值的位置。