JVM 性能调整的一些基本概念

本文是我在工作中调整 Solaris 8 上的 WebLogic 6.0SP2 中遇到诸多问题后,查阅相关资料而产生的一些概念,罗列出来,或许对您有所帮助。这并不代表,笔者推荐您使用 WebLogic 和 Solaris 的组合,相反,笔者欢迎相关 Tomcat 性能调整方面的心得。笔者在 Sun Tech Day 上和 Bea 公司的相关人员讨论后,认为 Bea 对 Open Source 和 Free Software 缺乏必要的远见。

另外,其中一些术语的翻译,是我自己的”创作“,我不知道别人是怎样翻译的。如果有不当的地方,希望指正。

堆( Heap)是 Java 程序的对象生活的地方,包含活的对象,死的对象以及剩余内存。

当对象不能被运行中的程序的指针所到达时,这些对象成为”垃圾“。

JVM 的堆大小决定了 VM 花费在收集垃圾上的时间和频度。

收集垃圾可以接受的速度与应用有关,应该通过分析实际的垃圾收集的时间和频率来调整。

如果堆的大小很大,那么完全垃圾收集就会很慢,但是频度会降低。

如果你把堆的大小和内存的需要一致,完全收集就很快,但是会更加频繁。

调整堆大小的的目的是最小化垃圾收集的时间,以在特定的时间内最大化处理客户的请求。

在基准测试的时候,为保证最好的性能,要把堆的大小设大,保证垃圾收集不在整个基准测试的过程中出现。

堆划分为两个区域:新生代和旧生代。

新生代又分为:Eden 和两片生存空间(survivor spaces)。其中保证有一片空间在任何时间是空的,当垃圾收集发生时, Eden 中的活的对象复制到下一片生存空间,对象就在生存空间之间复制,直到到达最大门限值(老化),然后复制到旧生代。

Eden 是新的对象分配的地方。

很多对象分配以后很快成为垃圾,这些对象称为具有 “infant mortality.”

对象生存的时间越长,需要的收集时间也越长,因此,收集变慢。

你的应用建立和释放对象的速度决定了垃圾收集的频度。因此,在编程时,应注意使用对象的缓冲,而不是新建立对象。

大多数对象在新生代就已经死去,因此你能有效的调整垃圾收集。如果你能安排大多数对象的生存期小于一个收集时间,那么,垃圾收集是十分高效的。

错误的”代“配置会导致频繁的垃圾收集,影响系统性能。

如果系统花费很多的时间收集垃圾,请减小堆大小。

一次完全的垃圾收集应该不超过 3-5 秒。

一般说来,你应该使用物理内存的 80% 作为堆大小。

在最大工作负荷的时候,监视 WebLogic 的性能。
使用 -verbosegc 选项测量有多少时间和资源用于垃圾收集。

打开垃圾收集的详细信息输出以及重定向:

% java -ms64m -mx64m -verbosegc -classpath $CLASSPATH
-Dweblogic.domain=mydomain -Dweblogic.Name=clusterServer1
-Djava.security.policy==/bea/weblogic6x/lib/weblogic.policy
-Dweblogic.management.server=192.168.0.101:7001 -Dweblogic.management.username=system
-Dweblogic.management.password=systemPassword weblogic.Server >> logfile.txt

在 Solaris 系统上,采用下面的命令:

weblogic.Server > server.out 2>&1

Java HotSpot VM 选项

标准的选项在各种平台都已经有介绍:
http://java.sun.com/j2se/1.3/docs/tooldocs/win32/java.html
http://java.sun.com/j2se/1.3/docs/tooldocs/solaris/java.html
http://java.sun.com/j2se/1.3/docs/tooldocs/linux/java.html

以 -X 开头的选项都为非标准选项(并不能在所有的 VM 上实现),在后续的版本中可能会不通知而变更。

由于 -XX 选项需要特别的系统权限,因此不建议随便使用。

在 1.3.0 之前的版本, J2SDK 的 Solaris 版本带有一个虚拟机的实现叫做 Exact VM(EVM),从 1.3.0 开始这个虚拟机被 Java HotSpot VM 所取代。

Java HotSpot VM 目前认识下面的 -X 选项:

-Xincgc 使用训练 GC
-Xnoincgc 不是用训练 GC(缺省)
-XX:MaxHeapFreeRatio= 最大堆剩余百分比(缺省 70)
-X:MinHeapFreeRation= 堆最小剩余百分比(缺省 40)
-Xint 只作解析 (不作 JIT 编译)
-XX:+UseBoundThreads 绑定用户级别的线程 (只针对 Solaris)
-Xmn 设置年轻一代的大小( young generation )(只对 1.4)

对象分配在 Eden,并且在这里死亡,当 Eden 满时,引起一个小的收集(minor collection),一些生存的对象被移动到旧生代,如果旧生代需要收集,则引起大收集(major collection ),这会比较缓慢。

如果 GC 成为瓶颈,那么需要指定代的大小,检查 GC 的详细输出,研究 GC 参数对性能的影响。

旧生代的收集采用 mark-compact 的方式,其中的一部分叫做”永久代“(permanent generation)很特别,他包括了 JVM 自身的所有反映数据(reflective data),例如类以及方法。

暂停时间的含义是应用因为垃圾收集而显示出来的短暂停顿。

吞吐量的含义是在一段比较长的时间内,没有用在垃圾收集的时间和总时间的百分比。

减少暂停时间的办法可以采用小的年轻代和增量收集,但是这以牺牲吞吐率为代价。

Footprint 是一批工作进程的集合,以页和缓冲行数计量,在物理内存有限或者有很多处理器的系统里,footprint 可代表伸缩性。

Promptness 是对象死去的时间和内存变为可用时的时间差,在分布系统中(包括 RMI)需要考虑。

很大的新生代能提高吞吐率,但是牺牲了 footprint 和 promptness。

Solaris 的 footprint 可以采用 pmap 命令来查看。

[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]
上面的三行是 GC 的详细输出,我们可以看到两次小收集和一次大收集。箭头前后的两个数字代表 GC 后活的对象的组合长度。括号内的数字代表合计的空间,等于堆大小减去一片生存空间。

除非你遇到暂停的问题,否则,可以分配足够的内存给 JVM,缺省的 64MB 总是太小。

设置 -Xms 和 -Xmx 为同样的值能提高 JVM 的预测,但是如果你的选择不对的话, JVM 不会补偿。

当增加处理器时,记得增加内存,因为分配可以并行进行,而 GC 不是并行的。

NewSize 和 MaxNewSize 绑定新生代的长度的低端和高端,设置为一样大小时和 -Xms 和 -Xmx 一样解决新生代的预测时间。

如果生存空间太小,拷贝直接进入旧生代,如果太大的话,会空闲在那里。

除非你碰到过渡的大收集或者暂停时间,否则分配足够的内存给新生代,缺省的 MaxNewSize (32MB) 往往太小。

如果需要的话,最大的永久代大小可以使用 MaxPermSize 增加。

直接的 GC 调用可以采用 -XX:+DisableExplicitGC 来关闭。

对于大服务器而言,1.4 的 JVM 能提供 64 bit 寻址能力,提供更大的新生代大小,并发收集来减少大收集引起的暂停的影响。

Java HotSpot Client VM 主要用于减少应用启动时间以及内存的 footprint 。

Java HotSpot Server VM 和 Java HotSpot Client VM 类似,但是在最大性能上作了调整。用于长期运行的服务应用。

Solaris 和 Linux 的 J2SE 1.3 随带 Java HotSpot Server VM 预安装。