Java教程

JVM常用命令和性能调优建议 [Could not create the Java virtual machine]

本文主要是介绍JVM常用命令和性能调优建议 [Could not create the Java virtual machine],对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

 

一、查看jvm常用命令
jinfo:可以输出并修改运行时的java 进程的opts。
jps:与unix上的ps类似,用来显示本地的java进程,可以查看本地运行着几个java程序,并显示他们的进程号。
jstat:一个极强的监视VM内存工具。可以用来监视VM内存内的各种堆和非堆的大小及其内存使用量。
jmap:打印出某个java进程(使用pid)内存内的所有'对象'的情况(如:产生那些对象,及其数量)。
jconsole:一个java GUI监视工具,可以以图表化的形式显示各种数据。并可通过远程连接监视远程的服务器VM。

需要注意:在使用这些工具前,先用JPS命令获取当前的每个JVM进程号,然后选择要查看的JVM。

二、jinfo
jinfo可以查看设置的jvm的信息

jinfo -flag MaxHeapSize [pid]  能够查看最大堆内存
jinfo -flag ThreadStackSize [pid] 

jinfo -flags [pid]
jinfo -flag UseConcMarkSweepGC [pid]
jinfo -flag UseG1GC [pid]
jinfo -flag UseParallelGC [pid]

三、查看jvm运行时的参数

1)java -XX:+PrintFlagsFinal -version
得出的结果,如果是
= 表示默认值
:= 表示被用户或者jvm修改后的值

2)-XX:+UnlockDiagnosticVMOptions 解锁诊断参数
-XX:+PrintCommandLineFlags 打印命令行参数

3)jps
jps这个没什么好说的,在linux里面ps是查看进程,jps就是java的ps,查看java进程
如果执行"jps -l"就能查看整个类名

四、jstat
这个指令用来查看jvm统计信息,主要分以下三类:

1)类装载
jstat -class [pid] 1000 10
这个指令指的是每1秒(1000就是1000ms)查看一次类的装载信息,一共看十次。后面的1000和10可以不要

2)垃圾收集
-gc、-gcutil、-gccause、-gcnew、-gcold

3)JIT编译
jstat -compiler [pid]
jstat -printcompilation [pid]

五、jmap
jmap是一个很重要的命令,可以查看jvm内存使用情况。可以分为两点讲解:

1)导出内存映像文件
内存溢出自动导出,设置参数既可:
+XX:HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./
不用多说,设置内存溢出自动导出还有导出的路径

使用jmap命令手动导出:
jmap -dump:format=b,file=heap.hprof [pid]
其中format=b代表用二进制导出

2)查看内存映像文件:
在这里http://www.eclipse.org/mat/downloads.php下载内存分析工具,然后把dump文件导入分析就可以了。

六 、jstack
jstack也是一个很重要的命令,可以查看线程使用情况。这里需要知道现场的几种状态,参考如下:

- NEW状态: The thread has not yet started.
- RUNNABLE状态:The thread is executing in the JVM.
- BLOCKED状态:The thread is blocked waiting for a monitor lock.
- WAITING状态:The thread is waiting indefinitely for another thread to perform a particular action.
- TIMED_WAITING状态:The thread is waiting for another thread to perform an action for up to a specified waiting time.
- TERMINATED状态:The thread has exited.

状态之间是怎么转换的呢?参考如下:
jvm里面查看线程使用情况的命令"jstack [pid]",将得到的线程情况输入一个叫做stack.txt的文件:
这个stack.txt文件能够查看到jvm里面某个id的线程是用来做什么的?如果发生了死锁,在最后也会列举出来。

在linux里面怎么知道线程线上执行的情况呢,哪些线程占用了很大的内存呢?
这时可以用这个命令 "top -p [pid] -H" 就能够看到单个进程里面线程的情况:

通过printf  "%x" [pid]  可以将10精致的pid 转换为16进制,转换成16进制有什么用?
因为jstack出来的线程是十六进制的,通过刚刚那个值,就能够在jstack出来的文件里面找到使用内存最多的线程是哪一个线程了。

七、jvisualvm
jvisualvm是一个可视化工具,集成了上面指令的几乎所有功能,但是它更加直观,所以jvisualvm也很重要。它也在jdk目录的bin目录下。监控本地应用很简单,直接打开jvisualvm就能看到有本地的了。下面着重介绍监控远程的:

1)监控远程tomcat
修改tomcat的Catalina.sh

JAVA_OPTS='-Dcom.sun.management.jmxremote.port=12345 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.1.54'   

参数说明:
第一个参数是端口,不用说;
第二个参数是不需要ssl验证,链接的时候不勾那个属性
第三个参数是不需要验证
第四个参数是这个tomcat应用的地址,注意不是jvisualvm的地址。

2)监控远程java应用:
nohup java 
-Dcom.sun.management.jmxremote
-Djava.rmi.server.hostname=192.168.1.54  
-Dcom.sun.management.jmxremote.port=12344
-Dcom.sun.management.jmxremote.ssl=false -
Dcom.sun.management.jmxremote.authenticate=false
-Djava.net.preferIPv4Stack=true
-jar test.jar

命令是一样的,但是一定要记住的是这里的192.168.1.54一定是我们的java的应用地址,而不是jvisual的地址!!
记得开放端口, 然后把需要的添加的机器192.168.1.54添加到jvisualvm的"远程"标签里面,然后在“远程“标签里面添加端口在ip后面就可以。

这里说一下监控远程可能会有一点坑,原因如下:
1)首先确保在java应用的机器里面可以使用jvisualvm才可以,也就是在java应用的机器里面调用jvisualvm没有报错(可能只是warning),才可以。
2)可能是jdk版本的问题。有可能确认了多少遍端口和应用的问题,但是就是连不上,所以就要看看是不是jdk版本的问题(也许不是,所以就在需要监控的java应用程序的机器上先试下jvisualvm)

                                                                JVM性能优化建议                                                                

一、JVM堆内存划分

JDK7及以前的版本:一个对象被创建以后首先被放到Nursery内存中的Eden内 存中,如果存活期超两个Survivor之后就会被转移到长时内存中(Old Generation)中。永久内存中存放着对象的方法、变量等元数据信息。通过如果永久内存不够,就会得到如下错误:Java.lang.OutOfMemoryError: PermGen
JDK8版本:JDK8中把存放元数据中的永久内存从堆内存中移到了本地内存(native memory)中,这样永久内存就不再占用堆内存,它可以通过自动增长来避免JDK7以及前期版本中常见的永久内存错误(java.lang.OutOfMemoryError: PermGen)。JDK8也提供了一个新的设置Matespace内存大小的参数:

-XX:MaxMetaspaceSize=128m

需要注意:如果不设置Matespaces内存大小参数,JVM将会根据一定的策略自动增加本地元内存空间。如果你设置的元内存空间过小,你的应用程序可能得到以下错误:java.lang.OutOfMemoryError: Metadata space

二、JVM参数
-XX 参数被称为不稳定参数,此类参数的设置很容易引起JVM性能上的差异。

1)不稳定参数语法规则
布尔类型

-XX:+<option> '+'表示启用该选项
-XX:-<option> '-'表示关闭该选项

数字类型

-XX:<option>=<number>
# 可跟随单位,例如:'m'或'M'表示兆字节;'k'或'K'千字节;'g'或'G'千兆字节。32K与32768是相同大小的。

字符串类型

-XX:<option>=<string>
# 通常用于指定一个文件、路径或一系列命令列表。例如:-XX:HeapDumpPath=./dump.core

2)行为选项

选项默认值描述
-XX:-AllowUserSignalHandlers限于Linux和Solaris默认关闭允许为java进程安装信号处理器。
-XX:AltStackSize=16384仅适用于Solaris,从5.0中删除备用信号堆栈大小(以字节为单位)
-XX:-DisableExplicitGC默认关闭禁止在运行期显式地调用 System.gc()。注意:你熟悉的代码里没调用System.gc(),不代表你依赖的框架工具没在使用。
-XX:+FailOverToOldVerifierJava6新引入选项,默认启用如果新的Class校验器检查失败,则使用老的校验器。解决兼容性问题。
关联选项:-XX:+UseSplitVerifier
-XX:+HandlePromotionFailureJava1.5以前默认关闭,Java1.6后默认启用新生代收集担保,于年老代预留内存。
-XX:+MaxFDLimit限于Solaris,默认启用设置java进程可用文件描述符为操作系统允许的最大值。
-XX:PreBlockSpin默认10控制多线程自旋锁优化的自旋次数
前置选项:
-XX:+UseSpinning
-XX:-RelaxAccessControlCheck默认关闭,Java1.6引入在Class校验器中,放松对访问控制的检查。作用与reflection里的setAccessible类似。
-XX:+ScavengeBeforeFullGC默认启用在Full GC前触发一次Minor GC
-XX:+UseAltSigs限于Solaris
默认启用
为了防止与其他发送信号的应用程序冲突,允许使用候补信号替代 SIGUSR1和SIGUSR2。
-XX:+UseBoundThreads限于Solaris
默认启用
绑定所有的用户线程到内核线程。 减少线程进入饥饿状态(得不到任何cpu time)的次数。
-XX:-UseConcMarkSweepGC默认关闭,Java1.4引入启用CMS低停顿垃圾收集器。
-XX:+UseGCOverheadLimit默认启用,Java1.6引入限制GC的运行时间。如果GC耗时过长,就抛OutOfMemoryError。
-XX:+UseLWPSynchronization限于solaris, 默认启用, Java1.4引入使用轻量级进程(内核线程)替换线程同步。
-XX:-UseParallelGC-server时启用, 其他情况下:默认关闭, Java1.4引入为新生代使用并行清除,年老代使用单线程Mark-Sweep-Compact的垃圾收集器。
-XX:-UseParallelOldGC默认关闭, Java1.5引入为老年代和新生代都使用并行清除的垃圾收集器。开启此选项将自动开启-XX:+UseParallelGC 选项
-XX:-UseSerialGC-client时启用, 默认关闭, Java1.5引入使用串行垃圾收集器。
-XX:-UseSpinningJava1.4.2和1.5需要手动启用, Java1.6默认已启用启用多线程自旋锁优化。关联选项:-XX:PreBlockSpin=10
-XX:+UseTLABJava1.4.2以前和使用-client选项时:默认关闭, 其余版本默认启用启用线程本地缓存区(Thread Local)
-XX:+UseSplitVerifierJava1.5默认关闭, Java1.6默认启用使用新的Class类型校验器 。关联选项: -XX:+FailOverToOldVerifier
-XX:+UseThreadPriorities默认启用使用本地线程的优先级。
-XX:+UseVMInterruptibleIO限于solaris, 默认启用, Java1.6引入在solaris中,允许运行时中断线程

3)性能选项

选项默认值描述
-XX:+AggressiveOptsJava1.5 引入,默认关闭,Java1.6后默认开启开启编译器性能优化。
-XX:CompileThreshold=10000默认值:1000通过JIT编译器,将方法编译成机器码的触发阀值,可以理解为调用方法的次数,例如调1000次,将方法编译为机器码。 [-client: 1,500]
-XX:LargePageSizeInBytes=4m默认值:4m,amd64位:2m设置堆内存的内存最大值。
-XX:MaxHeapFreeRatio=70默认70GC后,如果发现空闲堆内存占到整个预估上限值的70%,则收缩预估上限值。
-XX:MaxNewSize=size1.3.1 Sparc: 32m,1.3.1 x86: 2.5m新生代占整个堆内存的最大值。从Java1.4开始, MaxNewSize成为 NewRatio的一个函数
-XX:MaxPermSize=64mJava1.5以后::64 bit VMs会增大预设值的30%,1.4 amd64::96m,1.3.1 -client: 32m,其他默认 64mPerm(俗称方法区)占整个堆内存的最大值。
-XX:MinHeapFreeRatio=40默认值:40GC后,如果发现空闲堆内存占到整个预估上限值的40%,则增大上限值。关联选项:-XX:MaxHeapFreeRatio=70
-XX:NewRatio=2Sparc -client: 8,x86 -server: 8 ,x86 -client: 12 -client: 4 (1.3) 8 (1.3.1+) ,x86: 12 ,其他:2新生代和年老代的堆内存占用比例。 例如2表示新生代占年老代的1/2,占整个堆内存的1/3。
-XX:NewSize=2m5.0以后: 64 bit Vms 会增大预设值的30%,x86: 1m,x86, 5.0以后: 640k,其他:2.125m新生代预估上限的默认值。
-XX:ReservedCodeCacheSize=32mSolaris 64-bit, amd64, -server x86: 48m ,1.5.0_06之前, Solaris 64-bit ,amd64: 1024m ,其他:32m设置代码缓存的最大值,编译时用。
-XX:SurvivorRatio=8Solaris amd64: 6 ,Sparc in 1.3.1: 25 ,Solaris platforms 5.0以前: 32 ,其他:8Eden与Survivor的占用比例。例如8表示,一个survivor区占用 1/8 的Eden内存,即1/10的新生代内存,为什么不是1/9? 因为我们的新生代有2个survivor,即S1和S22。所以survivor总共是占用新生代内存的 2/10,Eden与新生代的占比则为 8/10。
-XX:TargetSurvivorRatio=50默认值:50实际使用的survivor空间大小占比。默认是47%,最高90%。
-XX:ThreadStackSize=512Sparc: 512 ,Solaris x86: 320 (5.0以前 256) ,Sparc 64 bit: 1024 Linux amd64: 1024 (5.0 以前 0) ,其他:512.线程堆栈大小,
-XX:+UseBiasedLockingJava1.5 update 6后引入,默认关闭。Java1.6默认启用。启用偏向锁
-XX:+UseFastAccessorMethods默认启用优化原始类型的getter方法性能。
-XX:-UseISM默认启用启用solaris的ISM
-XX:+UseLargePagesJava1.5 update 5后引入,默认关闭,Java1.6默认启用启用大内存分页。关联选项:-XX:LargePageSizeInBytes=4m
-XX:+UseMPSSJava1.4.1 之前默认关闭,其他版本默认启用启用solaris的MPSS,不能与ISM同时使用。
-XX:+UseStringCache默认开启缓存常用字符串。
-XX:AllocatePrefetchLines=1默认值:1在使用JIT生成的预读取指令分配对象后读取的缓存行数。如果上次分配的对象是一个实例则默认值是1,如果是一个数组则是3
-XX:AllocatePrefetchStyle=1默认值:1预读取指令的生成代码风格,0- 无预读取指令生成 ,1-在每次分配后执行预读取命令 ,2-当预读取指令执行后使用TLAB()分配水印指针来找回入口,
-XX:+UseCompressedStringsJava1.6 update 21引入其中,对于不需要16位字符的字符串,可以使用byte[] 而非char[]。对于许多应用,这可以节省内存,但速度较慢(5%-10%)
-XX:+OptimizeStringConcatJava1.6 update 20引入在可能的情况下优化字符串连接操作。

4)调试选项

选项默认值描述
-XX:-CITime默认启用打印JIT编译器编译耗时。
-XX:ErrorFile=./hs_err_pid.logJava1.6引入如果JVM crashed,将错误日志输出到指定文件路径。
-XX:-ExtendedDTraceProbesJava6引入,限于solaris,默认关闭启用dtrace诊断
-XX:HeapDumpPath=./java_pid.hprof默认是java进程启动位置堆内存快照的存储文件路径。
-XX:-HeapDumpOnOutOfMemoryError默认关闭在java.lang.OutOfMemoryError 异常出现时,输出一个dump.core文件,记录当时的堆内存快照(见 -XX:HeapDumpPath 的描述)
-XX:OnError=”\;\”Java1.4引入当java每抛出一个ERROR时,运行指定命令行指令集。指令集是与OS环境相关的,在Linux下多数是.sh脚本,windows下是.bat批处理。
-XX:OnOutOfMemoryError=”\;\”Java1.4.2 update 12和Java6时引入当第一次发生java.lang.OutOfMemoryError 时,运行指定命令行指令集。指令集是与OS环境相关的,在linux下多数是.sh脚本,windows下是.bat批处理。
-XX:-PrintClassHistogram默认关闭在Windows下, 按ctrl-break或Linux下是执行kill -3(发送SIGQUIT信号)时,打印class柱状图。 jmap -histo pid也实现了相同的功能。
-XX:-PrintConcurrentLocks默认关闭在thread dump的同时,打印java.util.concurrent的锁状态。jstack -l pid 也同样实现了同样的功能。
-XX:-PrintCommandLineFlagsJava1.5 引入,默认关闭Java启动时,往stdout打印当前启用的非稳态jvm options。 例如:-XX:+UseConcMarkSweepGC -XX:+HeapDumpOnOutOfMemoryError -XX:+DoEscapeAnalysis
-XX:-PrintCompilation默认关闭往stdout打印方法被JIT编译时的信息。
-XX:-PrintGC默认关闭开启GC日志打印。显示结果例如: [Full GC 131115K->7482K(1015808K), 0.1633180 secs] ,该选项可通过com.sun.management.HotSpotDiagnosticMXBean API 和 jconsole 动态启用。
-XX:-PrintGCDetailsJava1.4引入,默认关闭打印GC回收的详细信息。 显示结果例如: [Full GC (System) [Tenured: 0K->2394K(466048K), 0.0624140 secs] 30822K->2394K(518464K), [Perm : 10443K->10443K(16384K)], 0.0625410 secs] [Times: user=0.05 sys=0.01, real=0.06 secs] ,该选项可通过com.sun.management.HotSpotDiagnosticMXBean API 和 jconsole 动态启用。
-XX:-PrintGCTimeStamps默认关闭打印GC停顿耗时。 显示结果例如: 2.744: [Full GC (System) 2.744: [Tenured: 0K->2441K(466048K), 0.0598400 secs] 31754K->2441K(518464K), [Perm : 10717K->10717K(16384K)], 0.0599570 secs] [Times: user=0.06 sys=0.00, real=0.06 secs] ,该选项可通过com.sun.management.HotSpotDiagnosticMXBean API 和 jconsole 动态启用。
-XX:-PrintTenuringDistribution默认关闭打印对象的存活期限信息。 显示结果例如: [GC Desired survivor size 4653056 bytes, new threshold 32 (max 32) - age 1: 2330640 bytes, 2330640 total - age 2: 9520 bytes, 2340160 total 204009K->21850K(515200K), 0.1563482 secs] ,Age1,2表示在第1和2次GC后存活的对象大小。
-XX:-TraceClassLoading默认关闭打印class装载信息到stdout。记Loaded状态。 例如: [Loaded java.lang.Object from /opt/taobao/install/jdk1.6.0_07/jre/lib/rt.jar]
-XX:-TraceClassLoadingPreorder1.4.2引入,默认关闭按class的引用/依赖顺序打印类装载信息到stdout。不同于 TraceClassLoading,本选项只记 Loading状态。 例如: [Loading java.lang.Object from /home/confsrv/jdk1.6.0_14/jre/lib/rt.jar]
-XX:-TraceCla***esolution1.4.2引入,默认关闭打印所有静态类,常量的代码引用位置。用于debug。 例如: RESOLVE java.util.HashMap java.util.HashMapEntryHashMap.java:209<br>说明HashMap类的209行引用了静态类java.util.HashMapEntryHashMap.java:209<br>说明HashMap类的209行引用了静态类java.util.HashMapEntry
-XX:-TraceClassUnloading默认关闭打印class的卸载信息到stdout。记Unloaded状态。
-XX:-TraceLoaderConstraintsJava1.6 引入,默认关闭打印class的装载策略变化信息到stdout。装载策略变化是实现classloader隔离/名称空间一致性的关键技术。
-XX:+PerfSaveDataToFile默认启用当java进程因java.lang.OutOfMemoryError 异常或crashed 被强制终止后,生成一个堆快照文件。
-XX:ParallelGCThreads=n默认值:随JVM运行平台不同而异配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
-XX:+UseCompressedOops32位默认关闭,64位默认启动使用compressed pointers。这个参数默认在64bit的环境下默认启动,但是如果JVM的内存达到32G后,这个参数就会默认为不启动,因为32G内存后,压缩就没有多大必要了,要管理那么大的内存指针也需要很大的宽度了
-XX:+AlwaysPreTouch默认关闭在JVM 初始化时预先对Java堆进行摸底。
-XX:AllocatePrefetchDistance=n默认值取决于当前JVM 设置为对象分配设置预取距离。
-XX:InlineSmallCode=n默认值取决于当前JVM 设置当编译的代码小于指定的值时,内联编译的代码。
-XX:MaxInlineSize=35默认值:35内联方法的最大字节数。
-XX:FreqInlineSize=n默认值取决于当前JVM 设置内联频繁执行的方法的最大字节码大小。
-XX:LoopUnrollLimit=n默认值取决于当前JVM 设置代表节点数目小于给定值时打开循环体。
-XX:InitialTenuringThreshold=7默认值:7设置初始的对象在新生代中最大存活次数。
-XX:MaxTenuringThreshold=n默认值:15,最大值:15设置对象在新生代中最大的存活次数,最大值15,并行回收机制默认为15,CMS默认为4。
-Xloggc:默认关闭输出GC 详细日志信息至指定文件。
-XX:-UseGCLogFileRotation默认关闭开启GC 日志文件切分功能,前置选项 -Xloggc
-XX:NumberOfGClogFiles=1必须>=1,默认值:1设置切分GC 日志文件数量,文件命名格式:.0, .1, …, .n-1
-XX:GCLogFileSize=8K必须>=8K,默认值:8KGC日志文件切分大小。

三、垃圾收集器
1.  串行回收器
1.1)新生代串行回收器
特点
-  它仅仅使用单线程进行垃圾回收
-  它是独占式的垃圾回收
-  进行垃圾回收时, Java应用程序中的线程都需要暂停(Stop-The-World)
-  使用复制算法
-  适合CPU等硬件不是很好的场合

设置参数

-XX:+UseSerialGC   指定新生使用新生代串行收集器和老年代串行收集器, 当以client模式运行时, 它是默认的垃圾收集器

1.2)老年代串行回收器
特点
-  同新生代串行回收器一样, 单线程, 独占式的垃圾回收器
-  通常老年代垃圾回收比新生代回收要更长时间, 所以可能会使应用程序停顿较长时间

设置参数

-XX:+UseSerialGC   新生代, 老年代都使用串行回收器
-XX:+UseParNewGC   新生代使用ParNew回收器, 老年代使用串行回收器
-XX:+UseParallelGC 新生代使用ParallelGC回收器, 老年代使用串行回收器

2.  并行回收器
2.1)新生代ParNew回收器
特点
-  将串行回收多线程化
-  使用复制算法
-  垃圾回收时, 应用程序仍会暂停, 只不过由于是多线程回收, 在多核CPU上,回收效率会高于串行回收器, 反之在单核CPU, 效率会不如串行回收器

设置参数

-XX:+UseParNewGC 新生代使用ParNew回收器, 老年代使用串行回收器
-XX:+UseConcMarkSweepGC 新生代使用ParNew回收器, 老年代使用CMS回收器
-XX:ParallelGCThreads=n 指回ParNew回收器工作时的线程数量, cpu核数小时8时, 其值等于cpu数量, 高于8时,可以使用公式(3+((5*CPU_count)/8))

2.2)新生代ParallelGC回收器
特点
-  同ParNew回收器一样, 不同的地方在于,它非常关注系统的吞吐量(通过参数控制)
-  使用复制算法
-  支持自适应的GC调节策略

设置参数

-XX:+UseParallelGC  新生代用ParallelGC回收器, 老年代使用串行回收器
-XX:+UseParallelOldGC  新生代用ParallelGC回收器, 老年代使用ParallelOldGC回收器系统吞吐量的控制:
-XX:MaxGCPauseMillis=n(单位ms)   设置垃圾回收的最大停顿时间,
-XX:GCTimeRatio=n(n在0-100之间)  设置吞吐量的大小, 假设值为n, 那系统将花费不超过1/(n+1)的时间用于垃圾回收
-XX:+UseAdaptiveSizePolicy  打开自适应GC策略, 在这种模式下, 新生代的大小, eden,survivior的比例, 晋升老年代的对象年龄等参数会被自动调整,以达到堆大小, 吞吐量, 停顿时间之间的平衡点

2.3)老年代ParallelOldGC回收器
特点
-  同新生代的ParallelGC回收器一样, 是属于老年代的关注吞吐量的多线程并发回收器
-  使用标记压缩算法

设置参数

-XX:+UseParallelOldGC  新生代用ParallelGC回收器, 老年代使用ParallelOldGC回收器, 是非常关注系统吞吐量的回收器组合, 适合用于对吞吐量要求较高的系统
-XX:ParallelGCThreads=n   指回ParNew回收器工作时的线程数量, cpu核数小时8时, 其值等于cpu数量, 高于8时, 可以使用公式(3+((5*CPU_count)/8))

3.  CMS回收器(Concurrent Mark Sweep,并发标记清除)
3.1)老年代的并发回收器
特点
-  是并发回收, 非独占式的回收器, 大部分时候应用程序不会停止运行
-  针对年老代的回收器
-  使用并发标记清除算法, 因此回收后会有内存碎片, 可以使参数设置进行内存碎片的压缩整理
-  与ParallelGC和ParallelOldGC不同, CMS主要关注系统停顿时间

主要步骤
-  初始标记
-  并发标记
-  预清理
-  重新标记
-  并发清理
-  并发重置

需要注意:初始标记与重新标记是独占系统资源的,不能与用户线程一起执行,而其它阶段则可以与用户线程一起执行

设置参数

-XX:-CMSPrecleaningEnabled  关闭预清理, 不进行预清理, 默认在并发标记后, 会有一个预清理的操作,可减少停顿时间
-XX:+UseConcMarkSweepGC  老年代使用CMS回收器, 新生代使用ParNew回收器
-XX:ConcGCThreads=n  设置并发线程数量,
-XX:ParallelCMSThreads=n  同上, 设置并发线程数量,
-XX:CMSInitiatingOccupancyFraction=n  指定老年代回收阀值, 即当老年代内存使用率达到这个值时, 会执行一次CMS回收,默认值为68, 设置技巧: (Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100)>=Xmn
-XX:+UseCMSCompactAtFullCollection  开启内存碎片的整理, 即当CMS垃圾回收完成后, 进行一次内存碎片整理, 要注意内存碎片的整理并不是并发进行的, 因此可能会引起程序停顿
-XX:CMSFullGCsBeforeCompation=n  用于指定进行多少次CMS回收后, 再进行一次内存压缩
-XX:+CMSParallelRemarkEnabled  在使用UseParNewGC 的情况下, 尽量减少 mark 的时间
-XX:+UseCMSInitiatingOccupancyOnly  表示只有达到阀值时才进行CMS回收
-XX:+CMSConcurrentMTEnabled 当该标志被启用时,并发的CMS阶段将以多线程执行,默认开启
-XX:+CMSIncrementalMode 在增量模式下,CMS 收集器在并发阶段,不会独占整个周期,而会周期性的暂停,唤醒应用线程。收集器把并发阶段工作,划分为片段,安排在次级(minor) 回收之间运行。这对需要低延迟,运行在少量CPU服务器上的应用很有用。

3.2)Class的回收(永久区的回收)
设置参数

-XX:+CMSClassUnloadingEnabled  开启回收Perm区的内存, 默认情况下, 是需要触发一次FullGC
-XX:CMSInitiatingPermOccupancyFraction=n  当永久区占用率达到这个n值时,启动CMS回收, 需上一个参数开启的情况下使用

4. G1回收器(JDK1.7后全新的回收器, 用于取代CMS)
4.1)概述
特点
-  独特的垃圾回收策略, 属于分代垃圾回收器
-  使用分区算法, 不要求eden, 年轻代或老年代的空间都连续
-  并行性:回收期间, 可由多个线程同时工作, 有效利用多核cpu资源
-  并发性:与应用程序可交替执行, 部分工作可以和应用程序同时执行
-  分代GC::分代收集器, 同时兼顾年轻代和老年代
-  空间整理:回收过程中, 会进行适当对象移动, 减少空间碎片
-  可预见性:G1可选取部分区域进行回收, 可以缩小回收范围, 减少全局停顿

主要步骤
-  新生代GC
-  并发标记周期

初始标记新生代GC(此时是并行, 应用程序会暂停止) –> 根区域扫描 –> 并发标记 –> 重新标记(此时是并行, 应用程序会暂停止) –> 独占清理(此时应用程序会暂停止) –> 并发清理

-  混合回收

这个阶段即会执行正常的年轻代gc, 也会选取一些被标记的老年代区域进行回收, 同时处理新生代和年老轻

-  若需要,会进行FullGC

混合GC时发生空间不足
在新生代GC时, survivor区和老年代无法容纳幸存对象时
以上两者都会导致一次FullGC产生

设置参数

-XX:+UseG1GC  打开G1收集器开关,
-XX:MaxGCPauseMillis=n  指定目标的最大停顿时间,任何一次停顿时间超过这个值, G1就会尝试调整新生代和老年代的比例, 调整堆大小, 调整晋升年龄
-XX:ParallelGCThreads=n  用于设置并行回收时, GC的工作线程数量
-XX:InitiatingHeapOccpancyPercent=n  指定整个堆的使用率达到多少时, 执行一次并发标记周期, 默认45, 过大会导致并发标记周期迟迟不能启动, 增加FullGC的可能, 过小会导致GC频繁, 会导致应用程序性能有所下降

4.2)参数选项

选项默认值描述
-XX:+UseG1GC默认关闭使用G1垃圾处理器
-XX:MaxGCPauseMillis=n默认值:4294967295设置并行收集最大暂停时间,这是一个理想目标,JVM将尽最大努力来实现它。
-XX:InitiatingHeapOccupancyPercent=n默认值:45启动一个并发垃圾收集周期所需要达到的整堆占用比例。这个比例是指整个堆的占用比例而不是某一个代(例如G1),如果这个值是0则代表‘持续做GC’。默认值是45
-XX:NewRatio=n默认值:2设置年轻代和年老代的比值。例如:值为3,则表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4。
-XX:SurvivorRatio=n默认值:8年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxTenuringThreshold=n默认值:15设置垃圾最大存活阀值。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
-XX:ParallelGCThreads=n默认值:随JVM运行平台不同而异配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
-XX:ConcGCThreads=n默认值:随JVM运行平台不同而异并行垃圾收集时,使用的线程数。默认值和JVM运行的平台有关。
-XX:G1ReservePercent=n默认值:10设置保留用来做假天花板以减少晋升(新生代对象晋升到老生代)失败可能性的堆数目。
-XX:G1HeapRegionSize=n默认值根据堆大小而定使用G1垃圾回收器,java堆被划分成统一大小的区块。这个选项设置每个区块的大小。最小值是1Mb,最大值是32Mb。

4.3)其他GC相关的设置
4.3.1)System.gc()

1)禁用System.gc()
-XX:+DisableExplicitGC 禁止程序中调用System.gc(), 加了此参数, 程序若有调用, 返回的空函数调用System.gc()的调用, 会使用FullGC的方式回收整个堆而会忽略CMS或G1等相关回收器

2)System.gc()使用并发回收
-XX:+ExplicitGCCinvokesConcurrent  使用并发方式处理显示的gc, 即开启后, System.gc()这种显示GC才会并发的回收, (CMS, G1)

4.3.2)并行GC前额外触发的新生代GC

1)使用并行回收器 (UseParallelGC或者UseParallelOldGC)时, 会额外先触发一个新生代GC, 目的是尽可能减少停顿时间 
2)若不需要这种特性, 可以使用以下参数去除 
-XX:-ScavengeBeforeFullGC  即去除在FullGC之前的那次新生代GC, 原本默认值为true

4.3.3)对象何时进入老年代

1)当对象首次创建时, 会放在新生代的eden区, 若没有GC的介入,会一直在eden区, GC后,是可能进入survivor区或者年老代 

2)当对象年龄达到一定的大小 ,就会离开年轻代, 进入老年代, 对象进入老年代的事件称为晋升, 而对象的年龄是由GC的次数决定的, 
   每一次GC,若对象没有被回收, 则对象的年龄就会加1, 可以使用以下参数来控制新生代对象的最大年龄: 
-XX:MaxTenuringThreshold=n 假设值为n , 则新生代的对象最多经历n次GC, 就能晋升到老年代, 但这个必不是晋升的必要条件 
-XX:TargetSurvivorRatio=n  用于设置Survivor区的目标使用率,即当survivor区GC后使用率超过这个值, 就可能会使用较小的年龄作为晋升年龄 

3)除年龄外, 对象体积也会影响对象的晋升的, 若对象体积太大, 新生代无法容纳这个对象, 则这个对象可能就会直接晋升至老年代, 可通过以下参
   数使用对象直接晋升至老年代的阈值, 单位是byte 
-XX:PretenureSizeThreshold 即对象的大小大于此值, 就会绕过新生代, 直接在老年代分配, 此参数只对串行回收器以及ParNew回收有效, 而对ParallelGC回收器无效

4.3.4)在TLAB上分配对象(Thread Local Allocation Buffer, 线程本地分配缓存)

TLAB是一个线程专用的内存分配区域, 虚拟机为线程分配空间, 针对于体积不大的对象, 会优先使用TLAB, 这个可以加速对象的分配, TLAB是默认开启的, 若要关闭可以使用以下参数关闭 

-XX:-UseTLAB  关闭TLAB 
-XX:+UseTLAB  开启TLAB, 默认也是开启的 
-XX:+PrintTLAB 观察TALB的使用情况 
-XX:TLABRefillWasteFraction=n  设置一个比率n, 而refill_waste的值就是(TLAB_SIZE/n), 即TLAB空间较小, 大对象无法分配在TLAB,所以会直接分配到堆上,TLAB较小也很容易装满, 因此当TLAB的空间不够分配一个新对象, 就会考虑废弃当前TLAB空间还是直接分配到堆上, 就会使用此参数进行判断, 小于refill_waste就允许废弃, 而新建TLAB来分配对象,而大于refill_waste就直接在堆上分配, 默认是64 
-XX:+ResizeTLAB  开启TLAB自动调整大小, 默认是开启的, 若要关闭把+号换成-号即可 
-XX:TLABSize=n  设置一个TLAB的大小, 前提先关闭TLAB的自动调整

5.  性能调优工具
5.1)jps  (Java Virtual Machine Process Status Tool)
主要用来输出JVM中运行的进程状态信息。语法格式如下:

jps [options] [hostid]

# hostid语法如下:
[protocol:][[//]hostname][:port][/servername]
protocol - 如果protocol及hostname都没有指定,那表示的是与当前环境相关的本地协议,如果指定了hostname却没有指定protocol,那么protocol的默认就是rmi。
hostname - 服务器的IP或者名称,没有指定则表示本机。
port - 远程rmi的端口,如果没有指定则默认为1099。
Servername - 注册到RMI注册中心中的jstatd的名称。

# 如果不指定hostid就默认为当前主机或服务器。

# 命令行参数选项
-q 忽略输出类名、Jar名和传入main方法的参数
-m 输出传入main方法的参数
-l 输出main类或Jar的全限名
-v 输出传入JVM的参数
-V 输出通过标记的文件传递给JVM的参数(.hotspotrc文件,或者是通过参数-XX:Flags=<filename>指定的文件)
-J 用于传递jvm选项到由javac调用的java加载器中,例如,“-J-Xms48m”将把启动内存设置为48M,使用-J选项可以非常方便的向基于Java的开发的底层虚拟机应用程序传递参数。

5.2)stack(Java Stack Trace)
主要用来查看某个Java进程内的线程堆栈信息。语法格式如下:

jstack [option] pid
jstack [option] executable core
jstack [option] [server-id@]remote-hostname-or-ip

# 命令行参数选项说明如下:
-l 长列表,会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况
-m mixed mode,不仅会输出Java堆栈信息,还会输出C/C++堆栈信息(比如Native方法)
-F 当’jstack [-l] pid’没有相应的时候强制打印栈信息
-h | -help打印帮助信息

jstack可以定位到线程堆栈,根据堆栈信息我们可以定位到具体代码,所以它在JVM性能调优中使用得非常多。

5.3)jmap(Java Memory Map)
jmap用来查看堆内存使用状况,一般结合jhat使用。jmap语法格式如下:(如果运行在64位JVM上,可能需要指定-J-d64命令选项参数)

jmap [option] pid
jmap [option] executable core
jmap [option] [server-id@]remote-hostname-or-ip

# 参数说明
executable 产生core dump的java可执行程序
core 将被打印信息的core dump文件
remote-hostname-or-ip 远程debug服务的主机名或ip
server-id 唯一id,假如一台主机上多个远程debug服务
pid 需要被打印配相信息的java进程id,可以用jps查问

# 基本参数
-dump:[live,]format=b,file=<filename> 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的,假如指定live选项,那么只输出活的对象到文件
-finalizerinfo 打印正等候回收的对象的信息
-heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况
-histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量
-permstat 打印classload和jvm heap长久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来
-F 强迫.在pid没有相应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效
-h | -help 打印辅助信息
-J 传递参数给jmap启动的jvm

5.4)jhat(Java Heap Analysis Tool)
jhat用于对JAVA heap进行离线分析的工具,它可以对不同虚拟机中导出的heap信息文件进行分析

# jmap -dump:format=b,file=dumpFileName pid
jmap -dump:format=b,file=/tmp/dump.dat 21711

jhat -port 9998 /tmp/dump.dat
# 注意如果Dump文件太大,可能需要加上-J-Xmx512m这种参数指定最大堆内存

# 在浏览器中输入主机地址:9998查看

另外,可以使用Eclipse插件MAT(Memory Analyzer Tool)对dump文件进行分析。

5.5)jstat(Java Virtual Machine Statistics Monitoring Tool)
Jstat用于监控基于HotSpot的JVM,对其堆的使用情况进行实时的命令行的统计,使用jstat我们可以对指定的JVM做如下监控:

类的加载及卸载情况
-  查看新生代、老生代及持久代的容量及使用情况
-  查看新生代、老生代及持久代的垃圾收集情况,包括垃圾回收的次数及垃圾回收所占用的时间
-  查看新生代中Eden区及Survior区中容量及分配情况等

语法:

jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]

generalOption - 单个的常用的命令行选项,如-help, -options, 或 -version。
outputOptions -一个或多个输出选项,由单个的statOption选项组成,可以和-t, -h, and -J等选项配合使用。

statOption 根据jstat统计的维度不同,可以使用如下表中的选项进行不同维度的统计,不同的操作系统支持的选项可能会不一样,可以通过-options选项,查看不同操作系统所支持选项

-h n 用于指定每隔几行就输出列头,如果不指定,默认是只在第一行出现列头。
-J javaOption 用于将给定的javaOption传给java应用程序加载器,例如,“-J-Xms48m”将把启动内存设置为48M
-t n 用于在输出内容的第一列显示时间戳,这个时间戳代表的时JVM开始启动到现在的时间
vmid VM的进程号,即当前运行的java进程号
interval 间隔时间,单位可以是秒或者毫秒,通过指定s或ms确定,默认单位为毫秒
count 打印次数,如果缺省则打印无数次

===统计维度与输出===
1)class 用于查看类加载情况的统计

2)compiler 用于查看HotSpot中即时编译器编译情况的统计

3)gc 用于查看JVM中堆的垃圾收集情况的统计

4)gccapacity 用于查看新生代、老生代及持久代的存储容量情况

5)gccause 用于查看垃圾收集的统计情况(这个和-gcutil选项一样),如果有发生垃圾收集,它还会显示最后一次及当前正在发生垃圾收集的原因。

6)gcnew 用于查看新生代垃圾收集的情况

7)gcnewcapacity 用于查看新生代的存储容量情况

8)gcold 用于查看老生代及持久代发生GC的情况

9)gcoldcapacity 用于查看老生代的容量

10)gcpermcapacity 用于查看持久代的容量

11)gcutil 用于查看新生代、老生代及持代垃圾收集的情况

12)printcompilation HotSpot编译方法的统计

示例

jstat -gc 1618 250 4

5.6)jvisualvm(Java Virtual Machine Monitoring, Troubleshooting, and Profiling Tool)
jvisualvm同jconsole都是一个基于图形化界面的、可以查看本地及远程的JAVA GUI监控工具,Jvisualvm同jconsole的使用方式一样,直接在命令行打入Jvisualvm即可启动,不过Jvisualvm相比,界面更美观一些,数据更实时。

5.7)jconsole
一个java GUI监视工具,可以以图表化的形式显示各种数据。并可通过远程连接监视远程的服务器VM。用java写的GUI程序,用来监控VM,并可监控远程的VM,非常易用,而且功能非常强。命令行里打 jconsole,选则进程就可以了。

需要注意:在运行jconsole之前,必须要先设置环境变量DISPLAY,否则会报错误,Linux下设置环境变量如下:
export DISPLAY=:0.0

5.8)jprofile
JProfiler是一个需要商业授权的全功能的Java剖析工具(profiler),专用于分析J2SE和J2EE应用程序。

它把CPU、执行绪和内存的剖析组合在一个强大的应用中。JProfiler可提供许多IDE整合和应用服务器整合用途。JProfiler直觉式的GUI让你可以找到效能瓶颈、抓出内存漏失(memory leaks)、并解决执行绪的问题。它让你得以对heap walker作资源回收器的root analysis,可以轻易找出内存漏失;heap快照(snapshot)模式让未被参照(reference)的对象、稍微被参照的对象、或在终结(finalization)队列的对象都会被移除;整合精灵以便剖析浏览器的Java外挂功能。

5.9)jca
Java线程分析工具,专业的线程分析工具兼容sun/oracle JDK dump线程堆,图形化显示线程概括信息,非常容易的定位问题。
jca是一个类工具 启动方法

java -jar jca433.jar

5.10)jinfo命令 (Java Configuration Info)
jinfo可以输出并修改运行时的java 进程的opts。用处比较简单,用于输出JAVA系统参数及命令行参数。用法是jinfo -opt pid 如:查看2788的MaxPerm大小可以用 jinfo -flag MaxPermSize 2788。

5.11)Jdb命令 (The Java Debugger)
用来对core文件和正在运行的Java进程进行实时地调试,里面包含了丰富的命令帮助您进行调试。

5.12)Jstatd命令 (Java Statistics Monitoring Daemon)
jstatd是一个基于RMI(Remove Method Invocation)的服务程序,它用于监控基于HotSpot的JVM中资源的创建及销毁,并且提供了一个远程接口允许远程的监控工具连接到本地的JVM执行命令。

jstatd是基于RMI的,所以在运行jstatd的服务器上必须存在RMI注册中心,如果没有通过选项”-p port”指定要连接的端口,jstatd会尝试连接RMI注册中心的默认端口。后面会谈到如何连接到一个默认的RMI内部注册中心,如何禁止默认的RMI内部注册中心的创建,以及如何启动一个外部注册中心。
参数选项

-nr 如果RMI注册中心没有找到,不会创建一个内部的RMI注册中心。
-p port RMI注册中心的端口号,默认为1099。
-n rminame 默认为JStatRemoteHost;如果同一台主机上同时运行了多个jstatd服务,rminame可以用于唯一确定一个jstatd服务;这里需要注意一下,如果开启了这个选项,那么监控客户端远程连接时,必须同时指定hostid及vmid,才可以唯一确定要连接的服务,这个可以参看jps章节中列出远程服务器上Java进程的示例。
-J 用于传递jvm选项到由javac调用的java加载器中,例如,“-J-Xms48m”将把启动内存设置为48M,使用-J选项可以非常方便的向基于Java的开发的底层虚拟机应用程序传递参数。

安全性
jstatd服务只能监视具有适当的本地访问权限的JVM,因此jstatd进程与被监控的JVM必须运行在相同的用户权限中。但是有一些特殊的用户权限,如基于UNIX(TM)为系统的root用户,它有权限访问系统中所有JVM的资源,如果jstatd进程运行在这种权限中,那么它可以监视系统中的所有JVM,但是这也带来了额外的安全问题。

示例

# 使用内部RMI注册中心(默认端口是1099),启动jstatd
jstatd -J-Djava.security.policy=jstatd.all.policy

# 使用外部的RMI注册中心,启动jstatd
rmiregistry&jstatd -J-Djava.security.policy=all.policy

# 外部的RMI注册中心来启动jstatd,此注册中心的端口为2020
rmiregistry 2020&jstatd -J-Djava.security.policy=all.policy -p 2020

# 外部的RMI注册中心来启动jstatd,此注册中心的端口为2020,并且绑定到RMI注册中心的名为AlternateJstatdServerName
rmiregistry 2020&jstatd -J-Djava.security.policy=all.policy -p 2020 -n AlternateJstatdServerName

# 禁止内部RMI注册中心的创建
jstatd -J-Djava.security.policy=all.policy -nr

# 开启RMI日记记录,jstatd运行在开启了日志记录功能的RMI注册中
jstatd -J-Djava.security.policy=all.policy -J-Djava.rmi.server.logCalls=true

5.13)httpwatch
网页数据分析工具,对客户端到服务器端的请求,响应数据有效的监控分析。

6. 生产环境示例
6.1)Tomcat7
catalina.sh(只运行一个Tomcat)

# 8G内存
JAVA_OPTS="
-Dfile.encoding=UTF-8
-server
-Djava.awt.headless=true
-Xms6144m
-Xmx6144m
-XX:NewSize=1024m
-XX:MaxNewSize=2048m
-XX:PermSize=512m
-XX:MaxPermSize=512m
-XX:MaxTenuringThreshold=15
-XX:NewRatio=2
-XX:+AggressiveOpts
-XX:+UseBiasedLocking
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled
-XX:LargePageSizeInBytes=128m
-XX:+UseFastAccessorMethods
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+DisableExplicitGC"
 
# 16G内存(垃圾回收略)
JAVA_OPTS="
-Dfile.encoding=UTF-8
-server
-Xms13312m
-Xmx13312m
-XX:NewSize=3072m
-XX:MaxNewSize=4096m
-XX:PermSize=512m
-XX:MaxPermSize=512m
-XX:MaxTenuringThreshold=10
-XX:NewRatio=2
-XX:+DisableExplicitGC"
 
# 32G内存(垃圾回收略)
JAVA_OPTS="
-Dfile.encoding=UTF-8
-server
-Xms29696m
-Xmx29696m
-XX:NewSize=6144m
-XX:MaxNewSize=9216m
-XX:PermSize=1024m
-XX:MaxPermSize=1024m
-XX:MaxTenuringThreshold=10
-XX:NewRatio=2
-XX:+DisableExplicitGC"

6.2)Hadoop
建议Hadoop进程的GC参数加上如下选项,很多商业版都默认加上了,这对JDK性能提升有很大帮助

-XX:CMSInitiatingOccupancyFraction=70
-XX:+HeapDumpOnOutOfMemoryError
-XX:+UseConcMarkSweepGC
-XX:-CMSConcurrentMTEnabled
-XX:+CMSIncrementalMode
-Djava.net.preferIPv4Stack=true

7. 参考
JDK8中JVM堆内存划分

JDK8引进的JVM参数变化记录
JVM 不稳定参数
java 8 JVM性能优化
Java性能优化攻略详解
Jvm垃圾回收器详细

JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof使用详解
jps命令 (Java Virtual Machine Process Status Tool)

                                 Could not create the Java virtual machine                                 

一般原因如下:
1)执行程序里的JAVA路径不对造成的!通过"echo $JAVA_HOME"查找出JAVA路径,然后更新程序中的JAVA路径配置即可。
2)JVM参数不对造成(可能是stack size太小)。修改JVM_OPTIONS配置里的JVM参数值即可。
3)jdk版本不兼容导致。调整jdk版本(比如jdk版本过低,调整为高版本的jdk)即可。
这篇关于JVM常用命令和性能调优建议 [Could not create the Java virtual machine]的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!