专栏名称: OSC开源社区
OSChina 开源中国 官方微信账号
目录
相关文章推荐
OSC开源社区  ·  使用DeepSeek拯救数据中台 ·  昨天  
码农翻身  ·  近期尽量不要随便网购了! ·  3 天前  
51好读  ›  专栏  ›  OSC开源社区

似乎我对 Java 存在误解(1)

OSC开源社区  · 公众号  · 程序员  · 2016-12-17 08:20

正文



OSC 协作翻译

英文原文 Maybe I Was Wrong about Java – Part 1

链接: https://www.sitepoint.com/maybe-i-was-wrong-about-java-i/

译者: Robbie_Zhu, leoxu, Tocy, Viyi, Tony, 一刀, 花间_拾零, 白羊沈歌, 无若, snake_007, sword_87


我不是最狂热的 Java 粉,虽然也会在某些需要的情况下用到 Java 代码,但仍然不太习惯。和其它不怎么使用 Java 的人一样,我觉得 Java 有些刻板:它占内存且运行慢,虽然在编程界口碑不错,但也并不是那么好用。


我因为精心解析 PHP 这篇文章与大家结缘。在我写出这篇文章之前,我认为 PHP 挺难摸透的,还为此遭受了不少挫折。而对于 Java,我在试图了解它的同时,会觉得它……无趣。


换个角度说。PHP 流行是因为它有一个独特的优势:它抛弃了 Web 开发中一些赘余的步骤。在这一点上,Java 没有能与之相比的显著优势,但它一定有它自己擅长的领域,否则 Oracle 就不会吹嘘有9万亿设备都在运行 Java了;Java 也不会有胆量支持如此大量的大型网站;占据着主导地位的智能手机操作系统也不会建立在 Java 之上。我可不会想要一个运行 JVM 的烤面包机。

(它会比想像的工作得好,但需要花点时间等它预热。)


所以也许 — 也许 — 我误解了 Java。也许我对它的看法存在偏见。也许吧。


或许我应该重新审视自己对 Java 先入为主的观念。只要你愿意,也应该消除成见。


介绍一下,我涉猎了许多种语言,但我主要是做 Python,我喜欢它的很多设计模式。Python 有一些概念与 Java 相同,这也是一个很有趣的比较点。


Java 很慢


我觉得 Java 内存大运行慢。这句话一直出现在我有脑袋里。每当我想到 Java 的时候,就会想起在桌面应用中遇到的一些阻碍。最臭名昭著的可能是 Azureus —— Java 写的 BitTorrent 客户端,在十多年前大家用它来下载 Ubuntu 的发行版。但它只能用来下载文件,而且还出人意料的慢。所以当 µTorrent 出现的时候,大家都换过去了。µTorrent 是 C++ 写的,更快更高效。


这都不重要。我在那些年唯一使用的 BT 客户端是 Deluge,它是用 Python 写的。在所有测试中,Python 都没有 Java 快,但我用 Deluge 一直没问题。有时候使用 JVM,是因为某些场景下它真的很快。那什么时候我又会觉得它慢呢?


我们都知道,现在要做一项真实的测试是很困难的;有许多因素影响着速度,一个程序与自身进行比较都不全面,更不要说比较两个完全不同的软件。某个机制陷入困境了吗?GC 发生得不是时候?某种语言在进行特定任务的时候会更好一些?这些因素通常不可控,即使我能控制,测量对速度的感知仍然是棘手的事情。那么,除了这些,我能做的最好的事情就只有思考。


慢的程序,慢的语言


我怀疑我在这里有点偏见。如果程序慢,我们会责怪程序——但是如果 Java 程序慢我们就会责怪 Java。在这点上对 Java 特别的不利。由于 Swing 太显眼了 ,很容易注意到 GUI 是用 Java 写的,但是很难确定是不是 C++ 或其他类似的写的。


以我的经验来看,这在博弈圈特别明显,在那里你可以从从来没有写过一行代码的人身上发现对博弈执行的深入了解。 Minecraft 缓慢是因为它是用 Java 写的,你看,然而 Starbound 慢是因为开发者没有“优化”它,不管是什么没有优化。

还有一个可能的因素,但我不确定这是否真实:在我的印象中许多开发者使用 C++ 是因为他们想用,而用 Java 是因为他们不得不用?我看到过许多人使用 C++ 代码都没有什么特别的理由,但是许多使用 Java 代码的人都会说“我使用 Rails 编写,当它太慢时就移植”(或者“它是为 Android 开发的,因此我不得不使用 Java”)。


换句话来说就是,Java 似乎是专门用来加速那些已经太而慢的代码的,因此让 Java 程序来处理非常繁重的任务是合理的。这样来看,这不怪 Java 慢,使用 Java 写的往往是一些慢的程序。这其实是一个自我选择的问题。


与此同时,许多 C++ 写的是纯粹出于跟风。或者更确切地说,有些程序员只想着获得最大性能而不管他们是否需要。因为他们认为冒虚假的段错误和内存错误的风险来反对“裸机”是完全值得的。因此那些为了使用 C++ 获得高性能而使用 C++ 的做法未必合理。



问题是不是出在 JIT 上面?


我原本试图将运行缓慢的问题归结到 JIT 的热启动这个上面,但又不确定是不是因为它。因为用 PyPy 比较多,所以我已经开始更加频繁的对 JIT 的热启动进行体验。PyPy 是一个 JIT 的 Python 实现 (使用其自身来编写,并因此而得名), 而阻碍它被更大范围应用的一大障碍就是启动要花费好几秒的热身时间。 Python 程序在使用 PyPy 运行时会有明显的延迟,而使用原装 Python 则基本上都是立即就执行了 — 而由于 Python 普遍被用于命令行工具,这样的延迟会对相当多的程序带来影响。


不过对于那些要运行超过半分钟或者更久的程序来说,PyPy 的速度显然快得多。我期望 JVM 会有一个比相对而言比较年轻 PyPy 更加先进的 JIT,那样的话热身时间一定会更短。我怀疑 Java 之所以会有一个运行缓慢的名声,只是因为它在开始运行的 20 或者 30 多秒时间里比较慢而已。


我也曾观察到使用了 LÖVE 的 LuaJIT 的游戏引擎, JIT 启动会实时地发生。如果你在屏幕上将帧率显示出来的话,会发现它会在几秒钟之内从一开始的 10 左右呈斜坡趋势上升到 60。此后的运行就正常了。这里的减速耗时还是相当短的,而且并没有让我觉得引擎或者 JIT 运行缓慢。


作为玩具的 Java


我敢打赌如果要追溯 Java 发展,那一定有很多要说。一些 C++ 开发者当然会把 Java 当做 C++ 的再创造,但它比 C++ 要慢... 原来的 Sun JVM 好几年都没有一个 JIT,因此早起的 Java 很像现在的 Python,是以字节码解释的方式运行的 — 不过那会儿是运行在要比在慢得多的硬件上。


从文化观念的角度来看这也是有意义的。Java 是作为一种“严肃的”语言而出现 — 例如它是 C++ 的一个竞争者 — 而当时唯一“严肃的”语言就是 C++。任何一种就像是解释型的语言在很大程度上都会被视作一个玩具。如果将“人们可以拿来用于进行严肃开发的语言”范围从 C++ 延伸到 Java 的话, 那么 Java 理所当然就像一个笨重的野兽了。自动垃圾收集? 这是什么?小孩子玩意儿么? 我甚至认为这连语言都区分不了。


快进二十年,我的操作系统有一半是用 JavaScript 写的。我甚至曾用 Perl(yolo) 写过一个桌面应用。难道相比之下 Java 会更好?


Java 很臃肿


我不喜欢 “臃肿”这个词。因为它代表不了任何优势,换句话说,它表示的性能是消极的,也是我们不在意的。


曾经有人对我说他们不用 GIMP 来艺术创作,因为它跟臃肿;还有人说他们不喜欢简单的 Paint.NET,因为它缺乏一个明确的功能特性,但是 GIMP 包含该特性。


为了更准确地说明这个问题,下面用一些例子来说明“臃肿”的含义。


Java 程序占用大量内存


一提起 java 的内存,我就会想起我在 TECHCOMPANY 工作时候的一个故事。那时候我负责我们的构建系统,使用 Closure 编译器,这是一个 Java 编写的 JavaScript-to-JavaScript 的"编译器"。


这个构建系统运行 Closure 时使用了参数 java -Xmx1024M,将最大堆内存设置成 1GB,但是手动使用 Closure 的时候,我经常会忘记这个奇怪的 -Xmx 参数,然后 JVM 马上就会挂掉。后来我发现了有个 _JAVA_OPTIONS 的环境变量可以保存这些参数,不用我每次去记它。但是即使我加上了,这个现象还是会出现,最后聪明的我仔细研究后找到原因。


我们使用的是共享的开发机器,每台 64G 的内存,并且设置了 ulimit -v 为 5GB 大小,防止任何进程分配的内存超过这个大小。默认情况下,JVM 在启动的时候会分配机器物理内存的1/4,很明显这个内存分配超过了 5GB 然后失败了,这时候 JVM 就立刻关闭了。(文档建议堆内存默认至少 1GB,但是这种说法可能不准确)


这个问题其实不能怪 JVM,Linux 会尽可能的将机器内存分配给进程使用,只要不用完所有的机器内存,一切都工作的很好。JVM 利用这个好处提前分配一个超大内存块,从而避免了后面多次分配工作(耗时操作)。ulimit 可防止某个进程随意占用大量内存的进程而导致机器挂掉,但是它设定的这个限制值没有任何意义(悲哀,ulimit -m 原本是用来限制实际内存使用的,从 Linux 2.4 之后就没实际意义了)


在我脑海中,有这么一个印象"一次 Java 需要 16GB 内存"。这样容易让人误解的就是 Java 完全就是个内存黑洞啊,其实这种说法是有失偏颇的。就算 Java 本身好像需要这么多内存,但是如果我想想之前的事,就不会再这么认为了。


我能想到的绝大部分性能原因是:当 Java 程序变得庞大的时候会被谴责,但是 C++ 程序变得巨大的时候却不会被谴责;似乎复杂的任务用 Java 来实现就会挂掉;C++ 开发者鄙视垃圾回收和对象开销。


对于单个应用程序来讲,速度已经很难来量化,因而程序大小变成决定因素。每次运行同样类型的程序,同样的值占用的内存是一样的。例如,我们知道一个 CPython 对象占用16字节——一个类型指针和一个引用计数。我不是很确定 Java 中的对象占用——实际的内存使用取决于虚拟机的实现细节,并且 Java 中有很多虚拟机可以使用。(我只知道我所熟悉的 CPython)。但是我严重怀疑 JVM 会比 Python 用的内存更多,不过很多时候它需要的内存要少很多——Python 没有原始数据类型。所以, Java 的表现不应该比 Python 还要差,我并不认为 Python 是内存黑洞,因此我敢肯定 Java 也不是。


伪科学


我们大胆的推测,如果内存大小更能说明问题,那么让我们来做一些测试。这里我们简单地来与几个任意选择的桌面应用程初始内存的使用量进行对比,可能缺乏科学依据。


● XMind (Java), 一个思维导图器: 416 MB

yEd (Java), 图形编辑器(如流程图所示,不是y =x²): 372 MB

● jDiskReport (Java), 一个磁盘空间上报的东西: 228 MB

● muCommander (Java), 一个MC风格的文件浏览器: 183 MB

● FreeMind (Java), 另一个思维导图器: 181 MB

● SLADE (C++/wxWidgets), Doom地图编辑器: 93 MB

● GIMP (C++/GTK), 图像编辑器: 88 MB

● LMMS (C++/Qt), 一个数字音频工作站: 75 MB

● Deluge (Python/GTK), 一个BitTorrent客户端: 85 MB


(顺便说一下,所有的 Java 程序启动至少有 11GB 虚拟空间 --- 除了 FreeMind,因为它带有一个添加 -Xmx的shell 脚本。即使一个简单的 hello-world Java 程序都占用了 10.5 GB 的空间,所以默认初始堆的空间看起来仍然很大,我有 32 GB 的物理内存,所以一个四分之一将是 8 GB;其他的 10.5+ GB 的值我不知道来自哪里)。


即使了解到有许多因素正式针对 Java,我也没想到会有那么多反对意见。GTK 和 Qt 或许可以同其它进程共享一个库,Python UI 甚至都会对这些库进行封装并将它的许多数据放到 C 的层面来进行存储;Swing 几乎整个都立足于 Java 之中,而且只能在打开的进程中使用到。当然 Java 拥有一整个运行时 (似乎得用掉至少 26 MB 的空间), 而 C++ 的 “运行时”则要微观不少。Python 也有一个运行时, 不过 Deluge 比其它的要简单许多; 它只不过是我手上的一个非 C++ 类的东西而已。而 XMind 以及 yEd 则因为某些方面的原因,并没有什么代表性。


Java (就像 Python 一样) 因为反射和栈跟踪方面的缘故,也需要记住许多由遗留的 C++程序所产生的调试信息。垃圾收集往往会为了速度而牺牲备用内存;我并不了解 JVM GC 的运行细节, 但见过 GC 计算开销占到 100% 之多, 因此如果这会是一个重要因素的话,我并不会感到惊讶。我想说的是 GC 也会有很难解决的碎片问题。


Java 的字符串比较浪费


怀疑另外一个罪魁祸首就是 Java 的字符串类型。Java 字符串是“Unicode”的, 采用的是 1990 年代定义的“Unicode”。Unicode 曾承诺它永远不会超过 65,536 个字符,因此两个字节就足够把任何东西都表示出来。Windows 的 AP,至少有一个 GUI 的工具包,Java,JavaScript (有可能是从 Java 抄过来的),以及其它语言似乎都对这上了心,傻乎乎地将字符串声明为全都使用俩个字节的字符。字符编码从此一劳永逸。








请到「今天看啥」查看全文