当我在思考各种类型的内存使用情况时,我开始对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);
}
在下面的例子中,我想知道我的以下逻辑是否正确:
但是,如果我总是通过数组传递它:
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将再次传递给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)还是假设错了?
答案 0 :(得分:56)
您在此处缺少的内容:示例中的int值位于 stack 上,而不是堆上。
与堆上的对象相比,处理堆栈上存在的固定大小原始值的开销要小得多!
换句话说:使用“指针”意味着您必须在堆上创建一个新对象。 所有对象都存在于堆中;数组有 no 堆栈!停止使用后,对象会立即受到垃圾回收的影响。另一方面,当您调用方法时,堆栈会来来去去!
除此之外:请记住,编程语言为我们提供的抽象是为了帮助我们编写易于阅读,理解和维护的代码而创建的。您的方法基本上是进行某种微调,导致更复杂的代码。而这不是Java如何解决这些问题。
含义:使用Java,真正的“性能魔法”在运行时发生,当即时编译器开始运行时!你看,当“足够频繁”调用该方法时,JIT可以内联调用小方法。然后,将数据“关闭”在一起变得更加更多重要。如下所示:当数据存在于堆上时,您可能必须访问 memory 才能获取值。而生活在堆栈上的项目可能仍然“关闭”(如:在处理器缓存中)。因此,优化内存使用的小想法实际上可能会使程序执行速度降低几个数量级。因为即使在今天,访问处理器缓存和读取主存储器之间也存在数量级。
长话短说:避免对性能或内存使用情况进行这种“微调”:JVM针对“正常,典型”的用例进行了优化。因此,尝试引入巧妙的解决方法很容易导致“不太好”的结果。
所以 - 当你担心表现时:做其他人正在做的事情。如果你 一个 非常关心 - 那么了解JVM 的工作方式。事实证明,即使我的知识稍微过时 - 因为评论意味着JIT可以内联堆栈中的对象。从这个意义上说:专注于编写干净,优雅的代码,以直接的方式解决问题!
最后:这可能会在某些时候发生变化。有想法将 true 值值对象引入java。它基本上存在于堆栈中,而不是堆。但是不要指望在Java10之前发生这种情况。或者11.或......(我认为this在这里是相关的。)
答案 1 :(得分:18)
有几件事:
首先是分裂头发,但是当你在java中传递一个int时,你将4个字节分配到堆栈上,当你传递一个数组(因为它是一个引用)时,你实际上是在分配8个字节(假设是x64)架构)到堆栈,加上将int存储到堆中的额外4个字节。
更重要的是,生成在数组中的数据被分配到堆中,而对数组本身的引用被分配到堆栈上,当传递整数时,不需要堆分配,原语只被分配到堆栈中。随着时间的推移,减少堆分配将意味着垃圾收集器将有更少的东西需要清理。而堆栈帧的清理是微不足道的,并且不需要额外的处理。
然而,这一切都没有实际意义(因为在实践中,当你有复杂的变量和对象集合时,你可能最终将它们组合成一个类。通常,您应该编写以提高可读性和可维护性,而不是试图从JVM中挤出最后一滴性能。 JVM非常快,并且始终有Moore's Law作为后盾。
分析每个Big-O是很困难的,因为为了获得真实的图像,您必须考虑垃圾收集器的行为,并且该行为高度依赖于JVM本身和任何运行时(JIT)JVM对您的代码进行的优化。
请记住Donald Knuth明智的话语"过早优化是所有邪恶的根源"
编写避免微调的代码,从长远来看,提高可读性和可维护性的代码将会更好。
答案 2 :(得分:10)
如果你的假设是传递给函数的参数必然消耗内存(顺便说一下是假的),那么在第二个传递数组的例子中,会产生对数组的引用的副本。该引用实际上可能大于int,它不太可能更小。
答案 3 :(得分:7)
这些方法是否采用O(1)或O(N)取决于编译器。 (这里N是i
或i[0]
的值,取决于。)如果编译器使用尾递归优化,则可以重用参数,局部变量和返回地址的堆栈空间并执行那么将是O(1)的空间。缺少尾递归优化,空间复杂度与时间复杂度O(N)相同。
基本上,尾递归优化(在这种情况下)是编译器将代码重写为
public static int addUp(int i){
while(i != 0) i = i-1 ;
return 0;
}
或
public static int addUp(int[] i){
while(i[0] != 0) i[0] = i[0] - 1 ;
return 0 ;
}
一个好的优化器可能会进一步优化循环。
据我所知,目前没有Java编译器实现尾递归优化,但没有技术上的理由说它在很多情况下都无法完成。
答案 4 :(得分:5)
实际上,当您将数组作为参数传递给方法时 - 对此数组的引用将在引擎盖下传递。数组本身存储在堆上。并且引用的大小可以是 4 或 8字节(取决于CPU架构,JVM实现等;更重要的是,JLS没有说明有多大引用是在内存中。
另一方面,原始int
值总是只消耗 4个字节并驻留在堆栈上。
答案 5 :(得分:4)
传递数组时,可以通过接收数组的方法修改数组的内容。传递int原语时,接收它们的方法可能不会修改这些原语。这就是为什么有时你可能会使用原语,有时候使用数组。
同样一般来说,在Java编程中,您倾向于支持可读性,并允许JIT编译器完成这种内存优化。
答案 6 :(得分:4)
int数组引用在堆栈帧中实际占用的空间比int原语(8字节对4)要多。您实际上正在使用更多空间。
但我认为人们更喜欢第一种方式的主要原因是因为它更清晰,更清晰。
当涉及更多的投注时,人们实际上做的事情更接近第二。