JAVA JVM参数及作用记录|JVM监控打印内存使用信息

http://nccloud.yytimes.com/uploads/question/20230627/33b1c547c58d69063069e509eaa68e46.png

已邀请:

堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-Xmn:新生代大小
-XX:NewRati设置新生代和老年代的比值。如:为3,表示年轻代与老年代比值为1:3
-XX:SurvivorRati新生代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:为3,表示Eden:Survivor=3:2,一个Survivor区占整个新生代的1/5
-XX:MaxTenuringThreshold:设置转入老年代的存活次数。如果是0,则直接跳过新生代进入老年代
-XX:PermSize、-XX:MaxPermSize:分别设置永久代最小大小与最大大小(Java8以前)
-XX:MetaspaceSize、-XX:MaxMetaspaceSize:分别设置元空间最小大小与最大大小(Java8以后)
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行老年代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器新生代收集方式为并行收集时,使用的CPU数。并行收集线程数。



CompressedClassSpaceSize: 默认为1G

查看CompressedClassSpace大小

jcmd pid GC.heap_info

http://nccloud.yytimes.com/uploads/answer/20230627/e0e8602407248bb31e7a2dbfdab631f4.png

  • java8移除了permanent generation,然后class metadata存储在native memory中,其大小默认是不受限的,可以通过-XX:MaxMetaspaceSize来限制;如果开启了-XX:+UseCompressedOops及-XX:+UseCompressedClassesPointers(默认是开启),则UseCompressedOops会使用32-bit的offset来代表java object的引用,而UseCompressedClassPointers则使用32-bit的offset来代表64-bit进程中的class pointer;可以使用CompressedClassSpaceSize来设置这块的空间大小
  • 开启了指针压缩,则CompressedClassSpace分配在MaxMetaspaceSize里头,即MaxMetaspaceSize=Compressed Class Space Size + Metaspace area (excluding the Compressed Class Space) Size
  • 查看CompressedClassSpace的内存使用情况有好几种方法:
    • jcmd pid GC.heap_info(Metaspace为总的部分,包含了class space,而Metaspace area (excluding the Compressed Class Space)需要自己计算即total-class space)
    • jcmd pid VM.native_memory(class为总的部分,包含了Metaspace area (excluding the Compressed Class Space)及Class Space)
    • 使用JMX来获取NON_HEAP类型中的name为Metaspace及Compressed Class Space的MemoryPoolMXBean可以得到Metaspace及Compressed Class Space的使用情况(JMX得到的Metaspace为总的部分,而Metaspace area (excluding the Compressed Class Space)需要自己计算即total-class space)
    • 如果是springboot应用,它使用micrometer,通过/actuator/metrics接口提供相关指标查询功能,其中Metaspace及Compressed Class Space在jvm.memory.used这个metric中


如果你的java程序引用了太多的包,有可能会造成这个空间不够用,于是会看到

java.lang.OutOfMemoryError: Compressed class space


元空间是用于存储类元数据的内存区域,元类是在解析.class文件时创建的内部JVM结构。

类元数据包括:Java类的内部表示、具有字节码的方法、字段描述符、恒定池、符号、注解、等等

-XX:MaxMetaspaceSize 默认情况下是无限的。

-XX:+UseCompressedClassPointersoption为ON(堆<32G的默认设置)时,类将从Metaspace移到称为Compressed Class Space的单独区域。这是为了使用32位值而不是64位来寻址VM类结构。

因此,压缩类空间包含Java类的内部表示形式,而元空间则包含所有其余的元数据:方法,常量池,注释等。

压缩类空间的大小受限制-XX:CompressedClassSpaceSize,默认情况下为1G。的最大可能值为-XX:CompressedClassSpaceSize3G。

非类元空间和压缩类空间是两个不相交的区域。MaxMetaspaceSize限制两个区域的承诺大小:

committed(Non-class Metaspace) + committed(Compressed Class Space) <= MaxMetaspaceSize

如果MaxMetaspaceSize设置为小于CompressedClassSpaceSize,则后者会自动减小为

CompressedClassSpaceSize = MaxMetaspaceSize - 2*InitialBootClassLoaderMetaspaceSize

JVM 有个功能是 CompressedOops ,目的是为了在 64bit 机器上使用 32bit 的原始对象指针(oop,ordinary object pointer,这里直接就当成指针概念理解就可以了,不用关心啥是 ordinary)来节约成本(减少内存/带宽使用),提高性能(提高 Cache 命中率)。

使用了这个压缩功能,每个对象中的 Klass* 字段就会被压缩成 32bit(不是所有的 oop 都会被压缩的),总所周知 Klass* 指向的 Klass 在永久代(Java7 及之前)。但是在 Java8 及之后,永久代没了,有了一个 Metaspace,于是之前压缩指针 Klass* 指向的这块 Klass 区域有了一个名字 —— Compressed Class Space。Compressed Class Space 是 Metaspace 的一部分,默认大小为 1G。所以其实 Compressed Class Space 这个名字取得很误导,压缩的并不是 Klass,而是 Klass*。

JVM 也为 compressed class pointers(Klass*)多了俩选项:

  • -XX:+UseCompressedClassPointers(压缩开关)
  • -XX:CompressedClassSpaceSize(Compressed Class Space 空间大小限制)。-


-XX:+UseCompressedClassPointers 是需要 -XX:+UseCompressedOops 开启的,所以堆大小要是大于 32G,CompressedOops 自动关闭,CompressedClassPointers 也会关闭的,关闭了就没有 Compressed Class Space 了,这块就是 Class Space 了。

-XX:CompressedClassSpaceSize 大小的设置也是有限制,因为压缩开关是受制于 32G,所以这个自然也是不能大于 32G,不过 hotspot 规定了这个参数不准大于 3G,所以这个参数其实是不能大于 3G。

一般来说,平均一个 Klass 大小可以当成 1K 来算,默认的 1G 大小可以存储 100 万的 Klass。如果遇到了 `java.lang.OutOfMemoryError: Compressed class space`,就是类太多了,需要结合具体情况去选择 JVM 调优还是 bug 排查。

">

什么是Code Cache?

简而言之,JVM Code Cache (代码缓存)是JVM存储编译成本机代码的字节码的区域。我们将可执行本机代码的每个块称为 nmethod 。 nmethod 可能是一个完整的或内联的Java方法。

即时( JIT )编译器是代码缓存区的最大消费者。这就是为什么一些开发人员将此内存称为JIT代码缓存。

Code Cache优化

代码缓存的大小是固定的。一旦它满了,JVM就不会编译任何额外的代码,因为JIT编译器现在处于关闭状态。此外,我们将收到“ CodeCache is full… The compiler has been disabled ”警告消息。因此,我们的应用程序的性能最终会下降。为了避免这种情况,我们可以使用以下大小选项调整代码缓存:

  • InitialCodeCacheSize –初始代码缓存大小,默认为160K
  • ReservedCodeCacheSize –默认最大大小为48MB
  • CodeCacheExpansionSize –代码缓存的扩展大小,32KB或64KB

增加ReservedCodeCacheSize可能是一个解决方案,但这通常只是一个临时解决办法。

幸运的是,JVM提供了一个 UseCodeCache 刷新选项来控制代码缓存区域的刷新。其默认值为false。当我们启用它时,它会在满足以下条件时释放占用的区域:

  • 代码缓存已满;如果该区域的大小超过某个阈值,则会刷新该区域
  • 自上次清理以来已过了特定的时间间隔
  • 预编译代码不够热。对于每个编译的方法,JVM都会跟踪一个特殊的热度计数器。如果此计数器的值小于计算的阈值,JVM将释放这段预编译代码

Code Cache使用

为了监控Code Cache(代码缓存)的使用情况,我们需要跟踪当前正在使用的内存的大小。

要获取有关代码缓存使用情况的信息,我们可以指定 –XX:+PrintCodeCache JVM选项。运行应用程序后,我们将看到类似的输出:

CodeCache: size=32768Kb used=542Kb max_used=542Kb free=32226Kb

让我们看看这些值的含义:

  • 输出中的大小显示内存的最大大小,与 ReservedCodeCacheSize 相同
  • used 是当前正在使用的内存的实际大小
  • max_used 是已使用的最大尺寸
  • free 是尚未占用的剩余内存

PrintCodeCache选项非常有用,因为我们可以:

  • 看看什么时候会flushing
  • 确定我们是否达到了关键内存使用点

分段代码缓存

从Java9开始,JVM将代码缓存分为三个不同的段,每个段都包含特定类型的编译代码。更具体地说,有三个部分:

-XX:nonNMethoddeHeapSize
-XX:ProfiledCodeHeapSize
-XX:nonprofiedCodeHeapSize

这种新结构以不同的方式处理各种类型的编译代码,从而提高了整体性能。

例如,将短命编译代码与长寿命代码分离可以提高方法清理器的性能——主要是因为它需要扫描更小的内存区域。





大家都知道javac编译器,把java代码编译成class字节码,它和JIT编译器的区别是,javac只是前端编译(有的叫前期编译),jvm是通过执行机器码和底层交互的,这样我们编写的业务代码才能生效。所以还要把字节码class编译成与本地平台相关的机器码,这个过程就是后端编译。

后端编译根据具体的执行方式不同又分为两种:

1.解释执行

一行一行解释成机器码再执行,每次调用时都需要重新逐条解释执行。

2.编译执行(JIT)

将频繁调用的方法或循环体编译成机器码后,进行多层优化,然后缓存到codeCache里,避免重复编译。

两种执行方式的区别很明显,第一种在遇到频繁调用的方法或代码块时执行效率很低,但是解释执行可以节省内存(不存放到codeCache),立即执行。然后当程序运行一段时间后(达到一定的编译次数),编译执行即JIT优化,可以获得更高的执行效率。

所以说二者是相辅相成的。

现在的Java虚拟机这两种方式都包含(通过命令行java -version查看):

// mixed mode 解释+编译
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)

其实JIT编译只是一个统称,具体要看jvm是client端还是server端的,不同的端会分为C1,C2编译器,这两种编译器的区别下一篇会讲到,这里先不展开。

二. JIT编译优化

上面讲到了JVM会对频繁使用的代码,即热点代码(Hot Spot Code),达到一定的阈值后会编译成本地平台相关的机器码,并进行各层次的优化,提升执行效率。

热点代码也分两种:

  • 被多次调用的方法

  • 被多次执行的循环体

那阈值如何判断呢?

方法计数器,统计被多次调用的方法次数,该计数器统计的并不是方法被调用的绝对次数,而是在一段时间内方法被调用的次数。server模式下默认是10000次,可以通过-XX:CompileThreshold来设置(client模式一般很少用到,默认是1500)。

回边计数器,统计一个方法中循环体代码执行的绝对次数,在字节码中遇到控制流向后跳转的指令称为回边,主要通过OnStackReplacePercentage设置。

编译后进行优化,JIT的优化有很多种,比如:

  • 针对方法的优化,方法内联

  • 针对多次调用的循环体优化:栈上替换OSR(On-Stack Replace)

  • 无用代码消除

  • 复写传播

  • 逃逸分析

  • 更多JIT优化技术可参考jvm官网介绍

三. codeCache使用注意事项

上面主要讲了codeCache的作用和JIT的关系,codeCache主要是存放JIT编译后的机器代码,codeCache的大小主要是通过下面的参数设置:

  • -XX:InitialCodeCacheSize 设置codeCache初始大小,一般默认是48M

  • -XX:ReservedCodeCacheSize 设置codeCache预留的大小,通常默认是240M

如果codeCache的内存满了会进行回收,但在jdk1.8之前的jvm回收算法有点问题,当codeCache满了之后会导致编译线程无法继续,并且消耗大量CPU导致系统运行变慢,现象就是系统响应增加,如果你也遇到这个问题建议直接升级成jdk8,或者调大codeCache内存。

codeCache的大小设置可以通过-XX:+PrintCodeCache参数查看调整,但这个参数只在JVM停止的时候打印codeCache使用情况

5.JVM调优

在代大小的调优上,最关键的参数为:-Xms、-Xmx、-Xmn、-XX:SurvivorRatio、-XX:MaxTenuringThreshold
避免新生代大小设置过小:当新生代大小设置过小时,会导致minor GC的次数更加频繁且有可能导致minor GC的对象直接进入老年代,此时如进入老年代的对象占据了剩余空间,将触发Full GC。
除了调大新生代大小外,如果能够调大堆的大小,那就更好,但堆调大意味着单次GC时间的增加。
当minor GC过于频繁,或发现经常出现minor GC时,Survivor的一个区域空间满且老年代增长超过了Survivor区域大小时,就需要考虑调整新生代的大小了。调整时的原则是在不能调大堆的情况下,尽可能放大新生代空间,尽量让对象在minor GC阶段被回收。但新生代空间也不可过大,在能够调大堆的情况下,则可以按照增加的新生代空间大小增加堆大小,保证老年代空间够用。
避免新生代设置过大: 会导致老年代变小了,有可能导致Full GC频繁执行;minor GC的耗时大幅度增加。
大多数场景下都应设置得比老年代小,推荐是堆的33%。
避免Survivor区过小或过大:调大SurvivorRatio意味着Eden区变大,minor GC的触发次数会降低,但此时Survivor区域的空间变小了,如有超过Survivor空间大小的对象在minor GC后仍没有被回收,则会直接进入旧生代。调小则意味着Eden区变小,minor的触发次数会增加,但Survivor区可以储存更多minor GC后仍存活的对象,避免其进入老年代。
合理设置新生代存活周期
GC策略调优
大部分Web应用在处理请求时设置了一个最大可同时处理的请求数,当超过此请求数时,会将之后的请求放入等待队列中,而这个等待队列也限制了大小。当满了后仍有请求进入,那么这些请求将会被直接丢弃,所有的请求又都是有超时限制的。 因此Web应用非常需要一个暂停时间短的GC,再加上大部分Web应用的瓶颈都不在cpu上,所以CMS是个不错的选择。
在进行参数调整时,可根据目前收集到的顶峰时系统请求次数、响应时间以及G提升C的信息,来估计系统每次请求需要消耗的内存,以及每次minor GC时存活的对象所占的内存,从而估计需要设置多大的Survivor才能够尽可能地避免对象进入老年代
由于参数的估计是以请求次数和响应时间为基准的,因此一旦系统的响应速度下降或请求的次数上升,就可能仍然会导致大量对象进入老年代,从而触发频繁的Full GC,频繁的Full GC又导致系统的响应速度下降,从这个层面看,根本需要做的调优仍然是提升请求的处理速度以及降低每次请求需要分配的内存,只有这样才能使得应用能够支撑更高的并发量。
目前内存管理方面,JVM自身已经做得非常不错了,如果如果不是有确切的GC造成性能低的理由,就没必要做过多细节方面的调优。多数情况下只需要选择GC策略并设置堆的大小即可。

打印当前某个进程PID的内存使用情况

jmap -heap pid号

http://nccloud.yytimes.com/uploads/answer/20230627/8194a6753a8e670c698a5fe2be78fdc5.png

前言
大家都知道,jvm在启动的时候,会执行默认的一些参数。一般情况下,这些设置的默认参数应对一些平常的项目也够用了。但是如果项目特别大了,需要增加一下堆内存的大小、或者是系统老是莫明的挂掉,想查看下gc日志来排查一下错误的原因,都需要咱们手动设置这些参数。

 

各个参数介绍
-verbose:gc
表示,启动jvm的时候,输出jvm里面的gc信息。格式如下:

[ Full GC 178K -> 99K ( 1984K ), 0.0253877 secs]
1.
解读 :Full GC 就表示执行了一次Full GC的操作,178K 和99K 就表示执行GC前内存容量和执行GC后的内存容量。1984K就表示内存总容量。后面那个是执行本次GC所消耗的时间,单位是秒。
 

-XX:+PrintGC
这个打印的GC信息跟上个一样,就不做介绍了。
 

-XX:+PrintGCDetails
打印GC的详细信息。格式如下:

–Heap
– def new generation total 13824K, used 11223K [0x27e80000, 0x28d80000, 0x28d80000)
– eden space 12288K, 91% used [0x27e80000, 0x28975f20, 0x28a80000)
– from space 1536K, 0% used [0x28a80000, 0x28a80000, 0x28c00000)
– to space 1536K, 0% used [0x28c00000, 0x28c00000, 0x28d80000)
– tenured generation total 5120K, used 0K [0x28d80000, 0x29280000, 0x34680000)
– the space 5120K, 0% used [0x28d80000, 0x28d80000, 0x28d80200, 0x29280000)
– compacting perm gen total 12288K, used 142K [0x34680000, 0x35280000, 0x38680000)
– the space 12288K, 1% used [0x34680000, 0x346a3a90, 0x346a3c00, 0x35280000)
– ro space 10240K, 44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000)
– rw space 12288K, 52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000
解读:new generation 就是堆内存里面的新生代。total的意思就是一共的,所以后面跟的就是新生代一共的内存大小。used也就是使用了多少内存大小。0x开头的那三个分别代表的是 底边界,当前边界,高边界。也就是新生代这片内存的起始点,当前使用到的地方和最大的内存地点。

eden space 这个通常被翻译成伊甸园区,是在新生代里面的,一些创建的对象都会先被放进这里。后面那个12288K就表示伊甸园区一共的内存大小,91% used,很明显,表示已经使用了百分之多少。后面的那个0x跟上一行的解释一样。

from space 和to space 是幸存者的两个区。也是属于新生代的。他两个区的大小必须是一样的。因为新生代的GC采用的是复制算法,每次只会用到一个幸存区,当一个幸存区满了的时候,把还是活的对象复制到另个幸存区,上个直接清空。这样做就不会产生内存碎片了。

tenured generation 就表示老年代。

compacting perm 表示永久代。由于这两个的格式跟前面我介绍的那个几乎一样,我就不必介绍了。
 

-XX:+PrintGCTimeStamps
输出GC的时间戳(以基准时间的形式)。格式如下:

289.556 : [GC [ PSYoungGen : 314113K -> 15937K ( 300928K )] 405513K -> 107901K ( 407680K ), 0.0178568 secs] [ Times : user= 0.06 sys= 0.00 , real= 0.01 secs]
293.271 : [GC [ PSYoungGen : 300865K -> 6577K ( 310720K )] 392829K -> 108873K ( 417472K ), 0.0176464 secs] [ Times : user= 0.06 sys= 0.00 , real= 0.01 secs]
解读:289.556表示从jvm启动到发生垃圾回收所经历的的时间。GC表示这是新生代GC(Minor GC)。PSYoungGen(PS是指Parallel Scavenge)表示新生代使用的是多线程垃圾回收器Parallel Scavenge。314113K->15937K(300928K)]这个跟上面那个GC格式一样,只不过,这个是表示的是新生代,幸存者区。后面那个是整个堆的大小,GC前和GC后的情况。Times这个显而易见,代表GC的所消耗的时间,用户垃圾回收的时间和系统消耗的时间和最终真实的消耗时间。
 

-XX:+PrintGCDateStamps
输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
 

-Xloggc:logs/gc.log
这个就表示,指定输出gc.log的文件位置。(我这里写的log/gc.log就表示在当前log的目录里,把GC日志写到叫gc.log的文件里。)
 

-XX:+PrintHeapAtGC
表示在每次进行 GC 的前后打印出堆的信息(这个打印的基本格式跟上面第二条的基本类似,我也就不比多说了。)
 

-XX:ErrorFile
当 JVM 发生致命错误导致崩溃时,会生成一个 hs_err_pid<PID>.log 这样的文件, 默认情况下, 该文件是生成在工作目录下的:-XX:ErrorFile=./hs_err_pid<PID>.log
 

-XX:+TraceClassLoading
监控类的加载。格式如下:

•[Loaded java.lang.Object from shared objects file]
•[Loaded java.io.Serializable from shared objects file]
•[Loaded java.lang.Comparable from shared objects file]
•[Loaded java.lang.CharSequence from shared objects file]
•[Loaded java.lang.String from shared objects file]
•[Loaded java.lang.reflect.GenericDeclaration from shared objects file]
•[Loaded java.lang.reflect.Type from shared objects file]
使用这个参数就能很清楚的看到那些类被加载的情况了。
 

-XX:+PrintClassHistogram
跟踪参数。这个按下Ctrl+Break(Pause)后,就会打印一下信息:

num instances bytes class name
----------------------------------------------
1: 890617 470266000 [B
2: 890643 21375432 java.util.HashMap$Node
3: 890608 14249728 java.lang.Long
4: 13 8389712 [Ljava.util.HashMap$Node;
5: 2062 371680 [C
6: 463 41904 java.lang.Class
1.
2.
3.
4.
5.
6.
7.
8.
– 分别显示:序号、实例数量、总大小、类型。
这里面那个类型,B和C的其实就是byte和char类型。
 

-Xmx -Xms
这个就表示设置堆内存的最大值和最小值。这个设置了最大值和最小值后,jvm启动后,并不会直接让堆内存就扩大到指定的最大数值。而是会先开辟指定的最小堆内存,如果经过数次GC后,还不能,满足程序的运行,才会逐渐的扩容堆的大小,但也不是直接扩大到最大内存。

-Xms : 初始堆大小-XX:InitialHeapSize的缩写
-Xmx: 最大堆大小-XX:MaxHeapSize的缩写
 
-Xmn
设置新生代的内存大小。(如果需要进一步细化,初始化大小用-XX:NewSize,最大大小用-XX:MaxNewSize)
 

-XX:NewRatio
新生代和老年代的比例。比如:1:4,就是新生代占五分之一。
 

-XX:SurvivorRatio
设置两个Survivor区和eden区的比例。比如:2:8 ,就是一个Survivor区占十分之一。
 

-XX:+HeapDumpPath
表示指定 dump 文件存储路径, 结合 -XX:+HeapDumpOnOutOfMemoryError 使用。 -XX:HeapDumpPath=./dump.hprof  

-XX:+HeapDumpOnOutOfMemoryError
发生 OOM 时,导出堆的信息到文件。
 

-XX:OnOutOfMemoryError
当系统产生OOM时,执行一个指定的脚本,这个脚本可以是任意功能的。比如生成当前线程的dump文件,或者是发送邮件和重启系统。-XX:OnOutOfMemoryError="kill -9 %p"  

-XX:PermSize -XX:MaxPermSize
JDK1.7及之前版本:设置永久代(方法区)的内存大小和最大值。永久代内存用光也会导致OOM的发生。
 

-XX: MetaspaceSize -XX: MaxMetaspaceSize
JDK1.8及之后版本: 调整(元空间)方法区的大小。(MetaspaceSize默认20M)

 

从JDK8开始,永久代(PermGen)的概念被废弃掉了,取而代之的是一个称为Metaspace的存储空间。Metaspace使用的是本地内存,而不是堆内存,也就是说在默认情况下Metaspace的大小只与本地内存大小有关。当然你也可以通过以下的几个参数对Metaspace进行控制:

-XX:MetaspaceSize=N: 这个参数是初始化的Metaspace大小,该值越大触发Metaspace GC的时机就越晚。随着GC的到来,虚拟机会根据实际情况调控Metaspace的大小,可能增加上线也可能降低。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。使用java -XX:+PrintFlagsInitial命令查看本机的初始化参数,-XX:Metaspacesize为21810376B(大约20.8M)。
-XX:MaxMetaspaceSize=N: 这个参数用于限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。
-XX:MinMetaspaceFreeRatio=N: 当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机将增长Metaspace的大小。在本机该参数的默认值为40,也就是40%。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。
-XX:MaxMetasaceFreeRatio=N: 当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。在本机该参数的默认值为70,也就是70%。
-XX:MaxMetaspaceExpansion=N: Metaspace增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB)。
-XX:MinMetaspaceExpansion=N: Metaspace增长时的最小幅度。在本机上该参数的默认值为340784B(大约330KB为)。
说明: 在Java官方的HotSpot 虚拟机中,Java8版本以后,是用元空间来实现的方法区;在Java8之前的版本,则是用永久代实现的方法区;方法区是JVM规范的抽象定义,其具体的实现是通过永久代(JDK1.7及之前)和元空间(JDK1.8及之后)实现的

 

-Xss
设置每个线程的堆栈的大小。栈都是每个线程独有一个,所有一般都是几百k的大小。等同于-XX:ThreadStackSize

默认JDK1.4中是256K,JDK1.5+中是1M。 在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右

# 获取当前JVM ThreadStackSize参数的默认值
java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
-XX:+PrintGCApplicationStoppedTime
打印垃圾回收期间程序暂停的时间
输出形式: Total time for which application threads were stopped: 0.0468229 seconds
 

-XX:MinHeapFreeRatio
设置堆空间最小空闲比例。 当堆空间的空闲内存小于这个数值时, JVM 便会扩展堆空间。
 

-XX:MaxHeapFreeRatio
设置堆空间的最大空闲比例。 当堆空间的空闲内存大于这个数值时, 便会压缩堆空间, 得到一个较小的堆。
 

-XX:NewSize
设置新生代大小。
 

-XX:NewRatio
设置老年代与新生代的比例, 它等于老年代大小除以新生代大小。
 

-XX:SurvivorRatio
新生代中 Eden 区和 Survivor 区的比例。
 

-XX:MaxPermSize
设置最大的持久区大小。
 

-XX:PermSize
设置永久区的初始值。
 

-XX:TargetSurvivorRatio
设置 Survivor 区的可使用率。 当 Survivor 区的空间使用率达到这个数值时, 它将对象送入老年代。
 

-XX:+DisableExplicitGC
这个参数,会导致System.gc()调用变成一个空调用,没有任何作用

System.gc()默认会触发一次Full Gc,如果在代码中不小心调用了System.gc()会导致JVM间歇性的暂停

为什么不推荐使用-XX:+DisableExplicitGC?

有些NIO框架比如Netty框架经常会使用DirectByteBuffer来分配堆外内存,在分配之前会显式的调用System.gc()。如果开启了DisableExplicitGC这个参数,会导致System.gc()调用变成一个空调用,没有任何作用,反而会导致Netty框架无法申请到足够的堆外内存,从而产生java.lang.OutOfMemoryError: Direct buffer memory.

 

jmap -heap PID
# 启动参数
-Xms512M -Xmx512M -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintClassHistogram


jmap -heap 4828

Attaching to process ID 4828, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.171-b11

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
# 空闲堆空间的最小百分比,计算公式为:HeapFreeRatio =(CurrentFreeHeapSize/CurrentTotalHeapSize) * 100,值的区间为0到100,默认值为 40。如果HeapFreeRatio < MinHeapFreeRatio,则需要进行堆扩容,扩容的时机应该在每次垃圾回收之后。
MinHeapFreeRatio = 0
# 空闲堆空间的最大百分比,计算公式为:HeapFreeRatio =(CurrentFreeHeapSize/CurrentTotalHeapSize) * 100,值的区间为0到100,默认值为 70。如果HeapFreeRatio > MaxHeapFreeRatio,则需要进行堆缩容,缩容的时机应该在每次垃圾回收之后。
MaxHeapFreeRatio = 100
# JVM 堆空间允许的最大值. jvm参数 -Xms512m
MaxHeapSize = 536870912 (512.0MB)
# JVM 新生代堆空间的默认值. jvm参数 -Xmn128m
NewSize = 178782208 (170.5MB)
# JVM 新生代堆空间允许的最大值
MaxNewSize = 178782208 (170.5MB)
# JVM 老年代堆空间的默认值. 老年代空间 = 堆内存大小 - 年轻代大小
OldSize = 358088704 (341.5MB)
# 新生代(2个Survivor区和Eden区 )与老年代(不包括永久区)的堆空间比值。 表示 老年代:新生代=2:1, 即: 新生代占堆总大小的的1/3(512/3=170.66MB)
NewRatio = 2
# 两个Survivor区和Eden区的堆空间比值为 8,表示 S0 : S1 :Eden = 1:1:8
# Eden:Survivor=8:1. 新生代=Eden + From Survivor + To Survivor
# 即: Eden占8/10, From Survivor=To Survivor=1/10
SurvivorRatio = 8
# JVM 元空间的默认值 jvm参数 -XX:MaxMetaspaceSize
MetaspaceSize = 21807104 (20.796875MB)
# 类指针压缩空间大小, 默认为1G
CompressedClassSpaceSize = 1073741824 (1024.0MB)
# JVM 元空间允许的最大值
MaxMetaspaceSize = 17592186044415 MB
# 在使用 G1 垃圾回收算法时,JVM 会将 Heap 空间分隔为若干个 Region,该参数用来指定每个 Region 空间的大小
# G1区块的大小, 取值为1M至32M. 其取值是要根据最小Heap大小划分出2048个区块
G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
PS Young Generation
# 新生代-伊甸区内存分布
Eden Space:
# Eden区总容量
capacity = 134742016 (128.5MB)
# Eden区已使用
used = 40132832 (38.273651123046875MB)
# Eden区剩余容量
free = 94609184 (90.22634887695312MB)
# Eden区使用比率
29.784942508207685% used
# 新生代-From Survivor
From Space:
capacity = 22020096 (21.0MB)
used = 21206680 (20.224266052246094MB)
free = 813416 (0.7757339477539062MB)
96.3060288202195% used
# 新生代-To Survivor
To Space:
capacity = 20447232 (19.5MB)
used = 0 (0.0MB)
free = 20447232 (19.5MB)
0.0% used
# 老年代
PS Old Generation
capacity = 358088704 (341.5MB)
used = 20568088 (19.615257263183594MB)
free = 337520616 (321.8847427368164MB)
5.743852785705299% used

26215 interned Strings occupying 3125824 bytes.
JVM:jmap heap 堆参数分析MinHeapFreeRatio、MaxHeapFreeRatio、MaxHeapSize、NewSize、MaxNewSize

 

查看JVM使用的什么垃圾收集器
java -XX:+PrintCommandLineFlags -version

-XX:InitialHeapSize=132165184 -XX:MaxHeapSize=2114642944 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)
-XX:+UseParallelGC: 虚拟机运行在 Server 模式下的默认值, 打开此开关后, 使用 Parallel Scavenge + Serial Old(PS MarkSweep) 的收集器组合进行内存回收

Parallel Scavenge收集器架构中本身有PS MarkSweep收集器来进行老年代收集,并非直接使用了Serial Old收集器,但是这个PS MarkSweep收集器与Serial Old的实现非常接近,所以在官方的许多资料中都是直接以Serial Old代替PS MarkSweep进行讲解,这里笔者也采用这种方式。

 

for(GarbageCollectorMXBean garbageCollectorMXBean : ManagementFactory.getGarbageCollectorMXBeans()) {
System.out.println(garbageCollectorMXBean.getName());
}

/** 输出
PS Scavenge
PS MarkSweep
*/

各个jdk默认的垃圾回收器
JDK1.7 默认垃圾收集器Parallel Scavenge(新生代)+ Serial Old(PS MarkSweep)(老年代)
JDK1.8 默认垃圾收集器Parallel Scavenge(新生代)+ Serial Old(PS MarkSweep)(老年代)
JDK1.9 默认垃圾收集器G1
JDK1.8默认使用的垃圾收集器组合

# 可查看默认设置收集器类型
java -XX:+PrintCommandLineFlags -version
# 打印GC日志, 根据新生代、老年代名称判断
java -XX:+PrintGCDetails -version

 

JVM参数设置
# %p变量表示当前jvm的pid,用%t表示jvm的启动时间戳. eg: -Xloggc:./logs/gc-%t.log
-Xms1024M -Xmx1024M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -Xloggc:./gc/app_`date +%Y%m%d`.log -XX:HeapDumpPath=./gc/app-dump_`date +%Y%m%d`.hprof -XX:ErrorFile=./gc/app-error_`date +%Y%m%d`.log -Duser.timezone=GMT+08 -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

java <options> -jar XXX.jar --spring.profiles.active=prod


##################################################
# 参考
##################################################
# JDK7
JAVA_MEM_OPTS=" -server -Xmx2g -Xms2g -Xmn256m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 "

JAVA_DEBUG_OPTS=" -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/GCEASY/gc-%t.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/crashes/my-heap-dump.hprof -XX:OnOutOfMemoryError=/scripts/restart-myapp.sh "



# JDK8
JAVA_MEM_OPTS=" -server -Xmx2g -Xms2g -Xmn256m -XX:MetaspaceSize=256m -Xss1024m -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 "

JAVA_DEBUG_OPTS=" -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/GCEASY/gc-%t.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/crashes/my-heap-dump.hprof -XX:OnOutOfMemoryError=/scripts/restart-myapp.sh "
堆内存分配
JVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64;
JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。
默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;
空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。
因此服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。

说明:如果-Xmx 不指定或者指定偏小,应用可能会导致java.lang.OutOfMemory错误,此错误来自JVM,不是Throwable的,无法用try…catch捕捉。

 

打印 JVM 参数
# 打印 JVM 参数初始值(打印出所有XX选项的默认值),初始值可能被修改掉
java -XX:+PrintFlagsInitial

# 打印 JVM 参数最终值(打印出XX选项在运行程序时生效的值)
java -XX:+PrintFlagsFinal 2> nul
java -XX:+PrintFlagsFinal -version |grep CMSInitiatingOccupancyFraction

# 打印被修改过的 JVM 参数
java -XX:+PrintCommandLineFlags 2> nul
在 java 代码里面打印

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
private void printJvm() {
MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
System.out.println("堆内存信息: " + memorymbean.getHeapMemoryUsage());
System.out.println("方法区内存信息: " + memorymbean.getNonHeapMemoryUsage());

List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
System.out.println("\n####################运行时设置的JVM参数####################");
System.out.println(inputArgs);

System.out.println("\n####################运行时内存情况#######################");
long totle = Runtime.getRuntime().totalMemory();
System.out.println("总的内存量 [" + totle + "]");
long free = Runtime.getRuntime().freeMemory();
System.out.println("空闲的内存量 [" + free + "]");
long max = Runtime.getRuntime().maxMemory();
System.out.println("最大的内存量 [" + max + "]");

要回复问题请先登录注册