【问题标题】:Java Destruction of VariablesJava 销毁变量
【发布时间】:2012-12-23 00:02:05
【问题描述】:

最近一位开发者向我提出了这个问题,但无法提供答案...

在 Java 中,如何/何时处理变量?是吗?

假设我们有一个每秒被调用 50-75 次的 run() 方法(非常简化的示例)...

public void run() {
    long lastTime = System.nanoTime();
    double unprocessed = 0;
    int ticks = 0;
    int frames = 0;
    double nsPerTick = 1000000000.0 / 60.0;
    long now = System.nanoTime();
    unprocessed += (now - lastTime) / nsPerTick;
    lastTime = now;

    ticks++;
    tick();
    unprocessed -= 1;
}

将这些变量(lastTime、unprocessed、ticks 等)带入更高级别的范围是否会使程序运行得更好/更有效 - 使其不会被每秒创建和使用多次?

【问题讨论】:

  • This 可能会有所帮助
  • 变量不会被处理掉。它们具有定义的“范围”,并在退出范围时消失。 (另一方面,对象会被垃圾回收。)

标签: java performance memory


【解决方案1】:

它不会给您带来任何性能提升,因为这些变量是在 stack 中创建的。

阅读这篇文章以了解更多关于 Java 中的对象破坏:

What method in Java is used to destroy your objects

【讨论】:

  • 并且需要注意的是,与 Pascal 或 Algol 不同,Java 没有“词法作用域”,也没有类似于 Perl 或 Lisp 的“动态作用域”。变量可以是本地范围、实例范围或静态(类)范围。
【解决方案2】:

Java 使用garbage collector,因此未使用的对象会被自动处理掉。在您的示例中,您仅使用局部变量,它们在堆栈上分配,这非常快(这里不涉及垃圾收集)。将这些变量移到外部函数(“范围更高”)将无济于事。

【讨论】:

  • GC与原语无关。
  • 我没有写它...我真的写了,我引用“(这里不涉及垃圾收集)”
【解决方案3】:

在 Java 中,如何/何时处理变量?是吗?

首先,让我们弄清楚一些事情。在Java中,有些东西叫做变量,有些东西叫做对象。变量和对象不是一回事。

Java 中的变量始终是其他东西的一部分:

  • 局部变量是方法调用状态的一部分。当它超出范围时,它会被处理掉。这通常表示方法调用结束并且方法调用的堆栈帧从调用堆栈中弹出。

  • 实例变量(或字段或属性)是对象的一部分。当对象被处理掉时,它被处理掉。

  • 静态变量(或类变量)是类的一部分,只有在类被卸载时才会被释放。

在您的示例中,所有变量都是局部变量,当当前对 run() 方法的调用返回(或由于异常而终止)时,它们将被“处理掉”。

将这些变量(lastTime、unprocessed、ticks 等)带入更高级别的范围是否会使程序运行得更好/更有效 - 使其不会被每秒创建和使用多次?

没用。创建或处置局部变量的增量成本为零1。并且使用局部变量的成本(如果有的话)小于使用实例变量的成本。唯一的成本可能是初始化它,但无论如何你都会承担这个成本;例如在每个方法调用上重新初始化实例变量。

确实,将局部变量变成实例变量可能实际上是一件坏事。这意味着该方法现在使用对象变量来表示其状态,使其不可重入。 (不清楚这是否重要。这取决于run() 方法的使用方式是否要求它是可重入的……通常情况并非如此。)


1 - 这有点过于简单了。如果您有足够多的局部变量和/或深度递归方法,那么您可能需要使用比正常更大的线程堆栈大小。额外的内存使用将是成本。

【讨论】:

  • 这是一个内容丰富且完整的答案。非常感谢。您知道我可以参考哪些资源来更深入地了解 Java 内存管理吗?
【解决方案4】:

这个问题的答案取决于 Java 的垃圾收集,它有一个称为“nursery gc”的较小切片,它被非常频繁地调用并且不会导致 jvm 停止。所有的局部变量都进入这个 Nursery gc。当此方法执行完成时;所有这些变量都符合 GC 条件;并在调用nursery gc 时被删除。

回答您的问题是否将这些变量移出运行方法会更好。尽管 JVM 在每个版本中都变得越来越智能;创建对象对性能的影响较小。但是即使影响很小,我仍然感到;我觉得将对象设为全局并没有什么坏处。

虽然在这种情况下所有变量都是原始的;它不会产生任何影响。

【讨论】:

  • 在方法范围内声明的基元是在堆栈上创建的,不会被垃圾回收。
  • 对不起我的错误;我没有注意到所有都是原始人。我只是专注于变量,并给出了一个将它们视为对象的通用答案。
  • 您将术语“变量”和“对象”混为一谈。它们是完全不同且截然不同的,理解它们的区别很重要。
【解决方案5】:

Id 取决于 JVM 运行模式和 JVM 版本。

但大多数 JVM 版本会这样执行:

当 JVM 在-client 模式下运行时,代码将逐行运行。

但是当JVM在-server模式下运行时,代码会被优化:

假设tick()方法是:

public void tick(){
   System.out.println("tick");
}

优化后的最终代码可能:

public void run() {
  long lastTime = System.nanoTime();
  int ticks = 0;
  int frames = 1;
  double unprocessed = (System.nanoTime() - lastTime) / 16666666.66 -1 ;
  // tick method will inline here instead of invoking tick();
  System.out.println("tick");
}

【讨论】:

  • 这不是真的。 -client 和 -server 有不同的优化策略。见stackoverflow.com/questions/198577/…
  • @mabn 是的,优化被称为escape analysis 和method inline 使用上面。
  • 从内存中,如果该方法被调用足够多次,那么 JVM(使用任一选项)将加速它。假设该方法在外部不执行任何操作(例如 void func(){int i=0;i++;i--;}),那么一段时间后 JVM 将优化代码,以便该函数有效地执行 void func() {} 并完全删除函数调用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-19
  • 2013-03-28
  • 2018-01-21
  • 1970-01-01
  • 1970-01-01
  • 2012-02-08
相关资源
最近更新 更多