理想的状况下,一个Java程序使用JVM的默认设置也能够运行得很好,因此通常来讲,没有必要设置任何JVM参数。然而,因为一些性能问题(很不幸的是,这些问题常常出现),一些相关的JVM参数知识会是咱们工做中得好伙伴。在这篇文章中,咱们将介绍一些关于JVM内存管理的参数。知道并理解这些参数,将对开发者和运维人员颇有帮助。java
全部已制定的HotSpot内存管理和垃圾回收算法都基于一个相同的堆内存划分:新生代(young generation)里存储着新分配的和较年轻的对象,老年代(old generation)里存储着长寿的对象。在此以外,永久代(permanent generation)存储着那些须要伴随整个JVM生命周期的对象,好比,已加载的对象的类定义或者String对象内部Cache。接下来,咱们将假设堆内存是按照新生代、老年代和永久代这一经典策略划分的。然而,其余的一些堆内存划分策略也是可行的,一个突出的例子就是新的G1垃圾回收器,它模糊了新生代和老年代之间的区别。此外,目前的开发进程彷佛代表在将来的HotSpot JVM版本中,将不会区分老年代和永久代。算法
-Xms and -Xmx (or: -XX:InitialHeapSize and -XX:MaxHeapSize)缓存
-Xms和-Xmx能够说是最流行的JVM参数,它们能够容许咱们指定JVM的初始和最大堆内存大小。通常来讲,这两个参数的数值单位是Byte,但同时它们也支持使用速记符号,好比“k”或者“K”表明“kilo”,“m”或者“M”表明“mega”,“g”或者“G”表明“giga”。举个例子,下面的命令启动了一个初始化堆内存为128M,最大堆内存为2G,名叫“MyApp”的Java应用程序。运维
java -Xms128m -Xmx2g MyApp性能
在实际使用过程当中,初始化堆内存的大小一般被视为堆内存大小的下界。然而JVM能够在运行时动态的调整堆内存的大小,因此理论上来讲咱们有可能会看到堆内存的大小小于初始化堆内存的大小。可是即便在很是低的堆内存使用下,我也历来没有遇到过这种状况。这种行为将会方便开发者和系统管理员,由于咱们能够经过将“-Xms”和“-Xmx”设置为相同大小来得到一个固定大小的堆内存。 -Xms和-Xmx其实是-XX:InitialHeapSize和-XX:MaxHeapSize的缩写。咱们也能够直接使用这两个参数,它们所起得效果是同样的:对象
$ java -XX:InitialHeapSize=128m -XX:MaxHeapSize=2g MyApp生命周期
须要注意的是,全部JVM关于初始\最大堆内存大小的输出都是使用它们的完整名称:“InitialHeapSize”和“InitialHeapSize”。因此当你查询一个正在运行的JVM的堆内存大小时,如使用-XX:+PrintCommandLineFlags参数或者经过JMX查询,你应该寻找“InitialHeapSize”和“InitialHeapSize”标志而不是“Xms”和“Xmx”。进程
-XX:+HeapDumpOnOutOfMemoryError and -XX:HeapDumpPath内存
若是咱们无法为-Xmx(最大堆内存)设置一个合适的大小,那么就有可能面临内存溢出(OutOfMemoryError)的风险,这多是咱们使用JVM时面临的最可怕的猛兽之一。就同另一篇关于这个主题的博文说的同样,致使内存溢出的根本缘由须要仔细的定位。一般来讲,分析堆内存快照(Heap Dump)是一个很好的定位手段,若是发生内存溢出时没有生成内存快照那就实在是太糟了,特别是对于那种JVM已经崩溃或者错误只出如今顺利运行了数小时甚至数天的生产系统上的状况。开发
幸运的是,咱们能够经过设置-XX:+HeapDumpOnOutOfMemoryError 让JVM在发生内存溢出时自动的生成堆内存快照。有了这个参数,当咱们不得不面对内存溢出异常的时候会节约大量的时间。默认状况下,堆内存快照会保存在JVM的启动目录下名为java_pid<pid>.hprof 的文件里(在这里<pid>就是JVM进程的进程号)。也能够经过设置-XX:HeapDumpPath=<path>来改变默认的堆内存快照生成路径,<path>能够是相对或者绝对路径。
虽然这一切听起来很不错,但有一点咱们须要牢记。堆内存快照文件有可能很庞大,特别是当内存溢出错误发生的时候。所以,咱们推荐将堆内存快照生成路径指定到一个拥有足够磁盘空间的地方。
-XX:OnOutOfMemoryError
当内存溢发生时,咱们甚至能够能够执行一些指令,好比发个E-mail通知管理员或者执行一些清理工做。经过-XX:OnOutOfMemoryError 这个参数咱们能够作到这一点,这个参数能够接受一串指令和它们的参数。在这里,咱们将不会深刻它的细节,但咱们提供了它的一个例子。在下面的例子中,当内存溢出错误发生的时候,咱们会将堆内存快照写到/tmp/heapdump.hprof 文件而且在JVM的运行目录执行脚本cleanup.sh
$ java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -XX:OnOutOfMemoryError ="sh ~/cleanup.sh" MyApp
-XX:PermSize and -XX:MaxPermSize
永久代在堆内存中是一块独立的区域,它包含了全部JVM加载的类的对象表示。为了成功运行应用程序,JVM会加载不少类(由于它们依赖于大量的第三方库,而这又依赖于更多的库而且须要从里面将类加载进来)这就须要增长永久代的大小。咱们可使用-XX:PermSize 和-XX:MaxPermSize 来达到这个目的。其中-XX:MaxPermSize 用于设置永久代大小的最大值,-XX:PermSize 用于设置永久代初始大小。下面是一个简单的例子:
$ java -XX:PermSize=128m -XX:MaxPermSize=256m MyApp
请注意,这里设置的永久代大小并不会被包括在使用参数-XX:MaxHeapSize 设置的堆内存大小中。也就是说,经过-XX:MaxPermSize设置的永久代内存可能会须要由参数-XX:MaxHeapSize 设置的堆内存之外的更多的一些堆内存。
-XX:InitialCodeCacheSize and -XX:ReservedCodeCacheSize
JVM一个有趣的,但每每被忽视的内存区域是“代码缓存”,它是用来存储已编译方法生成的本地代码。代码缓存确实不多引发性能问题,可是一旦发生其影响多是毁灭性的。若是代码缓存被占满,JVM会打印出一条警告消息,并切换到interpreted-only 模式:JIT编译器被停用,字节码将再也不会被编译成机器码。所以,应用程序将继续运行,但运行速度会下降一个数量级,直到有人注意到这个问题。就像其余内存区域同样,咱们能够自定义代码缓存的大小。相关的参数是-XX:InitialCodeCacheSize 和-XX:ReservedCodeCacheSize,它们的参数和上面介绍的参数同样,都是字节值。
-XX:+UseCodeCacheFlushing
若是代码缓存不断增加,例如,由于热部署引发的内存泄漏,那么提升代码的缓存大小只会延缓其发生溢出。为了不这种状况的发生,咱们能够尝试一个有趣的新参数:当代码缓存被填满时让JVM放弃一些编译代码。经过使用-XX:+UseCodeCacheFlushing 这个参数,咱们至少能够避免当代码缓存被填满的时候JVM切换到interpreted-only 模式。不过,我仍建议尽快解决代码缓存问题发生的根本缘由,如找出内存泄漏并修复它。