常用的Java虚拟机参数
1.跟踪调试参数
在虚拟机的运行过程中,如果可以跟踪系统的运行状态,那么对于问题的故障排查会有一定的帮助。因此虚拟机提供了一些跟踪系统状态的参数,使用给定的参数执行Java虚拟机,就可以在系统运行时打印日志,用于问题分析。
1.1 跟踪垃圾回收器
Java的一大特色就是支持自动的垃圾回收(GC),但是有时候,如果垃圾回收频繁出现,或者占用了太长的CPU时间,就不得不引起重视。此时,就需要一些跟踪参数来进一步甄别垃圾回收器的效率和效果。
- -XX:+PrintGC--简单打印GC日志
- 使用这个参数启动Java虚拟机后,只要遇到GC,就会打印日志
- 例题代码:
- 运行结果
- 结果分析
该日志显示,一共进行了一次GC和一个FullGC,每次GC占用一行,在GC前,堆空间使用量约为13MB,两次GC后,堆空间使用量为599KB,当前可用的堆空间总和约为485MB(498688KB)。最后,显示的是本次GC所花费的时间。
- -XX:+PrintGCDetails--详细打印GC日志
- 例题代码同上
- 运行结果
- 结果分析
从这个输出中可以看到,系统经历了2次GC,第1次仅为新生代GC,回收的效果是新生代从回收前的11MB左右降低到1MB。第2次为FullGC,它同时回收了新生代、老年代和元数据。日志显示,新生代在这次GC中全部释放空间,老年代从832K降低到了603K。元数据区的大没有变化。在日志的最后,显示了GC所花费的时间,其中usr表示用户态CPU耗时,sys表示系统CPU耗时,real表示GC实际经历的时间。
还会使虚拟机在退出前打印堆的详细信息,详细信息描述了当前堆的各个区间的使用情况。如上输出所示,当前新生代(PSYoungGen)总大小为151552KB,已使用6504KB。紧跟其后的3个16进制数字表示新生代的下界、当前上界和上界。使用上界减去下界就能得到当前堆空间的最大值,使用当前上界减去下界,就是当前虚拟机已经为程序分配的空间大小。如果当前上界等于下界,说明当前的堆空间已经没有扩大的可能。
- -XX:+PrintHeapAtGC--打印全面的Java堆信息
- 例题代码同上
- 运行结果(只截图了部分结果)
- 结果分析
可以看到在GC日志输出前后,都有详细的堆信息输出,分别表示GC回收前和GC回收后的堆信息,使用这个参数,可以很好地观察GC对堆空间的影响。
- -XX:+PrintGCTimeStamps
- 该参数会在每次GC发生时,额外输出GC发生的时间,该输出时间为虚拟机启动后的时间偏移量。
- -XX:+PrintGCApplicationConcurrentTime
- -XX:+PrintGCApplicationStoppedTime
- 使用参数可以打印应用程序由于GC而产生的停顿时间
- -XX:+PrintReferenceGC
- 使用该参数可以跟踪系统内的软引用,弱引用,虚引用和Finallize队列
- -Xloggc:log/gc.log
- 默认情况下,GC的日志会在控制台中输出,这不便于后续分析和定位问题。为此,使用该参数-Xloggc:log/gc.log启动虚拟机,可以在当前目录下的log文件夹下的gc.log文件中记录所有的GC日志。
1.2 类的加载/卸载的跟踪
Java程序的运行离不开类的加载,为了更好地理解程序的执行,有时候需要知道系统加载了哪些类。一般情况下,系统加载的类存在于文件系统中,以jar的形式打包或者以class文件的形式存在,可以直接通过文件系统查看。但是随着动态代理、AOP等技术的普遍使用,系统也极有可能在运行时动态生成某些类,这些类相对比较隐蔽,无法通过文件系统找到,为此,虚拟机提供的类加载/卸载跟踪参数就显得格外有意义。
- -verbose:class
- -XX:+TraceClassLoading
- -XX:+TraceClassUnloading
- -XX:+PrintClassHistogram
- 使用该参数可以在运行时打印、查看系统中类的分布情况,只要在系统启动时加上该参数参数,然后在Java的控制台中按下Ctrl+Break组合键,控制台上就会显示当前的类信息柱状图。
1.3 系统参数查看
由于目前的Java虚拟机支持众多的可配参数,不同的参数可能对系统的执行效果有较大的影响,因此,有必要明确当前系统的实际运行参数。虚拟机提供了一些手段来帮助研发人员获
得这些参数。
- -XX:+PrintVMOptions
- 使用该参数可以在程序运行时,打印虚拟机接受到的命令行显式参数。
- -XX:+PrintCommandLineFlags
- 参数可以打印传递给虚拟机的显式和隐式参数,隐式参数未必是通过命令行直接给出的,它可能是由虚拟机启动时自行设置的
- -XX:+PrintFlagsFinal
- 如果需要查看系统的详细参数,开启这个参数后,虚拟机可能会产生多达500多行的输出,每一行为一个配置参数和其当前取值
2.堆的配置
堆空间是Java进程的重要组成部分,几乎所有与应用相关的内存空间都和堆有关。下面将主要介绍与堆有关的参数设置,这些参数可以说是Java虚拟机中最重要的,也是对程序性能有着重要影响的。
2.1 最大堆和初始堆的设置
- 当Java进程启动时,虚拟机就会分配一块初始堆空间,可以使用参数Xms指定这块空间的大小。一般来说,虚拟机会尽可能维持在初始堆空间的范围内运行。但是如果初始堆空间耗尽,虚拟机将会对堆空间进行扩展,其扩展上限为最大堆空间,最大堆空间可以使用数-Xmx指定。
- 根据上面介绍很容易让人想到,这里的最大可用内存就是指-Xmx的取值,当前总内存应该不小于-Xms的设定,因为当前总内存总是在-Xms和-Xmx之间,从-Xms开始根据需要向上增长。而当前空闲内存应该是当前总内存减去当前已经使用的空间。这样的理解差不多是正确的,之所以使用“差不多”一词,是因为这样理解并不是十分全面的。
- 实际上这是分配给堆的内存空间和实际可用的内存空间并非一个概念。由于垃圾回收的需要,虚拟机会对堆空间进行分区管理,不同的区域采用不同的回收算法,一些算法会使用空间换时间的策略工作,因此会存在可用内存的损失,最终的结果就是实际可用内存会浪费大小等于from/to的空间。因此,实际最大可用内存为-Xmx的值减去from的大小。实际上发现这个计算也有偏差,这个偏差是由于虚拟机内部并没有直接使用新生代from/to的大小,而是进一步对它们做了对齐操作。对于串行GC的情况,虚拟机使用某种方法估算from/to的大小,并进行了对齐。
- 在实际工作中,也可以直接将初始堆-Xms与最大堆-Xmx设置相等。这样的好处是可以减少程序运行时进行的垃圾回收次数,从而提高程序的性能。
2.2 新生代的配置
-XX:SurvivorRatio可以设置eden区与survivor区的比例.-XX:NewRatio可以设置老年代与新生代的比例。
2.3 堆溢出处理
我记得到公司的那一天,运行公司的大项目的时候就出现了内存溢出错误(Out Of Memory),简称OOM(当时我记得我先去看了一下笔记本的内存使用率,以为我笔记本的内存不够了)。出现以上错误后系统强迫退出了,根本运行不了项目。如果这个问题出现在生产环境中,就会导致服务器超载甚至被迫中断并重启。我记得我接管的第一个系统,第一天就发生了超载,并且服务被迫停止,我在排查问题的时候发现每个节点的堆空间设置的都特别大,加起来都超过了服务器所能提供的内存空间。
- -XX:+HeapDumpOnOutOfMemoryError
- -XX:HeapDumpPath
- 例题
- 虚拟机参数配置
- -Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump
- 分配堆空间20MB,并且导出整个堆的信息
- 运行结果
- 下面MAT工具查看刚导出的dump文件
3.非堆内存的参数设置
除了堆内存外,虚拟机还有一些内存用于方法区、线程栈和直接内存的使用。它们与堆内存是相对独立的。虽然和堆内存相比,这些内存空间和应用程序本身可能关系不那么密切,但是从系统层面上看,有效、合理地配置这些内存参数,对系统性能和稳定性也有着重要的作用。
3.1 方法去配置
- 方法区主要存放类的元信息。
- 在JDK1.6和JDK1.7等版本中,可以使用-XX:PermSize和-XX:MaxPermSize配置永久区大小。其中-XX:PermSize表示初始的永久区大小,-XX:MaxPermSize表示最大永久区。
- 在JDK1.8中,永久区被彻底移除,使用了新的元数据区存放类的元数据。默认情况下,元数据区只受系统可用内存的限制,但依然可以使用参数-XX:MaxMetaspaceSize指定永久区的最大可用值。
3.2 栈配置
- 栈是每个线程私有的内存空间。在Java虚拟机中可以使用-Xss参数指定线程的栈大小。
3.3 直接内存配置
- 直接内存也是Java程序中非常重要的组成部分,特别是在NIO被广泛使用后,直接内存的使用也变得非常普遍。直接内存跳过了Java堆,使Java程序可以直接访问原生堆空间,因此,从一定程度上加快了内存空间的访问速度。但是,武断地认为使用直接内存一定可以提高内存访问速度也是不正确的。
- 最大可用直接内存可以使用参数-X:MaxDirectMemorySize设置,如不设置,默认值为最大堆空间,即-Xmx。当直接内存使用量达到-XX:MaxDirectMemorySize时,就会触发垃圾回收,如果垃圾回收不能有效释放足够空间,直接内存溢出依然会引起系统的OOM。
- 虽然在访问读写上直接内存有较大的优势,但是在内存空间申请时,直接内存毫无优势可言。
- 由此,可以得出结论:直接内存适合申请次数较少、访问较频繁的场合。如果内存空间本身需要频繁申请,则并不适合使用直接内存。
4.虚拟机的工作模式
- 目前的Java虚拟机支持Client和Server两种运行模式,使用参数-client可以指定使用Client模式,使用参数-server可以指定使用Server模式。默认情况下,虚拟机会根据当前计算机系统
环境自动选择运行模式。使用-version参数可以查看当前的模式,如下所示;
- 与Client模式相比,Server模式的启动比较慢,因为Server模式会尝试收集更多的系统性能信息,使用更复杂的优化算法对程序进行优化。因此,当系统完全启动并进入运行稳定期后,Server模式的执行速度会远远快于Client模式。所以,对于后台长期运行的系统,使用-server参数启动对系统的整体性能可以有不小的帮助。但对于用户界面程序,运行时间不长,又追求启动速度,Client模式也是不错的选择。
- 从发展趋势上看,未来64位系统必然会逐步取代32位系统,而在64位系统中虚拟机更倾向于使用Server模式运行。
虚拟机在Server模式和Client模式下的各种参数可能会有很大不同,可以使用-XX:+PrintFlagsFinal参数查看对应模式下的参数和其职。