好吧,我已经完成了一项任务,基本上可以找出内存分配如何适用于我将使用的任何语言.经过一些研究,我有一些问题和怀疑,我希望得到一些见解.例如:
我在这里读到Java确切地指定了堆栈内容的组织方式.查看JVM规范结构,它基本上表示堆栈包含框架,并且框架通过正确分配变量和函数来包含类中的内容.也许我在这里遗漏了一些东西,但我不明白这与C++的作用有何不同.我问,因为第一个链接说Java的堆栈内容规范避免了编译器的不兼容性.
此外,我还没有找到内存段是如何精确组织在一起的.例如,我知道内存分为全局变量,调用堆栈,堆和C++代码,但我不知道堆的地址是否高于堆栈,或者是否取决于实现.我也想知道Java程序是否具有更多,以及它是如何布局的.我想有一个标准,因为JVM必须知道它在哪里使用它,虽然我想它可以只有指针并将其余部分留给操作系统.我想,至少必须有一个事实上的标准.
另一个我不理解的是运行时常量池.它应该是"类文件中的constant_pool表的每类或每接口运行时表示",但我不认为我理解它的作用.它似乎有一个标签来表明有问题的结构是什么类型的?然后是结构的名称(由程序员给出或由底层系统分配?)然后看起来它的其余部分随着标签所描述的不同而变化(线程,数组等).
如果我对运行时常量池的解释是正确的,那么为什么它们和堆栈帧一样必要?是因为堆栈帧只处理堆栈段,运行时常量池还必须有堆分配内存的指针吗?
查看JVM规范结构,它基本上表示堆栈包含框架,并且框架通过正确分配变量和函数来包含类中的内容.也许我在这里遗漏了一些东西,但我不明白这与C++的作用有何不同.我问,因为第一个链接说Java的堆栈内容规范避免了编译器的不兼容性.
在实践中,C++编译器遵循相同的基本策略.然而,标准委员会并未将其视为语言问题.相反,C++编译器遵循这个系统,因为这是大多数CPU和操作系统的设计方式.不同的平台不同意数据是传递给堆栈上的函数还是通过寄存器(RISC机器)传递,无论堆栈是增长还是减少,是否有不同的调用约定允许"正常"调用使用堆栈而其他人使用某些签名else(例如,__ fastcall和naked),是否存在嵌套函数,尾调用支持等等.
事实上,符合标准的C++编译器可以编译成类似于Scheme VM的东西,其中"堆栈"是非常不同的,因为Scheme要求实现支持尾调用和延续.我从来没有见过这样的东西,但这是合法的.
当前函数及其所有调用者的所有局部变量都在[""堆栈中,但请考虑ucontext.h和Windows Fibers ].对于每个平台(意思是OS + CPU +编译器),有一种方法可以找出["堆栈"]的位置.Tamarin这样做,然后它在GC期间扫描所有内存以查看当地人指向的位置....
这个魔法存在于一个宏MMGC_GET_STACK_EXTENTS中,在头文件MMgc/GC.h中定义....... [T]这是每个平台的单独实现.
在任何给定时刻,一些本地人可能在CPU寄存器中而不在堆栈中.为了解决这个问题,宏使用几行汇编代码将所有寄存器的内容转储到堆栈中.这样MMgc就可以扫描堆栈,它会看到所有局部变量.
此外,Java中的对象通常不会在堆栈上分配.而是引用它们.int,double,booleans和其他原始类型确实在堆栈上分配.在C++中,任何东西都可以在堆栈上分配,它有自己的优缺点列表.
另一个我不理解的是运行时常量池.它应该是"类文件中的constant_pool表的每类或每接口运行时表示",但我不认为我理解它的作用.
考虑:
String s = "Hello World";
int i = "Hello World".length();
int j = 5;
Run Code Online (Sandbox Code Playgroud)
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样式字符串常量存储它们也是可以接受的,但许多二进制格式不能使它成为一个选项).
一般来说,当一次打开一个以上的程序副本时,可以共享二进制文件的"常量数据部分"(因为在程序的每个副本中,常量数据将是相同的). 在ELF上,此部分称为.rodata部分.
给你的任务到底是什么?
Java 和 C++ 之间的主要区别在于,Java 由 VM 进行垃圾收集,而在 C++ 中,程序直接在机器上执行,并且通过操作系统服务管理内存。
关于堆栈,框架只是 C++ 编译器所做的更“官方”和标准的形式。当您从一个调用移动到另一个调用时,C++ 编译器只是将堆栈中的内容放在彼此的顶部。在 Java 中,术语是“框架”,并且由于编译后的 Java 代码应该在任何平台上运行,因此对于如何发生有非常明确的标准。在C++ 中,每个编译器可以不同地对待堆栈(例如,甚至根据字大小的性质)。
在Java中,一切都在管理一切的VM中运行,尽管它将一些东西委托给环境。换句话说,您无权访问 JVM 放置您的数据和代码的位置,并且您的代码甚至可能永远不会成为真正的“代码段”。换句话说,这个问题确实无法回答。在 C++ 中,一切都在硬件上运行,因此您将拥有堆栈段、数据段等。查看有关 C++ 的信息。
在 C++ 中,类在运行时在内存中没有表示;事实上,您可以将C++编译为C,然后将结果编译为汇编。在Java中,一切都在运行时表示,因此您可以询问一个对象它属于哪个类以及支持什么方法。因此,每个类文件都有一个“常量池”,其中出现表示方法名称、字段名称等内容的字符串。实际的类定义是指池。换句话说,这与堆栈帧关系不大。堆栈帧是存储方法参数、局部变量和返回值的地方。