文章
Java
三位顶尖 Java 开发人员 Ron Goldman、Tony Printezis 和 Kirk Pepperdine 与大家分享他们关于垃圾回收的真知灼见。本文为开发人员的真知灼见系列的一部分。
下载:
多年以来,我听到开发人员谈论他们喜爱的代码、最有趣的代码、最优美的代码、如何编写代码、如何不用编写代码、写出好代码的种种障碍、他们对于编写代码的爱和恨,等等。在此过程中,我遇到了许多值得分享的真知灼见。
该系列的第 1 和第 2 部分提供了关于如何写出好代码的建议。在第 3 部分中,开发人员反思了编写代码的实际过程、编写是如何发生的、编写时是什么感觉,以及他们是如何编写代码的。在第 4 部分中,开发人员与大家分享了他们喜爱的代码、最有趣的代码以及有关代码的故事。在第 5 部分,获得业界、学术界、Java 用户群 (JUG) 以及更大社区中 Java 开发人员的特别认可的三位 Java Champion 讨论了云计算和 Java 技术。
在这里,也就是第 6 部分,我们从三个不同但互补的角度来看一看垃圾回收 (GC) 这一挑战性问题。
|
Ron Goldman 是 Oracle 实验室的一名高级研究人员,他从事新的分布式系统软件架构方面的研究。目前他是 Sun SPOT 项目 (http://www.sunspotworld.com/) 的成员,该项目研究 Java 在小型嵌入式无线设备上的应用。他在确定 java.net 网站的前景和细节的过程中发挥了重要作用。他担任过各种开源项目的顾问,包括 NetBeans、OpenOffice 和 Jini。他与人合著了《Innovation Happens Elsewhere:Open Source as Business Strategy》,该书于 2005 年 4 月由 Morgan Kaufmann 出版。Goldman 于 1983 年获斯坦福大学计算机科学博士学位,他曾经是校内机器人小组的成员。
大量研究表明,即使是专家编写的经过严格测试的代码也会存在大量错误。编写一个程序或系统时假定什么都不会出错是不明智的。错误是在所难免的,既可能因实现中的错误也可能因来自环境的意外输入而引起。一段代码中的更改可能影响到相当远的看起来似乎毫无关联的其他代码段,对这种意料之外的交互作用进行彻底的测试是不可能的。只有在执行特定逻辑路径组合时,错误才可能显现出来。
与其在编程过程中全力设法防止出错,不如使用运行时资源来检测已经发生的错误并从中进行恢复,我们觉得后者才是更好的办法。我们来举历史上的一个例子:GC,即自动内存管理。
回顾 GC 的渊源大有裨益。当年 John McCarthy 在设计 LISP 语言时编写过一个程序,其执行符号微分法的算法非常优美。他意识到,该代码将用光内存,也就是说,如果不进行内存释放,最终将耗尽内存。为此,他慎重地做出了这样的决定,那就是他不希望让大量与他真正关心的问题无关的内存记录和簿记弄乱他优美的微分算法。于是他做了一件我们在许多其他场合也将考虑做的事情。他接受了所有程序都会有错误的理念,并创建了一个可以恢复和清理未使用内存的系统,这在某种意义上来说可以回收内存并使内存重新可用。
让内存管理成为一个独立于应用程序的单独进程,这可以让您的应用程序更加干净,使您可以更轻松地编写应用程序,同时也便于专心改进内存管理。对问题的修复和确认能够让系统更强健。如果我们真的想让我们的应用程序强健、可靠和安全,则需要为程序提供超过其自身使用的更多的资源。
尽管自动内存管理已经存在 50 多年了,还还是有许多人因为它看起来不够优美而不希望在系统中采用它。给人的第一印象是它是错误的,因为人们觉得,“这是我的内存。当我知道内存不再被使用时就会释放它。”理论上来说,这可能是正确的,但在实际中,程序员经常会在用完内存时忘记释放,或者出现甚至更糟的情况:在内存还在使用时就释放内存。结果如何?结果是,代码存在错误,经常会意外崩溃。
为什么要强制程序员执行他们并不适合的任务呢?为什么不让计算机尽可能多地接管这些繁琐的工作呢?我们的软件应主动参与维护其完整性。编程语言可以处理低级事务,如检查数组边界和防止缓冲区溢出攻击。对于更高级的事务,如维护系统级约束和提供指定的服务质量,我们需要新的机制。
阅读 Ron Goldman 的完整访谈。
|
Tony Printezis 是 Oracle 公司的技术人员中的一位主要成员,在美国马萨诸塞州伯灵顿工作。从 2006 开始,他一直致力于 Java HotSpot 虚拟机。他将大部分时间用在 Java 平台的动态内存管理的工作上,专注于垃圾回收器的性能、可伸缩性、响应速度、并行性和可视化。他分别于 1995 年和 2000 年获得苏格兰格拉斯哥大学的(荣誉)学士学位和博士学位。此外,他还因其受到高度评价的有关 GC 的 JavaOne 专题讲座而荣获 JavaOne Rock Star 称号。
GC 正朝着更大的堆、更低的延迟以及更多的垃圾回收的方向发展!以下是我曾花大量时间来纠正的关于 GC 的 8 个常见错误观念:
1. 引用计数 GC 将解决所有延迟问题。
2. malloc 和 free 的性能总是比 GC 好。
3. 只要对象变得不可达,就应立即调用终结器。
4. GC 将消除所有内存泄漏。
5. 如果能够显式回收某些重要对象,情况就会好得多,因为我知道这些对象何时不可达。
6. 可以得到既提供高吞吐量又提供极低延迟的 GC。
7. 在代码的重要部分需要禁用 GC。
8. 在系统中可以用 GC 写的任何内容,都可以用 malloc 和 free 来写。
|
Kirk Pepperdine 自 2005 年 9 月起就成为了 Java Champion,他是 javaperformancetuning.com 的主要撰稿人和顾问,该网站被公认为是关于 Java 性能调优信息的首选站点,他还与人合著了《Ant Developer's Handbook》。从开始编程生涯以来,他在应用程序性能领域涉足就很深,并曾使用多种语言进行应用程序调优:Cray 汇编语言、C、Smalltalk,以及(从 1996 年开始使用)Java 编程语言。他目前是 theserverside.com 的独立顾问。
不久前,我在演示文稿中添加了一个幻灯片,说“我将要告诉你们的终将会变成错误的。”我之所这样说是因为随着时间的流逝,技巧会慢慢变得过时,事情需要重新进行评估。更可怕地是,有些技巧一开始就是完全错误的。
这样考虑一下:给您朋友开的处方(药),如果您去服用,结果就可能会生病。性能技巧同样如此。
下面就是一个例子:曾经有人请我对一篇文章进行评论,内容是关于您可以在代码中做些什么来帮助垃圾回收器。该文中的一大技巧是应该在不再需要引用时尽快清空引用。在代码中乱放 myObject = null 语句看起来是不对的。我会反驳说,如果您可以清空一个对象而没有副作用,这个对象就一定是作用域设置不当或作用域设置太宽。比较好的解决方案是缩小对象的作用域,以便在不再需要该值时它就会消失。这个例子说的是,聪明的帮助垃圾回收器的代码实际上是一种代码感觉。
……尽管收集生命周期非常短暂的对象几乎不怎么费事,但高速的对象循环还是可能导致极低效的 GC 数。有时,问题只不过是 Java 虚拟机 (JVM) 没有足够的堆空间。对 GC 活动进行监视可以提供必要的提示,以便您能够成功执行堆大小调整。但在其他情况下,您必须执行一些对象创建分析来确定循环源并在应用程序代码中对其进行处理。
HPJMeter 是一个免费工具,它可以读取冗长的 GC 日志并为您提供该量度。Tagtraum 也有一个可以读取日志并计算 GC 效率的工具。我经常发现,仅仅调整堆空间大小就可以极大地改变 GC 效率、应用程序响应时间和吞吐量。
我想应插入另一个 GC 日志查看器 gchisto。创建者 Tony Printezis 已允许我帮助他添加功能。该查看器已经集成到 VisualVM 中。我还试验了自己的名为 Censum 的工具。它现在还没有准备好发布,但不久将会与大家见面。我已请人给我发送他们的 GC 日志以便使用实际数据进一步进行测试。作为回报,我提供了 GC 日志的读取结果(或茶叶,如果您喜欢的话)。到目前为止,我有了几位接受者,有些来自一些相当著名的站点,这非常令人振奋。我刚参加了慕尼黑的 NetBeans 日,在我演讲的前一夜,我在激动之中将 Censum 集成到了 VisualVM 中,然后成功地在演示中一起提供了它们。这项集成有些局限性,它只能用于运行于同一计算机上的 JVM,但要将其从演示作品变成更强健的作品似乎也不会太费劲。这会是一个有趣的夏季项目。
阅读 Kirk Pepperdine 的完整访谈。