【问题标题】:Runtime vs compile time memory allocation in javajava中的运行时与编译时内存分配
【发布时间】:2012-09-05 16:00:09
【问题描述】:

我对 java 中的内存分配是发生在运行时还是编译时感到困惑。

例如:

class Test{
  int a;
  public Test(){
    a=10;
  }
};

// somewhere else
Test t = new Test();

a 是在运行时还是在编译时分配的?如果在编译时,java怎么可能在直接获取已编译的.class文件的VM上运行?

还有:

  • a 何时被赋值为10

  • 引用变量t如何工作?

谢谢。

【问题讨论】:

  • 这个例子不能编译(赋值应该和声明合并)。
  • 您可能会混淆可在编译时确定的内存分配大小与直到运行时才知道的内存分配。
  • 在程序运行之前,引用的大小是未知的。 64 位 JVM 可以使用 32 位或 64 位引用。
  • 是的,我可能知道两者之间有什么区别。任何示例都会有很大帮助。在编译时确定大小如何帮助因为 JVM 分配内存并且文件在其他地方编译。

标签: java allocation


【解决方案1】:

编译时没有内存分配发生。仅在加载和运行时。

编译时生成 .class 文件就可以了。

请记住,您需要有一个主类来运行程序。当您使用带有 .class 文件的类路径的 Java 运行程序时,会有诸如加载和链接等步骤,

类加载器将文件加载到 permgen。

main方法调用时,会创建栈并放局部变量

当运行时遇到 new 时,它会在堆上创建对象并在那里分配所需的内存,就像测试所需的内存一样。

【讨论】:

  • a 是原语,不是对象或引用。
  • 这个问题令人困惑。 ;)
  • @PeterLawrey:我认为 OP 确实在寻找部件如何编织在一起,从加载到运行时等,示例问题是,只是为了了解内存 JVM 如何处理指令(甚至认为它不是完美的)。我在学习 java 时也有类似的困惑,因为几个月的进展变得清晰了。
【解决方案2】:

局部变量和方法参数(如原语或引用)在编译时理论上会在堆栈上分配一个位置。

在运行时,这不能保证反映它在内存中的布局方式。

堆上的对象分配只发生在运行时。

java在直接编译.class文件的VM上运行怎么可能。

只有 VM 知道如何编译代码,因此无法在编译时进行内存分配。

什么时候是赋值10

在赋值发生的那一行。鉴于它没有被使用,JIT 可以丢弃它,所以它可能根本不会发生。

同样的问题代表参考变量 t。

t 在构造对象之后分配到= 所在的位置。

【讨论】:

  • @peter-lawry 你能否澄清一下你的意思:“局部变量和方法参数,如原语或引用,在编译时理论上在堆栈上分配了一个位置。” 1. 你所说的“名义上”是什么意思? 2. 如果 JVM 实际上没有运行任何东西,为什么它会在编译时有一个堆栈?这句话似乎与这句话相矛盾:“只有 VM 知道代码将如何编译,因此无法在编译时进行内存分配。” ...我对 Java 比较陌生,所以谢谢!
  • @LXXIII “名义上”是指字节码用于虚拟机,但实际上并不存在。因此,虽然它为 VM 寄存器分配,但它们如何以及是否是实际寄存器是在运行时确定的。
  • @LXXIII 明显的收缩是javac 编译器可以分配/分配常量、指令、寄存器、堆栈使用,但在运行时 JIT 可以根据实际硬件重新优化它。即在运行时机器代码可能看起来完全不同。
  • 当你说编译时是指字节码何时生成,或者字节码何时变成CPU可以运行的东西?
【解决方案3】:

嗯,这个有点笨拙,我不确定你是否会从整个线程中得到你想要的确切答案,因为实际上,你要问的是专业编译器内部的讨论,大多数人真的不在乎。

在大多数情况下,Java 编译器使用自动内存管理,因此真正由编译器决定它将做什么或不做什么,并且这可以在版本之间改变。

但在我开始解释之前,我想澄清一下我的符号:

  1. 当引用不是 原始。
  2. 当引用内存中具有值和标识符的位置时,我将使用 [object]。
  3. 我将使用 [primitive] 来指代由 Java 中的一种基本类型(int、double、float、 以此类推,不包括字符串)
  4. String是java中的一个特例,虽然是一个对象,但可能与其他对象区别对待。

    一个 [object] 有一个特殊的属性。它有一个值和一个标识符,标识符被解析为一个值的过程以及它发生的时间取决于绑定的类型。

    有静态绑定,其中绑定可以在编译时解析,其值或方法在编译时是已知的。这也称为“早期”绑定。例如。

    int a = 0; // 和 //直接函数调用,比如 print();

    还有动态绑定,其中标识符与值或子程序与程序之间的绑定直到运行时才会发生。这也称为“后期”绑定。例如。

    public void foo(java.util.List list) { list.add("bar"); }

    还有一种混合类型的绑定,但我不打算谈论它,因为我还没有发现 Java 拥有它。

    现在,绑定也与作用域密切相关,作用域是变量“存在”在特定作用域中的想法。这是我真的不想讨论的话题(范围界定有点像熊)并使这篇文章成为小说而不是中篇小说。

    Java 中内存分配的工作方式取决于以下几点:

    1. 如果对 [Object]、[object] 或 [primitive] 的引用在编译时已知,并且可能发生静态绑定,则编译器可能会为这些对象分配内存(注意我是如何不使用括号的)在编译时。

    2. 如果在编译时无法知道对 [Object]、[object] 或 [primitive] 的引用,并且必须使用动态绑定,则编译器可能会在运行时为这些对象分配内存.

    Java 处理在运行时分配的对象的方式有所不同,这取决于哪个绑定用于什么类型。

    1. 静态绑定
      • 作为一种 [Object] 的对象将在编译时将其引用放置在堆栈上,但在运行时将其内存分配在堆上。 (懒惰的)。
      • 属于一种 [primitive] 的对象将在运行时绑定和分配。
      • 字符串是一种特殊情况,但通常像 [Object] 的情况一样处理
    2. 后期绑定
      • 堆栈和堆上的分配在运行时完成。

    总之,不用担心。这样做让你很头疼。

    如果我对此有任何误解,请告诉我。我有点生疏了。

【讨论】:

    【解决方案4】:

    在java中,除非创建了在运行时创建的对象,否则不会加载类,因此在加载类时,您的任何成员变量(如“a”)都会获得空间对于对象来说,它将在运行时分配空间.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-06
      • 2023-04-05
      • 1970-01-01
      • 2015-08-31
      • 1970-01-01
      • 2012-06-26
      相关资源
      最近更新 更多