为了回答这个问题,需要先将该问题分成几个可能引起慢的原因:
垃圾回收器。这是一把“双刃剑”。如果你的程序遵循“大部分对象都在年青代中消亡”模型,垃圾回收器是非常有利的(很少的碎片,更好的缓存局部性)。但是,如果程序不遵循该模型,JVM将花费很多资源来回收堆内存。
大对象。在Java中,所有的对象都有一个vtable指针,而C++中使用POD结构没有额外开销。此外,所有的Java对象是可以被锁定的。其实现依赖于JVM,这可能需要在对象中增加额外的字段。大对象 == 缓存更少的对象 == 更慢。(另一方面,Java 7 用64位记录压缩后的指针,这也是造成该问题的一部分原因。
缺乏内联对象。在Java中,所有的类都是指针。在C++中,对象可以和其它对象一起分配,或者在栈上分配。这样可以提高缓存的局部性,从而减少动态内存分配的开销。
平台函数调用。在Java中,JNI的调用或者将对象编译成本地代码都会带来不小的开销。如果你需要频繁调用客户端的C++代码,会增加很大的开销。
低效的强制抽象。例如,在Java中字符串是不可变的。如果你想写一个XML分析器,你只使用String对象(没有char[]),它将会很慢,因为需要分配额外的空间。
虚函数调用增加。JVM中,几乎所有的函数调用都是虚函数调用。有许多代码尝试避免虚函数调用,但是很多场景下,JVM无法解决这个问题。这阻碍了代码的内联,使代码变慢。
缺乏高级的编译特征及转为汇编的能力。 如果你写了一段能从汇编得益的代码Java可能表现不佳。
在我看来,最大的问题是垃圾回收。在程序中,强制在大的内存中进行多次完全GC,是最容易导致Java和C++之间产生鸿沟的原因之一。除此之外,如果将程序的工作集放在L2缓存之外,像大对象、缺乏内联对象等问题,也会导致两者之间的巨大差别。
低效的强制抽象和平台函数也会导致速度下降,但是这通常只会因为低级的代码才会产生。如果你使用写得很好的Java代码库,这通常不是什么大问题。
我基本同意Ben Maurer(hey Ben!)的回答。有几个小点不同:
在最新的JVM中,当这种分配永远不会从(a)局部函数或(b)局部线程逃逸出去的时候,逃逸分析能有效地决定一种固定分配。也就是说当分配不需要加锁,通常是在自身的栈空间上进行的。这两种情况下都是一种简单的“指针碰撞(bump the pointer)”分配,这等同于C中的栈分配。
译者注:
即使没有逃逸分析,年青代的分配也是通过指针碰撞方式,在线程本地分配缓冲区(TLAB)中完成的,不需要进行同步。所以Java中小对象的分配有的时候比C语言实现的 malloc() 方式更快。更好的 malloc 方法像Google的 tcmalloc,采用了类似的方式。但是由于C语言无法在内存中对分配后的对象重新分配,所以某些方面会受到限制。
虽然存在内联和虚函数问题,但是实际上,Java在某些情况下甚至可以做的比C更好。特别是,C不能通过动态链接功能来实现内联,因为内联是在编译时期进行的,而不是运行时期。而Java可越过不同的类或库的边界来动态内联一个函数,即使该类的真正实现在编译期间还不可用。许多工作中,这种方式比C++的虚函数调用更有效,C++虚函数调用总是需要调用虚表。而JIT编译器,如果之前动态属性已经丢失(如新的类已经被加载),能够聪明地取消内联优化。
新版本的GCC提供一些这方面优化,称为“全程序优化”或“链接时优化”( http://gcc.gnu.org/wiki/LinkTime …),允许在工程范围内越过对象文件进行内联。但是,基本上还是不允许通过动态链接的方式来实现内联(如通过内联的方式实现zlib的调用等)。许多大型项目都是通过复制标准库的功能到它们的代码中来实现。
原文链接: quora 翻译:ImportNew.com -paddx
译文链接:[]