一位同事告诉我,在Java对象创建中,您可以执行的最昂贵的操作。因此,我只能得出结论:创建尽可能少的对象。
这似乎在某种程度上破坏了面向对象编程的目的。如果我们不创建对象,那么我们只是在编写一种长类C样式以进行优化?
一位同事告诉我,在Java对象创建中,您可以执行的最昂贵的操作。因此,我只能得出结论:创建尽可能少的对象。
这似乎在某种程度上破坏了面向对象编程的目的。如果我们不创建对象,那么我们只是在编写一种长类C样式以进行优化?
Answers:
您最昂贵的手术就是听他们的。他们浪费了您的时间,将您误导到已经过期十年(截至发布此答案的原始日期)的信息上,以及您不得不花时间在此处发布信息并研究互联网真相。
希望他们只是在无知地反省自己十多年前听到或读过的东西,而他们的情况再好不过了。我也将他们说的话当作可疑的东西,这对任何一种保持最新状态的人来说都是众所周知的谬论。
primitives
)除了基元(int, long, double
,等)以外的所有东西都是Java中的对象。无法避免使用Java创建对象。
在大多数情况下,由于Java的内存分配策略,在Java中创建对象的速度比C ++快,并且与JVM中的所有其他功能相比,从所有实际目的出发,都可以认为它是“免费的”。
早在1990年代末2000年代初,JVM实现确实在对象的实际分配中有一些性能开销。至少从2005年开始就没有这种情况了。
如果您调整-Xms
以支持应用程序正常运行所需的所有内存,则在现代GC实现中,GC可能永远不必运行并清除大部分垃圾,因此寿命很短的程序可能根本就不会使用GC。
它不会尝试最大化可用空间,无论如何这都是一条红鲱鱼,它可以最大化运行时性能。如果这意味着JVM Heap几乎一直都是100%分配的,那就这样吧。可用的JVM堆内存无论如何都不会给您任何好处。
有人误以为GC将以一种有用的方式将内存释放回系统的其余部分,这完全是错误的!
JVM堆不会增长和收缩,因此JVM Heap中的可用内存会对系统的其余部分产生积极影响。-Xms
在启动时分配所有指定的内容,其启发式方法是在JVM实例完全退出之前,不要将任何内存真正释放回OS与其他OS进程共享。-Xms=1GB -Xmx=1GB
不管在给定时间实际创建了多少个对象,都会分配1GB的RAM。有一些设置允许释放一定百分比的堆内存,但是出于所有实际目的,JVM永远无法释放足够的内存来实现这一点因此没有其他进程可以回收此内存,因此JVM Heap的空闲也无法使系统的其余部分受益。2006年11月29日“接受”了此申请的RFE ,但是对此一无所获。权威人士不认为这是行为。
有一个误解,即创建许多小的短期对象会导致JVM长时间暂停,这现在也是错误的
实际上,当前的GC算法已经过优化,可以创建许多寿命很短的小对象,这基本上是每个程序中Java对象的99%启发式。在大多数情况下,尝试进行对象池化实际上会使JVM的性能变差。
今天唯一需要池化的对象是引用JVM 外部有限资源的对象。套接字,文件,数据库连接等,可以重复使用。常规对象的池化含义与允许您直接访问内存位置的语言的含义不同。对象缓存是一个不同的概念,并且可能不是某些人幼稚地称为“ 池化 ”的概念,这两个概念不是同一回事,因此不应混为一谈。
现代的GC算法不存在此问题,因为它们不按计划进行分配,而是在特定一代中需要空闲内存时进行分配。如果堆足够大,则不会发生足够长的释放以引起任何暂停。
_alloca
摊销的要快。
new
在热点地区使用关键字之前,应始终查看构造函数中发生的情况。我见过人们new ImageIcon(Image)
在paint()
Swing对象的方法中使用它,这是非常昂贵的,并且使整个UI变得迟钝。因此,这不是一个黑白答案,请在使用new
某个地方之前先考虑一下。
底线:不要为了获得创建对象的捷径而牺牲设计。避免不必要地创建对象。如果可行,请进行设计以避免冗余操作(任何形式)。
与大多数答案相反-是的,对象分配确实有相关的成本。这是一种低成本,但是您应该避免创建不必要的对象。与应该避免代码中不必要的内容相同。大对象图使GC变慢,意味着执行时间更长,因为您可能将要进行更多的方法调用,触发更多的CPU缓存未命中,并增加在低RAM情况下将进程交换到磁盘的可能性。
在有人大声疾呼这是一个极端情况之前,我已经分析了一些应用程序,这些应用程序在优化之前创建了20 + MB的对象,以便处理约50行数据。这在测试中很好,直到您每分钟最多扩展一百个请求,然后突然每分钟创建2GB数据。如果您想以每秒20请求的速度执行操作,则需要创建400MB的对象,然后将其丢弃。对于一个体面的服务器来说,每秒20请求是很小的。
while(something) { byte[] buffer = new byte[10240]; ... readIntoBuffer(buffer); ...
这可能与...相比浪费byte[] buffer = new byte[10240]; while(something) { ... readIntoBuffer(buffer); ...
。
实际上,由于Java语言(或任何其他托管语言)使内存管理策略成为可能,因此对象创建只不过是增加称为年轻代的内存块中的指针而已。它比必须搜索空闲内存的C快得多。
成本的另一部分是对象销毁,但是很难与C进行比较。集合的成本基于长期保存的对象数量,但是集合的频率取决于创建的对象数量...在最后,它仍然比C风格的内存管理要快得多。
Point
对象可以容纳2个通用寄存器)。
其他张贴者正确地指出,在Java中对象创建非常快,并且在任何普通的Java应用程序中通常都不必担心它。
有一对夫妇的非常特殊的情况下是为避免对象创建一个好主意。
您的同事所说的话有道理。我谨此建议,对象创建的问题实际上是垃圾回收。在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。
在一种情况下,由于开销而可能不鼓励您在Java中创建太多对象- 在Android平台上设计性能
除此之外,以上答案都是正确的。
GC已针对许多短期对象进行了调整
也就是说,如果您可以减少对象分配,则应该
一个示例是在循环中构建字符串,幼稚的方式是
String str = "";
while(someCondition){
//...
str+= appendingString;
}
它String
在每个+=
操作上创建一个新对象(加上a StringBuilder
和新的基础char数组)
您可以轻松地将其重写为:
StringBuilder strB = new StringBuilder();
while(someCondition){
//...
strB.append(appendingString);
}
String str = strB.toString();
这种模式(不变的结果和局部可变的中间值)也可以应用于其他事物
但除此之外,您应该拉起探查器以找到真正的瓶颈,而不是追逐幽灵
StringBuilder
方法的最大优点是预先设置了大小,StringBuilder
因此它不必用StringBuilder(int)
构造函数重新分配基础数组。这使其成为单个分配,而不是1+N
分配。
Joshua Bloch(Java平台创建者之一)在2001年的有效Java一书中写道:
除非维护池中的对象非常重,否则通过维护自己的对象池来避免创建对象是一个坏主意。证明对象池合理的对象的典型示例是数据库连接。建立连接的成本非常高,以至于可以重用这些对象。但是,一般而言,维护自己的对象池会使代码混乱,增加内存占用,并损害性能。现代JVM实现具有高度优化的垃圾收集器,这些垃圾收集器在轻量对象上的性能很容易超过此类对象池。
这实际上取决于特定的应用程序,因此通常很难说。但是,如果对象创建实际上是应用程序中的性能瓶颈,我会感到非常惊讶。即使它们很慢,代码风格的好处也可能会超过性能(除非它实际上对用户而言是显而易见的)
无论如何,在分析了代码以确定实际的性能瓶颈而不是猜测之前,您甚至不必担心这些事情。在此之前,您应该为代码的可读性而不是性能做任何最好的事情。
我认为您的同事必须从不必要的对象创建的角度说过。我的意思是,如果您经常创建同一对象,则最好共享该对象。即使在对象创建很复杂并且占用更多内存的情况下,您也可能希望克隆该对象并避免创建复杂的对象创建过程(但这取决于您的要求)。我认为“对象创建成本很高”这一说法应结合上下文来考虑。
就JVM内存需求而言,请等待Java 8,甚至不需要指定-Xmx,元空间设置将满足JVM内存需求,并且它会自动增长。
除了分配内存之外,创建类还需要更多。还有初始化,我不确定为什么所有答案都没有涵盖该部分。普通类包含一些变量,并进行某种形式的初始化,这不是免费的。根据类是什么,它可能会读取文件或执行其他任何数量的缓慢操作。
因此,在确定它是否免费之前,只需考虑类构造函数的作用。
实际上,Java的GC在以“突发”方式快速创建许多对象方面进行了非常优化。据我了解,他们使用顺序分配器(用于可变大小请求的最快和最简单的O(1)分配器)将这种“突发周期”插入到称为“ Eden空间”的内存空间中,并且仅当对象持久存在时在GC周期结束后,它们会被移到GC可以一次收集它们的地方。
话虽如此,如果您的性能需求变得足够关键(以实际的用户端要求衡量),则对象确实会产生开销,但就创建/分配而言,我不会这么想。它与引用的局部性以及Java中支持反射和动态分派等概念所需的所有对象的额外大小有关(Float
大于float
,通常比64位大4倍,具有对齐要求,并且Float
根据我的理解,不一定保证数组是连续存储的)。
我见过的用Java开发的最引人注目的内容之一就是使我认为它是我的领域(VFX)的重量级竞争者,它是一种交互式多线程标准路径跟踪器(不使用辐照度缓存或BDPT或MLS或其他任何东西) CPU提供实时预览,可以很快地收敛到无噪点的图像。我曾与C ++专业人士一起工作,他们的职业生涯都是由花哨的分析器完成的,他们很难做到这一点。
但是我仔细查看了源代码,尽管它以微不足道的代价使用了很多对象,但路径跟踪器的最关键部分(BVH,三角形和材质)非常清楚并故意避免使用对象,而推荐使用大类型的原始类型(大多float[]
和int[]
),这使得它使用少得多的存储器和保证空间局部性从一个得到float
阵列到下一个英寸 我认为如果作者使用喜欢Float
在那里,它的性能将付出相当大的代价。但是,我们谈论的是引擎最关键的部分,鉴于开发人员熟练地对其进行了优化,我可以肯定的是,他非常明智地对其进行了测量和应用,因为他乐于在其他地方使用对象。他令人印象深刻的实时路径跟踪器的花费微不足道。
一位同事告诉我,在Java对象创建中,您可以执行的最昂贵的操作。因此,我只能得出结论:创建尽可能少的对象。
即使在像我一样对性能至关重要的领域中,如果您在无关紧要的地方束手无策,也不会编写高效的产品。我什至可以断言,最关键的性能领域可能会对生产力提出更高的要求,因为我们需要所有额外的时间,从而可以通过浪费时间来解决那些真正重要的热点, 。就像上面的路径跟踪器示例一样,作者巧妙而明智地仅将这种优化应用于真正重要的地方,并且可能在事后测量之后,仍然很高兴地在其他地方使用了对象。
float[]
vs. Float[]
,顺序处理一百万个对象可能比第二种要快得多。
正如人们所说的那样,对象创建在Java中并不是一个大代价(但是我敢打赌,它比大多数简单的操作(如加法等)还要大),并且您不应该避免过多使用它。
那仍然是一个代价,有时您可能会发现自己试图报废尽可能多的对象。但是仅在剖析表明这是一个问题之后。
这是有关该主题的精彩演讲:https : //www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-ficient-java-tutorial.pdf