一位同事告诉我,在Java对象创建中,您可以执行的最昂贵的操作。因此,我只能得出结论,创建尽可能少的对象。

这似乎有点违反了面向对象编程的目的。如果我们不创建对象,那么我们只是在编写一种长类C样式以进行优化?

评论

“创建Java对象是您可以执行的最昂贵的操作”。介意分享该声明的来源吗?

与-最昂贵的操作相比,是什么?是将pi计算为900位数字还是将int增量为整数?

他们可能一直在谈论特定对象创建的特定部分吗?我在想苹果如何使用tableViewCells的队列。也许您的同事建议创建一些对象并重用它们,因为与那些特定对象相关的一些开销?只是想弄清楚他们为什么会提出这样的要求。

这是我听说过的关于编程的最有趣的事情之一。)

算术也很昂贵。我们应该避免计算,哦,而且启动JVM确实很昂贵,所以我们不应该启动任何JVM :-)。

#1 楼

您的同事不知道他们在说什么。
您最昂贵的业务就是听他们的话。他们浪费了您的时间,将您误导到已经过期十年(截至发布此答案的原始日期)的信息上,以及您不得不花时间在此处发布内容和研究互联网真相。 >希望他们只是在无知地反省自己十多年前听到或读过的东西,而他们的情况再好不过了。我也想说他们怀疑的话,这对任何一个保持最新状态的人来说都是众所周知的谬论。
一切都是对象(primitives除外)
除基本元素以外的一切( int, long, double等)是Java中的对象。无法避免在Java中创建对象。
在Java中,由于其内存分配策略,对象创建在大多数情况下比C ++快,并且从所有实际目的出发,与JVM中的其他所有功能相比,都可以认为是“免费的” 。
早在1990年代末2000年代初,JVM实现确实在对象的实际分配中有一些性能开销。至少从2005年开始就没有这种情况了。
如果调整-Xms以支持应用程序正常运行所需的所有内存,则在现代环境中,GC可能永远不必运行并清除大部分垃圾GC实现,寿命短的程序可能根本就不会使用GC。
它不会尝试最大化可用空间,无论如何这都是一条红线,它可以最大化运行时的性能。如果这意味着JVM Heap几乎一直都是100%分配的,那就这样吧。释放JVM堆内存并不会给您任何东西。
误解是GC会以一种有用的方式将内存释放回系统的其余部分,这完全是错误的!
JVM堆不会增长和缩小,因此JVM堆中的可用内存会对系统的其余部分产生积极影响。 -Xms会在启动时分配所有指定的内容,其启发式方法是在JVM实例完全退出之前,切勿将任何内存真正释放回OS与其他OS进程共享。 -Xms=1GB -Xmx=1GB分配1GB的RAM,无论在任何给定时间实际创建了多少个对象。有一些设置允许释放一定百分比的堆内存,但是出于所有实际目的,JVM永远无法释放足够的内存来实现这一目标,因此其他进程无法回收该内存,因此其余JVM Heap的免费也无法使系统受益。 2006年11月29日“接受”了此申请的RFE,但是对此一无所获。权威人士都不认为这是行为。
有一个误解,认为创建许多小的短期对象会导致JVM长时间暂停,这现在也是错误的。
当前实际上,已对GC算法进行了优化,以创建许多寿命很短的小对象,这基本上是每个程序中Java对象的99%启发式。在大多数情况下,尝试进行对象池化实际上会使JVM的性能变差。
当今唯一需要池化的对象是引用JVM外部有限资源的对象。套接字,文件,数据库连接等,可以重复使用。常规对象的池化含义与允许您直接访问内存位置的语言的含义不同。对象缓存是一个不同的概念,可能不被某些人幼稚地称为“池化”,这两个概念不是同一件事,不应该混为一谈。
现代的GC算法不存在此问题,因为它们不按计划进行分配,而是在特定一代中需要空闲内存时进行分配。如果堆足够大,则不会发生足够长时间的释放以引起任何暂停。
如今,在计算敏感测试中,面向对象的动态语言仍在击败C。

评论


+1:“您最昂贵的手术就是听他们的话……”。我听到一段时间以来最好的。

–rjnilsson
2012年5月22日晚上8:16

@DeadMG,即使有累积的GC开销,Java仍可以比C ++更快(例如,由于进行了堆压缩,从而使某些数据结构的高速缓存未命中最小化)。

– SK-logic
2012年5月22日晚上8:43

@ SK-logic:由于GC是不确定的,因此充其量很难真正证明这一点。至于最大程度地减少缓存未命中,当每个对象都必须是另一个间接对象时,很难做到这一点,这会浪费缓存空间并增加执行时间。但是,我认为,只需在C ++中使用适当的分配器(如对象池或内存竞技场),就可以轻松匹配或击败垃圾收集器的性能。例如,您可以编写一个内存竞技场类,该类的性能将比摊销的_alloca快。

– DeadMG
2012年5月22日8:49



现在,对象创建比以前便宜。它不是免费的。谁告诉你那是在说谎。关于面向对象语言击败C的链接是对真正尝试学习的人的夸张回应。

–贾森克
2012年5月22日在22:08

由于这些答案,我们最终得到了糟糕的代码。正确的答案是用Java创建对象既要创建Java对象又要对其进行初始化。第一部分很便宜,第二部分可能很昂贵。人们在热点地区使用new关键字之前,应始终查看构造函数中发生的情况。我见过人们在Swing对象的paint()方法中使用新的ImageIcon(Image),这非常昂贵,并且使整个UI变得迟钝。因此,这不是一个黑白答案,请在使用新的地方之前先考虑一下。

– qwertzguy
16年6月23日在18:25

#2 楼

底线:
为了获得创建对象的捷径,请不要妥协您的设计。避免不必要地创建对象。如果明智的话,请进行设计以避免任何形式的冗余操作。

与大多数答案相反-是的,对象分配确实有相关的成本。这是一种低成本,但是您应该避免创建不必要的对象。与应避免代码中不必要的内容相同。大对象图会使GC变慢,这意味着执行时间更长,因为您可能将有更多的方法调用,触发更多的CPU缓存未命中,并增加了在低RAM情况下将进程交换到磁盘的可能性。

在任何人都认为这是一个极端情况之前,我已经对应用程序进行了概要分析,这些应用程序在优化之前创建了20 MB以上的对象,以便处理约50行数据。这在测试中很好,直到您每分钟最多扩展一百个请求,然后突然每分钟创建2GB数据。如果您想以每秒20请求的速度执行操作,则需要创建400MB的对象,然后将其丢弃。对于一个像样的服务器来说,每秒20请求是很小的。

评论


我可以添加一个示例:在某些情况下,代码的清晰度实际上并没有什么区别,但是可以在性能上或多或少地产生很大的不同:例如,从流中读取时,通常不使用while(东西){byte [] buffer = new byte [10240]; ... readIntoBuffer(buffer); ...与例如字节[]缓冲区=新字节[10240]; while(something){... readIntoBuffer(buffer); ....

– JimmyB
2012年5月22日上午9:41

+1:不必要的对象创建(或删除后清理)有时有时会很昂贵。 Java中的3D图形/ OpenGL代码是我看到的为使创建的对象数量最少而进行的优化的地方,因为GC否则会对帧速率造成严重破坏。

–狮子座
2012年5月22日下午14:10

哇! 20+ MB可处理50行数据?听起来很疯狂。无论如何,这些对象是长寿的吗?因为对于GC,情况就是如此。另一方面,如果您只是在讨论内存需求,那么这与垃圾回收或对象创建效率无关。

– Andres F.
2012年5月22日23:52



对象是短暂的(通常情况下少于0.5秒)。垃圾量仍然会影响性能。

–贾森克
2012年5月23日23:17

感谢您对现实的坚定支持,任何受过深思熟虑的建筑影响的人都可以与之建立联系。

– J. M. Becker
16年6月19日在20:35

#3 楼

实际上,由于Java语言(或任何其他托管语言)使内存管理策略成为可能,所以对象创建只不过是增加了称为年轻代的内存块中的指针而已。它比必须搜索空闲内存的C快得多。

代价的另一部分是对象销毁,但很难与C进行比较。集合的成本基于关于长期保存的对象数量,但是收集的频率取决于创建的对象数量...最后,它仍然比C样式的内存管理要快得多。

评论


+1即使垃圾收集器“可以正常工作”,每个Java程序员都应该了解分代垃圾收集器。

– benzado
2012年5月22日13:16

较新版本的Java可以进行转义分析,这意味着它可以为不对堆栈中的方法进行转义的对象分配内存,因此清理它们是免费的-垃圾收集器不必处理这些对象,当方法的堆栈框架退绕时(方法返回时),它们将被自动丢弃。

–杰斯珀
2012年5月22日下午14:28

转义分析的下一步是仅在寄存器中为小对象“分配”内存(例如,Point对象可以容纳2个通用寄存器)。

– Joachim Sauer
2012年5月23日下午5:34

@Joachim Sauer:就像在HotSpot VM的较新实现中那样。这称为标量替换。

–someguy
2012年5月24日15:02



@someguy:前一段时间我已经读过它,但是没有继续检查它是否已经完成。得知我们已经有了这个消息,这真是一个好消息。

– Joachim Sauer
2012年5月24日19:31

#4 楼

其他张贴者正确地指出,在Java中对象创建非常快,并且在任何普通的Java应用程序中通常都不必担心它。

有两种非常特殊的情况,即避免创建对象的好主意。


编写延迟敏感的应用程序并希望避免GC暂停时。产生的对象越多,发生的GC越多,暂停的机会就越大。这可能是与游戏,某些媒体应用程序,机械手控制,高频交易等相关的有效考虑因素。解决方案是预先分配您需要的所有对象/数组,然后重新使用它们。有专门提供这种功能的库,例如Javolution。但是可以说,如果您真的很在意低延迟,那么应该使用C / C ++ /汇编程序,而不是Java或C#:-)
避免装箱的原语(Double,Integer等)可能是一个非常有益的微优化方法。某些情况下。由于未装箱的基元(double,int等)避免了每个对象的开销,因此它们是CPU密集型工作,例如数值处理等要快得多。通常,基元数组在Java中的性能很好,因此您希望将它们用于数字运算而不是数字运算任何其他类型的对象。
在受限内存的情况下,由于每个对象的开销很小(取决于JVM的实现,通常为8-16字节),因此您希望减少创建的(活动)对象的数量。在这种情况下,您应该使用少量的大对象或数组来存储数据,而不是使用大量的小对象。


评论


值得注意的是,转义分析将允许将短暂(限寿)对象存储在堆栈中,可以免费释放这些对象ibm.com/developerworks/java/library/j-jtp09275/index.html

–Richard Tingle
2013年6月27日10:36



@Richard:重点-逃逸分析提供了一些非常好的优化。但是,您必须谨慎地依赖它:它不能保证在所有Java实现中和所有情况下都发生。因此,您通常需要进行基准测试以确保确定。

– mikera
13年6月27日在11:43

同样,100%正确的逃逸分析等效于停止问题。在复杂的情况下不要依赖逃逸分析,因为它至少在某些时候可能会给出错误的答案。

–法律
15年1月25日在8:51

第一点和最后一点不适用于快速释放的小对象,它们死于eden(认为C语言中的堆栈分配,几乎是免费的),并且永远不会影响GC时间或内存分配。 Java中的大多数对象分配都属于此类,因此基本上是免费的。第二点仍然有效。

– Bill K
18-4-3在16:32



#5 楼

您的同事所说的话有道理。我谨此建议对象创建的问题实际上是垃圾回收。在C ++中,程序员可以精确地控制内存的释放方式。该程序可以根据需要累积任意长或短的累积时间。此外,C ++程序可以使用与创建该线程不同的线程丢弃该线程。因此,当前正在工作的线程永远不必停止清理。

相反,Java虚拟机(JVM)会定期停止您的代码以回收未使用的内存。大多数Java开发人员从未注意到此暂停,因为该暂停通常很少且很短。您积累的原始数据越多或JVM的约束越多,这些暂停就越频繁。您可以使用VisualVM之类的工具来可视化此过程。

在Java的最新版本中,可以对垃圾回收(GC)算法进行调整。通常,您要暂停的时间越短,虚拟机的开销(即协调GC进程所花费的CPU和内存)就越昂贵。

什么时候这可能重要?每当您关心一致的亚毫秒级响应率时,您都会关心GC。用Java编写的自动交易系统会严重调整JVM,以最大程度地减少暂停时间。在某些情况下,如果系统必须始终保持高度响应,那么本来会写Java的公司会转向C ++。

为记录起见,我一般不容忍对象避免!默认为面向对象的编程。仅当GC妨碍您时才调整此方法,然后才尝试将JVM调整为暂停较少的时间。关于Java性能调优的一本好书是Charlie Hunt和Binu John撰写的Java Performance。

评论


大多数现代JVM支持比“停止世界”更好的垃圾收集算法。垃圾回收所花费的摊销时间通常少于显式调用malloc / free所花费的时间。 C ++内存管理是否也在单独的线程中运行?

–user1249
2012年5月22日下午6:59

Oracle的较新Java版本可以进行转义分析,这意味着不会对方法进行转义的对象将分配到堆栈上,这使它们的清理工作变得自由-当方法返回时,它们将自动释放。

–杰斯珀
2012年5月22日14:37

@ThorbjørnRavnAndersen:真的吗?您是真正的意思是无停顿的GC,还是“通常是并行的,但有时会暂停一点”的GC?我不明白GC如何永远都不会暂停程序,却又无法同时移动对象……从字面上看,对我来说似乎是不可能的……

–user541686
2012年5月23日6:56



@ThorbjørnRavnAndersen:它如何“停止世界”却从不“暂停JVM”?这是没有意义的...

–user541686
2012年5月23日7:52



@ThorbjørnRavnAndersen:不,我真的不;我只是不明白你在说什么。 GC有时会暂停程序,在这种情况下,顾名思义,它不是“无暂停”,或者它永远不会暂停程序(因此,它是“无暂停”),在这种情况下,我不知道该怎么做对应于您的“它无法跟上它就会停止世界”的语句,因为这似乎意味着该程序在GC运行时已暂停。您介意说明哪种情况(是否不停顿吗?),以及如何做到这一点?

–user541686
2012年5月23日在8:03



#6 楼

在某些情况下,可能会因开销而阻止您在Java中创建太多对象-
在Android平台上为提高性能而设计

除此之外,上述答案仍然成立。

评论


再次阅读问题。它未在包括标签在内的任何地方指定JVM。它只提到Java。

– Peter Kelly
2012年5月22日15:08

#7 楼

GC已针对许多短期对象进行了优化

,如果您可以减少对象分配,则应该

一个例子是在循环中构建字符串,这是一种幼稚的方式将是

String str = "";
while(someCondition){
    //...
    str+= appendingString;
}


,它将在每个String操作上创建一个新的+=对象(加上一个StringBuilder和新的底层字符数组)

轻松将其重写为:

StringBuilder strB = new StringBuilder();
while(someCondition){
    //...
    strB.append(appendingString);
}
String str = strB.toString();


该模式(不可变结果和局部可变中间值)也可以应用于其他事物

但除此之外,您应该拉起探查器以找到真正的瓶颈,而不是追逐幽灵

评论


这种StringBuilder方法的最大优点是可以预先调整StringBuilder的大小,这样它就不必使用StringBuilder(int)构造函数重新分配基础数组。这使其成为单个分配,而不是1 + N分配。

–user7519
2012年5月22日18:11

@JarrodRoberson StringBuilder将至少使当前容量翻倍,换句话说,仅对log(n)分配,容量将呈指数增长

–棘轮怪胎
2012年5月22日19:09



#8 楼

Joshua Bloch(Java平台创建者之一)在其2001年的《有效Java》一书中写道:


通过维护自己的对象池来避免对象创建是一个不好的想法,除非对象在游泳池非常重量级。一个证明对象池合理的对象的典型示例是
数据库连接。建立连接的成本很高,以至于可以重用这些对象。
一般来说,维护自己的对象池
会使代码混乱,增加内存占用,并且损害性能。现代JVM实现具有高度优化的垃圾收集器,它们在轻量级对象上的性能很容易超过此类对象池。


评论


即使有了网络连接之类的东西,重要的是不要维护与连接关联的GC分配的对象,而是要维护存在于GC管理的世界之外的一组连接。有时,合并对象本身可能会有所帮助。这些中的大多数源于以下情况:对同一对象的一对引用在语义上等效于对相同但又不同的对象的一对引用,但是仍然具有一些优点。例如,可以检查对相同的五万个字符串的两个引用是否相等。

–超级猫
2014年12月29日20:49在

...比引用两个相同长度但截然不同的字符串要快得多。

–超级猫
2014年12月29日20:49在

#9 楼

这实际上取决于特定的应用程序,因此通常很难说。但是,如果对象创建实际上是应用程序中的性能瓶颈,我会感到非常惊讶。即使它们很慢,代码风格的好处也可能会超过性能(除非它实际上对用户而言是很明显的)

无论如何,您甚至不应该开始担心这些事情,直到您意识到已经分析了您的代码以确定实际的性能瓶颈,而不是猜测。在此之前,您应该尽一切可能最好地执行代码可读性,而不是性能。

评论


在Java的早期版本中,垃圾收集器清理丢弃的对象会产生大量成本。 Java的最新版本大大增强了GC选项,因此对象创建很少成为问题。通常,Web系统受到对外部系统的IO调用的限制,除非您在应用程序服务器上计算真正复杂的内容作为页面加载的一部分。

– greg
2012年5月22日下午2:18

#10 楼

我认为您的同事必须从不必要的对象创建的角度说过。
我的意思是,如果您经常创建相同的对象,则最好共享该对象。
即使对象创建很复杂并且占用更多内存,您可能希望克隆该对象并避免创建复杂的对象创建过程(但这取决于您的要求)。我认为应该在上下文中使用“对象创建成本很高”的语句。

就JVM内存需求而言,请等待Java 8,甚至不需要指定-Xmx,meta空间设置将满足JVM内存需求,并且会自动增长。

评论


如果人们在拒绝任何答案的同时也发表评论,那将是非常有建设性的。毕竟,我们所有人都是在这里共享知识,仅仅在没有适当理由的情况下做出判断就没有任何作用。

–AKS
13年8月2日在11:33

当对该问题发表评论时,堆栈交换应具有某种机制来通知对任何答案投反对票的用户。

–AKS
13年8月2日在16:46

#11 楼

实际上,Java的GC在“突发”方式快速创建大量对象方面进行了非常优化。据我了解,他们使用顺序分配器(用于可变大小请求的最快和最简单的O(1)分配器)将这种“突发周期”插入到称为“ Eden空间”的内存空间中,并且仅当对象持久存在时经过GC周期后,它们会被移到GC可以一次收集它们的地方。

话说回来,如果您的性能需求变得足够关键(以实际用户的标准衡量,最终需求),那么对象确实会产生开销,但就创建/分配而言,我不会这么想。它与引用的局部性以及Java中支持反射和动态调度等概念所需的所有对象的额外大小有关(Float大于float,通常其对齐要求在64位上大4倍,并且Float的数组不一定能保证按照我的理解是连续存储的。)

我见过的用Java开发的最引人注目的东西之一使我认为它是Java的重量级竞争者我的领域(VFX)是CPU上的交互式多线程标准路径跟踪器(不使用辐照度缓存或BDPT或MLS或其他任何东西),可提供实时预览,并迅速收敛为无噪点图像。我曾与C ++专业人士一起工作,他们的职业生涯都是由花哨的分析器完成的,他们很难做到这一点。

但是我仔细看了一下源代码,尽管它以微不足道的代价使用了很多对象,但路径跟踪器的最关键部分(BVH,三角形和材质)非常清楚并故意避免使用对象,而推荐使用大类型的原始类型( (主要是float[]int[]),这使得它使用更少的内存并保证了从数组中的一个float到下一个Float的空间局部性。我认为,如果作者在那里使用像q4312079q这样的盒装类型,那将以很大的代价牺牲其性能,我认为这不是太投机。但是,我们谈论的是引擎最关键的部分,鉴于开发人员熟练地对其进行了优化,我可以肯定的是,他非常明智地对其进行了测量和应用,因为他乐于在其他地方使用对象。





同事告诉我,在Java对象创建中,您可以执行的最昂贵的操作。因此,我只能得出结论,创建尽可能少的对象。




即使在像我一样对性能至关重要的领域,如果您不打算编写高效的产品,你在无关紧要的地方腿。我什至可以断言,最关键的性能领域可能会对生产力提出更高的要求,因为我们需要所有额外的时间,因此可以通过浪费时间在那些不重要的事情上来调整那些真正重要的热点。就像上面的路径跟踪器示例一样,作者巧妙而明智地将此类优化仅应用于真正,真正重要的地方,并且可能是事后测量之后,仍然快乐地在其他地方使用了对象。

评论


您的第一段在正确的轨道上。伊甸园和年轻空间是使用副本收集器,具有大约。死物费用为0。我强烈推荐这个演讲。顺带一提,您意识到这个问题来自2012年,对吧?

– JimmyJames
18/12/20在22:52

@JimmyJames我很无聊,喜欢只筛选一些问题,包括老问题。希望人们不要介意我的巫术! :-D

–user321630
18/12/20在22:54

@JimmyJames装箱的类型不再需要额外的内存并且不能保证在数组中是连续的吗?我倾向于认为对象在Java中非常便宜,但一种情况可能相对昂贵,例如float []与Float [],顺序处理一百万个对象可能比第二种要快得多。

–user321630
18/12/20在22:59

当我第一次开始在这里发布文章时,我也是这样做的。当对旧问题的回答往往很少受到关注时,请不要感到惊讶。

– JimmyJames
18/12/21在14:46

我很确定应该假定盒装类型比原始类型使用更多的空间。围绕它可能存在优化,但总的来说,我认为这正是您所描述的。上面的演示文稿的Gil Tene再次讨论了有关添加特殊类型集合的建议,该集合将提供结构的某些好处,但仍使用对象。这是一个有趣的想法,我不确定JSR的状态。成本很大程度上取决于时间,这就是您所暗示的。如果您可以避免让对象“转义”,则甚至可以为它们分配堆栈,并且永远不要触摸堆。

– JimmyJames
18/12/21在14:53

#12 楼

创建类不仅仅是分配内存。
还有初始化,我不确定为什么所有答案都没有涵盖那一部分。
普通类包含一些变量,并进行某种形式的初始化,这不是免费的。取决于类的种类,它可能会读取文件或执行其他许多慢速操作。

因此,在确定类构造函数是否免费之前,请考虑类构造函数的作用。

评论


出于这个原因,一个设计良好的类不应在其构造函数中进行繁重的工作。例如,您的文件读取类可以通过仅在启动时验证其目标文件存在,并将所有实际的文件操作推迟到第一个需要从文件中获取数据的方法调用时,来降低其实例化成本。

–GordonM
2012年5月24日6:00

如果将额外的费用转移到'if(firstTime)',则比在循环中重新创建类要比重用该类花费更多。

– Alex
2012年5月24日14:19

我将要发布一个类似的答案,我相信这是正确的答案。这么多人对这些说法视而不见,因为创建Java对象很便宜,以至于他们没有意识到通常构造函数或进一步的初始化可能并不便宜。例如,Swing中的ImageIcon类,即使您将其传递给预加载的Image对象,其构造函数也非常昂贵。而且我不同意@ GordonM,JDK中的许多类都在构造函数中执行大部分init,并且我认为它使代码更精简,设计更好。

– qwertzguy
16年6月23日在18:17

#13 楼

正如人们所说的那样,对象创建在Java中并不是一个大代价(但是我敢打赌,比大多数简单的操作(如加法等)还要大),并且您不应该避免过多使用它。

那仍然意味着成本,有时您可能会发现自己想报废尽可能多的对象。但是只有在剖析表明这是一个问题之后。

以下是关于该主题的精彩演讲:https://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-ficient -java-tutorial.pdf

#14 楼

我对此做了一个快速的微基准测试,并在github中提供了完整的源代码。我的结论是,创建对象是否昂贵并不成问题,但是连续创建对象时,GC会为您处理事情的想法将使您的应用程序更快地触发GC进程。 GC是一个非常昂贵的过程,最好在可能的情况下避免使用它,而不要尝试将其插入。

评论


这似乎并没有提供任何对之前15个答案中提出和解释的观点的实质性建议。几乎没有内容值得质疑两年前提出的问题,并且得到了非常彻底的回答

– gna
15年1月25日在20:27