Java int 内存使用情况

2022-09-01 07:59:03

当我考虑各种类型的内存使用情况时,我开始对Java在传递给方法时如何利用整数的内存感到有些困惑。

比如说,我有以下代码:

public static void main (String[] args){
     int i = 4;
     addUp(i);
}

public static int addUp(int i){
     if(i == 0) return 0;
     else return addUp(i - 1);         
}

在下面的例子中,我想知道我的以下逻辑是否正确:

  • 我最初为整数i = 4做了一个记忆。然后我把它传递给一个方法。但是,由于基元在Java中没有指向,因此在addUp(i == 4)中,我创建了另一个整数i = 4。然后,还有另一个addUp(i == 3),addUp(i == 2),addUp(i == 1),addUp(i == 0),其中每次,由于该值没有指向,因此在内存中分配一个新的i值。
  • 然后对于单个“int i”值,我使用了6个整数值记忆。

但是,如果我总是通过数组传递它:

public static void main (String[] args){
     int[] i = {4};
     // int tempI = i[0];
     addUp(i);
}

public static int addUp(int[] i){
     if(i[0] == 0) return 0;
     else return addUp(i[0] = i[0] - 1);         
}

- 由于我创建了一个大小为1的整数数组,然后将其传递给addUp,它将再次传递给addUp(i[0] == 3),addUp(i[0] == 2),addUp(i[0] == 1),addUp(i[0] == 0),我只需要使用1个整数数组内存空间,因此更具成本效益。此外,如果我事先创建一个int值来存储i[0]的初始值,我仍然有我的“原始”值。

然后这让我想到了一个问题,为什么人们在Java方法中像int一样传递原语?只是传递这些基元的数组值不是更节省内存吗?还是第一个例子仍然只是O(1)内存?

在这个问题之上,我只是想知道使用int[]和int的内存差异,特别是对于1的大小。提前感谢您。我只是想知道使用Java可以提高内存效率,这出现在我的脑海中。

感谢所有的答案!我现在很快想知道,如果我要“分析”每个代码的大内存,它们都被认为是O(1)还是假设是错误的?


答案 1

这里缺少的是:示例中的 int 值位于堆栈上,而不是堆上。

而且,与堆上的对象相比,处理堆栈上存在的固定大小的基元值的开销要少得多!

换句话说:使用“指针”意味着您必须在堆上创建一个新对象。所有对象都位于堆中;没有数组堆栈!对象在您停止使用后立即受到垃圾回收的影响。另一方面,堆栈在您调用方法时来来去去!

除此之外:请记住,编程语言提供给我们的抽象是为了帮助我们编写易于阅读,理解和维护的代码而创建的。您的方法基本上是进行某种微调,从而导致更复杂的代码。这不是Java解决此类问题的方式。

这意味着:使用Java,真正的“性能魔术”发生在运行时,当即时编译器启动时!您会看到,当调用小方法“足够频繁”时,JIT 可以内联调用小方法。然后,将数据“紧密”在一起变得更加重要。如:当数据位于堆上时,您可能必须访问内存才能获得值。而存在于堆栈上的项目 - 可能仍然是“接近”的(如:在处理器缓存中)。因此,优化内存使用率的小想法实际上可能会使程序执行速度减慢几个数量级。因为即使在今天,访问处理器缓存和读取主内存之间也存在数量级。

长话短说:避免在性能或内存使用方面进行这种“微调整”:JVM针对“正常,典型”用例进行了优化。因此,您尝试引入巧妙的解决方法很容易导致“不太好”的结果。

所以 - 当你担心性能时:做其他人都在做的事情。如果你 真的在乎 - 那么了解JVM是如何工作的。事实证明,即使我的知识也有点过时了 - 因为注释暗示JIT可以在堆栈上内联对象。从这个意义上说:专注于编写干净,优雅的代码,以直接的方式解决问题!

最后:这在某些时候可能会发生变化。有一些想法可以将真正的值值对象引入java。它基本上存在于堆栈上,而不是堆上。但不要指望这种情况发生在Java10之前。或 11。或。。。(我认为这在这里是相关的)。


答案 2

几件事:

第一件事是拆分头发,但是当你在java中传递一个int时,你将4个字节分配给堆栈,当你传递一个数组(因为它是一个引用)时,你实际上是在将8个字节(假设是x64架构)分配给堆栈,再加上将int存储到堆中的另外4个字节。

更重要的是,存在于数组中的数据被分配到堆中,而对数组本身的引用被分配到堆栈上,当传递整数时,不需要堆分配,原语只分配到堆栈中。随着时间的推移,减少堆分配将意味着垃圾回收器需要清理的东西将更少。而堆栈帧的清理是微不足道的,不需要额外的处理。

但是,这都是没有意义的(恕我直言),因为在实践中,当您拥有复杂的变量和对象集合时,您最终可能会将它们组合成一个类。一般来说,你应该写这篇文章来提高可读性和可维护性,而不是试图从JVM中榨取最后一滴性能。JVM非常快,并且总是有摩尔定律作为后盾。

很难分析每个的 Big-O,因为为了获得真实的图像,您必须考虑垃圾回收器的行为,并且该行为高度依赖于 JVM 本身和 JVM 对代码所做的任何运行时 (JIT) 优化。

请记住唐纳德·高德纳(Donald Knuth)的明智之言:“过早优化是万恶之源”

编写避免微调整的代码,从长远来看,提高可读性和可维护性的代码会更好。