Biaobiaoqi的博客

JVM参数调优:Eclipse启动实践

| Comments

本文主要参考自《深入理解 Java 虚拟机》。这本书是国人写的难得的不是照搬代码注释的且不是废话连篇的技术书,内容涵盖了 Java 从源码到字节码到执行的整个过程,包括了 JVM(Java Virtual Machine)的架构,垃圾收集的介绍等。这里摘录出关于配置 JVM 基本参数来调优 Eclipse 启动的过程,比较初级,供初学者参考。

基础知识

针对 JVM 的参数调优主要集中在数据区大小的控制和垃圾回收策略的选择。关于 JVM 运行机制等更多内容可参考其他博文

JVM 的运行时数据区

运行时 JVM 的数据区主要包括各线程私有的栈和程序计数器,线程共享的方法区,以及管理对象的堆(又称回收堆)等。程序运行时,类信息、常量、静态变量等会被加载到方法区。运行过程中几乎所有对象都在堆里,内存占用的空间最大,这也是最值得优化得部分。

JVM 的垃圾回收策略

Java 程序中,除了基本类型(primitive types),其他的数据都是以对象的形式存在。对象生命周期有长有短,如果无区别的保留在内存中,会造成内存超载。内存垃圾回收(Garbage Collection, 缩写 GC)就是解决这一问题的策略。

注意:JVM 不仅仅只对对象进行垃圾回收,实际上也会对废弃常量和无用的类做回收。

垃圾回收首先得找到需要被回收的对象,一般采用根搜索算法来标记处这些过时的对象(另外有一种简单的实现:引用计数,但存在明显的弊端,即循环引用)。

回收垃圾的过程会消耗计算资源和时间。根据不同的处理方式,垃圾回收有不同的策略,现在常用的是分代收集算法:根据对象的存活周期将堆划分为几代:新生代(Young Generation 或 New Generation)和老生代(Tenured Generation),HotSpot 虚拟机里还分出了永生代(基本等同于方法区)。不同代采用不同的垃圾回收策略。

HotSpot 虚拟机

HotSpot 虚拟机中,Perm 代指永生代,Old 代指老年代,而新生代使用复制算法,将区域划分为三块:Eden,S0 和 S1(S 是 Survivor 的缩写)。

IBM 研究表明,新生代中的对象 98%是朝生夕死的,三者的比例划分是 8:1:1。对象先分配到 Eden,如果 Eden 中内存占用量达到一定得比例,触发 Minor GC,JVM 会将 Eden 和 S0(或 S1)中存活的对象复制到 S1(或 S0),并清空 Eden 和 S0(或 S1)。如果同时老年代的内存占用量打达到一定比例,会触发 Major GC(也称 Full GC)。通常 Major GC 比 Minor GC 慢 10 倍以上。

编译过程

Java 一直号称“Write once, run anywhere”,这个特性正是由 JVM 这一虚拟层来支撑的。

Java 源代码首先编译为 Java 字节码,字节码再被 JVM 加载运行。运行的过程可以是直接针对字节码的解释执行,也可以是经过了 JIT(Just in time)编译为机器码后的执行。另外,还有静态提前编译器(Ahead Of Time,也缩写为 AOT),能将源码直接编译为机器码。

HotSpot 虚拟机的 JIT 编译器有:Client Complier(简称 C1)、Server Complier(简称 C2)以及在 Java7 中堆出的分层编译器。C1 编译器做一些快速的优化,C2 做一些更耗时的优化但是产生更高效的代码,而分层编译器则结合了两者的优点:快速的启动和逐步的优化(brings client startup speeds to the server VM)。

性能监控和故障处理工具

对于系统调优和问题定位,周志明在《深入 Java 虚拟机》中总结到

给一个系统定位问题的时候,知识、经验是关键基础,数据是依据,工具是运用知识处理数据的手段。这里说的数据包括:运行日志、异常堆栈、GC 日志、线程快照、堆转储快照等……应当意识到工具永远都是知识技能的一层包装,没有什么工具是“秘密武器”。

Java 提供了很多工具给开发者来监控和处理运行中的问题。包括命令行工具以及可视化工具

命令行工具

比如 jps, jstat, jinfo 等。举例如下:

1
2
3
jstat -gcutil xxx #xxx 是 jps 查出的 LVMID,查看 gc 相关数据
jstat -gccause xxx#查看 gc 的原因
jinfo  -flag XXX xxx#XXX 是参数名,xxx 是 VMID,查看虚拟机的参数值

可视化工具

  • JConsole:比较老得分析软件,Java 自带。Windows 下搜 jconsole.exe,Mac 下启动命令为 jconsole
  • Eclipse Memory Analyzer Tool:用于分析 dump 下的堆数据
  • VisualVM:推荐,很全能的分析工具

JVM 参数

这里零散的罗列了一些我用到的简单的 JVM 配置参数:

内存大小控制:

  • -Xmx20M: 堆的最大值
  • -Xms10M: 堆的初始分配内存
  • -Xmn: 新生代堆的分配内存
  • -Xss128k: 线程的栈空间大小
  • -XX:PermSize=10M | 方法区初始大小
  • -XX:MaxPermSize=10M |方法区最大值

编译相关:

  • -Xint: 关闭 JIT,完全使用解释执行,实践中没什么作用,解释执行很慢。
  • -client: 启动 C1 编译器
  • -server: 启动 C2 编译器
  • -XX:+TieredCompilation: 启动分层编译器

其他:

  • -XX:+DisableExplicitGC: 屏蔽 System.gc()调用
  • -PrintGCDetails: 让 jvm 在每次发生 gc 的时候打印日志,利于分析 gc 的原因和状况
  • -XX:+HeapDumpOnOutOfMemoryError: 内存溢出时 dump 下 heap,可以通过 Eclipse Memory Analyzer Tool 打开查看。
  • -XX:HeapDumpPath=/: dump 下的 heap 文件快照的路径。Eclipse 中默认放到项目的根目录中
  • -XVerify:none: 禁止字节码验证

调优 Eclipse 启动的实践

实践环境

  • CPU: 2.8 GHz Intel Core i7
  • 内存: 8 GB 1333 MHz DDR3
  • 操作系统: OS X 10.9.1(64bits)
  • Eclipse:Version: Kepler Service Release 1, Build id: 20130919-0819
  • Java:java version “1.7.0_45”

调优实践

调优 Eclipse 启动实际上就是调优 Eclipse 在 JVM 中的加载和程序启动阶段的运行。由于默认的 Ecpise 启动配置无法适应所有不同的硬件、软件环境,做针对性的调优是必要的。

Eclipse 的启动配置文件是 eclipse.ini,对 JVM 的参数调优直接在该文件中修改。OS X 下,其文件路径为 $ECLIPSE/Eclipse.app/Contents/MacOS/eclipse.ini(注意不是 Eclipse 文件包根目录下得 eclipse.ini)。

测 Eclipse 启动时间

要优化 Eclipse 的启动时间,先要能确定 Eclipse 的启动时间。这里推荐网友实现的一个 Eclipse 插件:计算启动时间的 Eclipse 插件。下载后放到 Eclipse 的插件包中,启动 Eclipse 即可看到弹窗显示的启动时间。为了得到一个尽可能公平的测试结果,需要在测试过程中关闭其他程序,避免 CPU 负载带来的误差,并多次测试取平均值。

调优策略

使用 VisualVM 查看程序的运行状况来定位瓶颈,尝试调优解决。下图是 VisualVM 的示例图,右边图示展示了 GC 的状态以及编译时间、类加载时间和垃圾回收时间等指标。

img

也可以通过命令行工具查看 GC 的状态,比如:jstat -gc XXX #其中 XXX 是 jps 查出的进程的 LVMID.

我的实践总结如下:

  • 类加载时间过长:禁止加载类时的字节码验证。我们认为 Eclipse 的字节码是可靠的。-XVerify:none
  • 编译时间:-client缩短了编译时间,但长期运行的性能可能受影响,-server编译时间长,而编译优化做得更多,后期使用汇报大,-XX:+TieredCompilation分层编译则集合了前两者的优势。没有特殊需求,可选用这一编译选项。
  • Minor GC 次数太多:新生代空间太小,加大新生代的内存大小。-Xmn800m,同时,增大回收堆的总大小上限(-Xmx1024m)。
  • Full GC 次数太多:为了避免回收堆由小到大的动态增长增加时间开销,可将其初始大小跟最大上限设定为同一值-Xms1024m,并增加老年代的大小(-XX:PermSize=256m-XX:MaxPermSize=256m)。
  • 代码中的 GC 调用:Eclipse 代码中有 System.GC()的调用可能促使 JVM 执行垃圾回收,可以通过-XX:+DisableExplicitGC来防止。
  • 垃圾收集器的选择:垃圾收集器有很多,比如 Serial、ParNew、Parallel Scavenge、CMS、G1 等。推荐使用老生代 CMS 新生代 ParNew 的组合来应对 Eclipse 用户交互频繁的情况(-XX:+UseParNewGC-XX:+UserConcMarkSweepGC)。同时提升 CMS 的垃圾回收的触发条件:-XX:CMSInitiatingOccupancyFraction=85,进一步降低 Full GC 的出现。

最终 eclipse.ini

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
-startup
../../../plugins/org.eclipse.equinox.launcher_1.3.0.v20130327-1440.jar
--launcher.library
../../../plugins/org.eclipse.equinox.launcher.cocoa.macosx.x86_64_1.1.200.v20130807-1835
-product
org.eclipse.epp.package.standard.product
--launcher.defaultAction
openFile
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
256m
--launcher.defaultAction
openFile
--launcher.appendVmargs
-vmargs
-Dosgi.requiredJavaVersion=1.6
-XstartOnFirstThread
-Dorg.eclipse.swt.internal.carbon.smallFonts
-Xms1024m
-Xmx1024m
-Xmn800m
-Xdock:icon=../Resources/Eclipse.icns
-XstartOnFirstThread
-Dorg.eclipse.swt.internal.carbon.smallFonts
-XX:+TieredCompilation
-XX:PermSize=256m
-XX:MaxPermSize=256m
-XX:+DisableExplicitGC
-XVerify:none
-XX:+UseParNewGC
-XX:+UserConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=85

其他参考