性能调优、虚拟机、垃圾回收、软硬件协调相关文章和视频 — Part1

最近好几个月,我一直在考虑整理性能调优、Java虚拟机(JVM)、Java垃圾回收(GC)、软硬件协调(Mechanial Sympathy)等主题的文章和视频,最终我决定花时间去做——在我做这件事的时候也许正标志着智力提升的转折点。

感谢Attila-Mihaly给我机会为他的年刊Java Advent Calendar 写下这篇后记。为此我阅读了一系列好符合本文要求的相关Java话题。本文对文章和视频的选择完全是随机的,按照我了解它们的先后进行排序。我的另一个目的是通过这项工作理解和拓宽自己的知识域,同时与他人分享。

本文的内容包含三次演讲,一次Attila Szegedi的演讲,两次Ben Evans的演讲。这些演讲详细地讨论了Java性能和GC方面的主题。Attila的演讲包含了很多他在推特做工程师的经验——所以有很多信息是超出生产系统领域日常经验的。多使用小对象(thin object)而不是富对象(fat object)是他的口头禅。

Ben在他的两次演讲中深度探讨了性能、JVM和GC。他指出了人们对性能,JVM和GC的误解,人们在产品中没有使用特定的运行时参数(run-time flag)。机器底层的工作方式如何?为什么按照这样的方式工作?为了得到最好的输出,应该做什么和不做什么?

下面是我的评论,我决定从Attila Szegedi的演讲开始,因为我挺喜欢这标题……

1、我所知道的关于JVM性能调优的那些事儿  —— Attila Szegedi @推特(视频&幻灯片)

Attila进行这次演讲时正在推特工作,在那里他学到了很多JVM内部实现机制和Java语言本身的知识——在推特,JVM调优、低延时都是标准实践。

他谈到了下面这些有趣的话题:

  • 延迟产生的原因。
  • 已完成的代码不能直接上线。
  • 需要性能调优的领域(主要是内存调优和锁竞争机制的调优)。
  • 内存调优(内存溢出异常 “OOME”、低延时优化、FAT数据)。
  • FAT数据——这是Attila创造的一个新术语,讨论了如何解决由FAT数据引发产生的问题(非常深入而且有趣);学习了在Java或JVM语言中各种数据类型的字节分配。

他还讨论了一些比较深入的话题,例如建议压缩对象指针(这里包含了一个陷阱);通过JVM分析工具发现,Scala 2.7.7中有一些类型是低效的;不要使用Thrift——因为它不是低延时的(low-latency),它们的开销很大——每个对象的开销增加了52到72字节,而且不支持32比特的浮点数;小心本地的线程——它们占用的资源比预期得更多。

译注:Apache Thrift,一个可伸缩的跨语言服务框架。

Attila分享了他对性能三角(Performance triangle)概念的深入理解:GC是JVM最大的威胁;旧代(也称年轻代)使用ConcCollector,新代(也称终身代)使用SWT进程;包含一系列吞吐量(throughput)、低延时的Collector。

通过大小自适应策略来提高性能是GC的改进目标。通过高吞吐量Collector配合)自适应策略(也可以不使用,用标准检查程序 “benchmark”测试这些结果,他带领我们游历了一系列 –XX:+Print等参数(flag)并说明了它们的作用。保持碎片数位于较低水平并避免GC完全停止。这其中包含许多GC工作的技术细节以及如何提高GC性能(同时优化新代和旧代)。

与GC无关的延迟——线程协调优化。在使用线程时可以通过设置障碍(译注:通过避免竞争)减少延迟——可以配置原子值(Automic Value)和原子引用(Automic Refernce)一起使用;使用Cassandra slab allocator(译注:Slab Allocator,一种内存管理机制)可以帮助提高性能和效率——无需自己编写内存管理器。Attila已经不再是“软引用,SoftReference”的粉丝——理论虽好但不实用,GC需要更多的周期才清除软引用对象。

结论

经常去了解你的代码,它们往往是问题的根源所在——框架常常导致性能问题。如果深谙如何更好地使用构成开发环境的基础模块数据结构,那么提高性能还有很多事情可以做。针对JVM维持最佳吞吐量和最佳性能是一个非常困难的游戏。

— 建议观看视频,有很多上文概要没有覆盖到的内容 —

2、Java性能的9大谬误 —— Ben Evans(博客)

在这篇文章中Ben破除了一些Java性能、GC方面的神话和假设。涵盖的内容包括:

  1. Java很慢。
  2. 每行Java代码的功能都是孤立的。
  3. 一个微基准测试(micro-benchmark)验证的内容会和你想象的一样。
  4. 算法通常是导致性能问题的主要原因。
  5. 缓存能解决所有问题。
  6. 所有应用都需要考虑GC完全停止(Stop-the-world)问题(译注:当垃圾收集没有结束前对于外部的请求是不会进行响应的,直到收集完毕应用才会继续响应请求)。
  7. 手工对象池(Hand-rolled Object Pooling)适合绝大部分应用。
  8. 在GC中,CMS比并行旧代收集器总是一个更好的选择。
  9. 增加堆的大小会解决内存问题。

译注:根据Ben的解释实际的情况是:

  • JIT编译的代码很多情况下和C++一样快。
  • JIT编译器可以优化无用以及未使用代码,甚至在做数据分析(Profiling)时也是如此。在JRockit这样的JVM中,JIT可以分解对象的操作。
  • 为了获得最佳结果,不要过早优化,而应该修正潜在的性能问题。
  • Richard Feynman曾经说过:“第一原则是不能欺骗自己,而自己往往是最容易被欺骗的。”——在编写Java微基准测试时须牢记这句话。

问题在于人们心中关于Java的看法与事实相反。建议大家重新思考自己的看法,然后基于事实而不是假设或过去的观点得出自己的结论。

  • GC、数据库存取、错误配置等与问题算法一样会使应用变得缓慢。
  • 估算,而不是猜测!使用实际的生产数据来发现性能问题的真正原因。
  • 不要通过增加缓存把问题转移到别的地方,这样会增加系统的复杂性。要收集基本的数据使用情况(未命中率、命中率),以此证明实际运行中用到了缓存。
  • 如果用户没有抱怨或者没有用低延迟栈(Low-latency Stack)—— 不必担心GC的Stop-the-world暂停问题(根据堆的大小而有所不同,大约暂停间隔是200毫秒)。
  • 对象池使用起来非常困难,只有GC引起的暂停不可接受时才可以使用。智能调优和尝试重构也不能在可接受的程度上减少GC暂停时间。
  • 请确认GMS是否是最适合你的GC策略,首先确认由并行旧代收集器引起的STW(Stop-the-world)问题是不可以接受并且不可优化的。Ben在这里要强调的是:要确保所有数据都从与生产系统匹配的环境中获得
  • 在改变堆内存或其他参数之前,正确理解对象的动态分配和生命周期是很有必要的。没做调查就开始行动只会使事情更糟。在这里,获得GC的分布信息非常重要。

结论

GC子系统有着令人难以置信的优化潜能,可以在生产数据指导下进行调优;可以使用工具来分析log——不管是手写的脚本或工具生成的图形;可以利用可视化工具,比如(开源的)GCViewer 或者商业工具。

3、可视化Java垃圾回收(GC)—— Ben Evans(视频&幻灯片)

通常人们对GC会产生误解或者理解上有欠缺。GC并不仅仅是标记对象清理对象。如今,很多运行时也具备了GC!现在有两种思想流派——GC和引用计数!与机器运行时要求的高精度相对的是,人类往往会犯错。真正的GC是难以置信的高效的,而在Java中引用计数的开销很大。

所有的对象都来自分配列表。在不停止应用的情况下,你没法得到运行中的应用在某个特定时刻所有对象的精准视图,这就是为什么会有STW(Stop-the-world)!

GC黄金定律

  • 必须回收所有垃圾(敏感规则)。
  • 必须不能回收任何活动对象。

(诀窍:任何对象都不会平等创建)

HotSpot是C、C++、汇编应用程序。堆是具备不同内存池的相邻内存块——新代、旧代和永久代内存池。对象由应用(突变体 mutator)创建,由GC移除。由于GC的存在,应用速度不会很慢。

PermG——不可取,在Java 8中被移除了(已知问题:会引发OOME异常),在堆外由Metaspace取代了(本地内存)。

GC是基于“弱代假设(Weak Generation Hypothesis)”——由实证研究发现,对象死于新代或旧代。

Tenuring threshold是指你转移到旧代(Tenuring空间)之前留存的GC数目,JavaFX与jdk7u6及更高版本绑定。

JavaFX内存可视化器使用Java编写源码,取代了之前使用FlexML(FXML)编写的Flash版本——https://github.com/kittylyst/jfx-mem。关于如何使用FlexML编写程序有着广泛的解释。FlexML是一个很好的编程语言,它结合了构造者模式和类似DSL(领域专用语言)的表达式。这个程序为GC如何工作、如何创建、销毁对象并在不同的对象池之间转移建立了模型。

下面列举了必须的命令参数,不会对性能产生任何影响:

  • verbose: gc
  • Xloggc:<pathtofile>
  • XX:+PrintGCDetails
  • XX:+PrintTenuringDistribution

运行应用和GC所需要的所有信息在上文都提到了,并且还包含了设置基本堆大小的参数。设置堆参数进行匹配在新版本的JDK中已不再适用了。此外,还有200多个GC和VM的参数,这还不包括文档中没有提及的参数个数。

GC的工作日志文件在后续处理中很有用,但有时候记录的内容并不完全正确。MXBeans会对运行中的程序有影响,但是比起工作日志文件并没有提供更多的信息。

GC的工作日志文件采用的是一般格式,提供了诸如内存分配变化、内存占用、占用信息、集合信息等等。GC的log文件格式呈现爆发式增长,然而没有很多相关的支持工具。许多免费的工具包含GC的一些指示信息,比如各种统计数据等等;商业工具会提供更好的方法和更有用的信息。对象早熟现象(premature promotion)——在创建对象的压力下,对象在不经过残存空间(Survivor spaces)直接从新代过渡到旧代。

使用工具测量,不要猜测

结论

当不知道一件事情时,去了解事实找到细节,而不是猜测和假设。错误的设想已经多次导致对JVM和GC流程的假设和错误理解。不要仅仅改变参数(flag)或者使用工具,而是要去了解为什么这么做,以及它们究竟做了什么。例如,虽然切换GC工作日志(使用恰当的参数)看不到JVM性能上的显著提高,但是中长期来看会收获良好的效果。

— 强烈建议观看视频,有很多上文没有覆盖到的内容。Ben用最简洁的方式解释了GC,包含了很多重要细节 —

翻阅所有这类的视频和文章是不实际的,所以我选出了一些把链接贴在下文中以便深入学习时提供参考。本文已经转述或者直接引用了这些文章中的内容以及作者想要传递的信息。

有用的资源

原文链接: javacodegeeks 翻译: ImportNew.com - 顾星竹
译文链接: http://www.importnew.com/8147.html
[ 转载请保留原文出处、译者和译文链接。]



相关文章

发表评论

Comment form

(*) 表示必填项

1 条评论

  1. RalphSu 说道:

    Attila分享了他对性能三角(Performance triangle)概念的深入理解:GC是JVM最大的威胁;旧代(也称年轻代)使用ConcCollector,新代(也称终身代)使用SWT进程;包含一系列吞吐量(throughput)、低延时的Collector。


    Sorry, can not type chinese on my linux. The translation is incorrect on the yong generation and old generation. Also it’s STW(stop the world) NOT SWT.

    Thumb up 0 Thumb down 0

跳到底部
返回顶部