1、JVM Server与Client运行模式
JVM Server模式与client模式启动,最主要的差别在于:-Server模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升.原因是:
当虚拟机运行在-client模式的时候,使用的是一个代号为C1的轻量级编译器, 而-server模式启动的虚拟机采用相对重量级,代号为C2的编译器. C2比C1编译器编译的相对彻底,,服务起来之后,性能更高.
java -version 可以直接查看出你使用的是client还是 server
client
C:\Documents and Settings\Administrator>java -version
java version "1.6.0_21"
Java(TM) SE Runtime Environment (build 1.6.0_21-b06)
Java HotSpot(TM) Client VM (build 17.0-b16, mixed mode, sharing)
server
[root@kaifa02 ~]# java -version
java version "1.6.0_06"
Java(TM) SE Runtime Environment (build 1.6.0_06-b02)
Java HotSpot(TM) Server VM (build 10.0-b22, mixed mode)
两种模式的切换可以通过更改配置(jvm.cfg配置文件)来实现:
32位的虚拟机在目录JAVA_HOME/jre/lib/i386/jvm.cfg,
64位的在JAVA_HOME/jre/lib/amd64/jvm.cfg, 目前64位只支持server模式, 配置内容大致如下
-server KNOWN
-client KNOWN
-hotspot ALIASED_TO -client
-classic WARN
-native ERROR
-green ERROR
当JVM用于启动GUI界面的交互应用时适合于使用client模式,当JVM用于运行服务器后台程序时建议用Server模式。
JVM在client模式默认-Xms是1M,-Xmx是64M;JVM在Server模式默认-Xms是128M,-Xmx是1024M。我们可以通过运行:java -version来查看jvm默认工作在什么模式。
示例
nohup java -Xloggc:jvm.log -server -Xms1000m -Xmn800m -Xmx1000m -XX:MaxPermSize=512m -classpath $CLASSPATH com.com... >>log.log &
- -Xms
设置虚拟机可用内存堆的初始大小,缺省单位为字节,该大小为1024的整数倍并且要大于1MB,可用k(K)或m(M)为单位来设置较大的内存数。初始堆大小为2MB。
例如:-Xms6400K,-Xms256M
-Xmx
设置虚拟机内存堆的最大可用大小,缺省单位为字节。该值必须为1024整数倍,并且要大于2MB。可用k(K)或m(M)为单位来设置较大的内存数。缺省堆最大值为64MB。
例如:-Xmx81920K,-Xmx80M
-Xloggc:
将虚拟机每次垃圾回收的信息写到日志文件中,文件名由file指定,文件格式是平文件,内容和-verbose:gc输出内容相同。
-client,-server
这两个参数用于设置虚拟机使用何种运行模式,client模式启动比较快,但运行时性能和内存管理效率不如server模式,通常用于客户端应用程序。相反,server模式启动比client慢,但可获得更高的运行性能。
在 windows上,缺省的虚拟机类型为client模式,如果要使用server模式,就需要在启动虚拟机时加-server参数,以获得更高性能,对服 务器端应用,推荐采用server模式,尤其是多个CPU的系统。在Linux,Solaris上缺省采用server模式。
2、收集器
查看默认的收集器是什么?
2.1、查看步骤
linux和cmd执行命令是一样的:
java -XX:+PrintCommandLineFlags -version
输出如下(举例):
针对上述的-XX:UseParallelGC,这边我们引用《深入理解Java虚拟机:JVM高级特性与最佳实践》的介绍:
也就是说,打开此开关,使用的垃圾收集器是:新生代(Parallel Scavenge),老年代(Ps MarkSweep)组合。
2.2、验证下,是不是那么回事吧
我用ide起了一个程序,然后在main中进行长时间睡眠。启动时,设置其VM 参数如下:
然后用Jconsole连接该程序,切换到VM概要这个tab,注意下图红圈圈出来的地方:
结合第一步中的资料,很容易验证,使用-XX:UseParallelGC的情况下,使用的垃圾收集器为:新生代(Ps Scanvenge),老年代(Ps MarkSweep,与Serial Old)。
2.3、Ps Scanvenge的简要介绍
这边附上我的简单理解:该垃圾收集器适用于新生代,采用标记复制算法、多线程模型进行垃圾收集。
与其他新生代垃圾收集器的差别是,它更关注于吞吐量,而不是停顿时间。一般来说,需要与用户交互的
程序更关注较短的停顿时间,而如果是需要达成尽量大的吞吐量的话,则该处理器会更加适合。
其通过-XX:UseAdaptiveSizePolicy参数,可以开启其自动调节功能,适用于对垃圾收集器的调优不太了解的
2.4、Serial Old的简要介绍
我的理解:和其他老年代垃圾处理器一样,都是使用的标记整理算法,(毕竟没有靠山可以担保,没法复制,只能自己整理了,哎),
2.5、Serial Old和Ps MarkSweep的区别
如上图所示,也说了,在实际中,(正如第二节的截图所示),实际应用中,大多使用的就是Ps MarkSweep。
Ps MarkSweep是以Serial Old为模板设计的,按照我们程序员的说法,估计是拷贝过来,改吧改吧出来的。
所以差不太多。
2.6、我们可以自己设置收集器,通过配置参数来修改启动的收集器
参考:https://www.cnblogs.com/trgl/p/7271111.html
前阵--对底层账单系统进行了压测调优,调优的最后一步--jvm启动参数中,减小了线程的堆栈空间:-XX:ThreadStackSize=256K,缩减至原来的四分之一,效果明显,不过并没有调试其他内存空间及gc相关参数。这次有机会在实际压测中,调优这一部分内容,笔者以cms收集器为例,将有、无调优配置情况下的压测结果进行对比,来分析各项调用参数的意义及效果。
准备工作:
1.调用查询接口的测试jar包,作为dubbo-consumer,依赖了查询服务的api,测试module基于maven开发,执行maven clean package即可通过编译得到jar包,本次查询api使用账户查询接口getUserAccount
2.JMeter:Apache组织开发的基于Java的压力测试工具,添加测试计划,线程组容量均为200
方案:
无限次请求查询接口(保证任意时刻并发量相同),观察Error%为0,当请求平稳进行时的tps,为该接口吞吐量
实施:
2.6.1.jvm中只配置打印gc日志等监控参数
-XX:+PrintGCDetails:打印gc日志详细信息
-XX:+PrintGCTimeStamps:打印gc发生时相对jvm启动的时间戳,(后来加入了PrintGCDateStamps,打印gc发生的日期)
-Xloggc:设置gc日志的生成位置
压测聚合报告
压测数据稳定后,我们观察到200线程并发压力下,tps可达到1200+,99%Line600ms左右,我们观察一下gc日志
这里截取了一次minor gc的数据,两次gc发生间隔很短(最短不到1s就发生第二次minor gc),系统默认为新生代只分配了100到200MB左右空间,老年代也就是200到300MB左右,可以看出默认分配的空间并不大,并且由于新生代容量非常小,一直在频繁发生gc。jvm默认采用PSYoungGen-并行收集器来回收新生代空间。
2.6.2.jvm中加入cms收集器,并手动规划jvm空间分配
-Xms4096M:堆容量初始值
-Xmx4096M:堆容量最大值
-Xmn1024M:新生代容量,所以老年代容量 = 堆容量 - 新生代容量 = 3072M
-Xss256K:线程堆栈空间大小
-XX:MaxDirectMemorySize:Direct Buffer Memory大小
-Djava.awt.headless=true:使用缺少外设的系统配置模式
-Dfile.encoding=UTF-8:设置编码规范
jmx配置用于远程管理
-XX:+HeapDumpOutOfMemoryError:当出现OOM时,打印堆快照
-XX:HeapDumpPath:堆快照打印路径,建议文件后缀设为hprof,可被MAT识别
-XX:+DisableExplicitGC:关闭System.gc()
-XX:SurvivorRatio=1:Eden区与Survivor区的大小比值
-XX:+UserConcMarkSweepGC:使用CMS收集器
-XX:+UserParNewGC:新生代使用ParNew收集器
-XX:+CMSParallelRemarkEnabled:降低标记停顿
-XX+UseCMSCompactAtFullCollection:在full gc的时候,对年老代的压缩
-XX:CMSFullGCsBeforeCompaction=0:full gc后不压缩老年代内存空间
-XX:LargePageSizeInBytes:内存页的大小
-XX:+UseFastAccessorMethods:原始类型的快速优化
-XX:+UseCMSInitiatingOccupancyOnly:使用手动定义初始化定义开始CMS收集,禁止hostspot自行触发CMS GC
-XX:CMSInitiatingOccupancyFraction=80:老年代使用80%后开始CMS收集
-XX:SoftRefLRUPolicyMSPerMB=0:每兆堆空闲空间中SoftReference的存活时间为0秒
以上这些配置我们重点关注jvm空间分配相关参数和收集器相关参数,首先扩大了堆空间大小至4G,新生代1G,老年代3G,直观上系统可以承载更多实例的创建,但是同样也可能造成因对象引用导致的寻址时间增加。另外,手动配置了使用CMS收集器回收老年代,CMS是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高,但由于其拥有并发线程进行标记工作,所以尽可能地降低了GC时服务的停顿时间。而为了保证应用线程不停顿,系统需要更多的CPU资源。总得来说,CMS回收器减少了回收的停顿时间,但是降低了堆空间的利用率,也降低了吞吐量,所以使用该收集器的前提是应用程序对停顿比较敏感,并且有许多生存周期长的对象。我们在此处使用该收集器主要用来观察手动配置后与默认配置相比有哪些优劣。压测结果如下
我们发现这种情况下吞吐量有小幅度提高,并且响应时间降低,那么jvm起了哪些作用呢,我们看一下gc日志
eden、s1、s2容量被新生代均分为3份,两个gc间隔时间为2s左右,已进行了2次full gc,其余时间一直在执行minor gc。两张图结合分析,我们发现随着新生代容量的扩大,jvm创建实例能力略有提高,同样200个线程的并发压力,同一时间可以承受更多的请求,那是不是新生代容量越大吞吐量就越高呢,笔者后来将新生代内存调至3G,吞吐量不增反降,说明其二者并不是线性相关。我们此时可以确定的是:jvm内存容量要适当增大,并且内存分配比例要尽可能符合jvm对对象的实际创建情况。
总结:
jvm参数是灵活使用jvm的关键,我们在使用时需要谨慎添加,当然如果搭配的好,至少在系统内部服务层面,性能会有客观的提升。但这也是想表达的另一个观点,如何配置jvm内存空间,选择哪些gc收集器组合,并没有绝对的标准,绝对的好与不好,都是在多次的权衡、搭配下,产生对你所运行的服务一个相对好的效果。jvm调优路漫漫,吾将继续求索。
3、性能调优的一些手段
3.1 性能调优的目的
减少minor gc的频率、以及full gc的次数
3.2 性能调优的手段
1.使用JDK提供的内存查看工具,如JConsole和Java VisualVM
2.控制堆内存各个部分所占的比例
3.采用合适的垃圾收集器
手段1:内存查看工具(如jstat)和GC日志分析
n -verbose.gc:显示GC的操作内容。打开它,可以显示最忙和最空闲收集行为发生的时间、收集前后的内存大小、收集需要的时间等。
n -xx:+printGCdetails:详细了解GC中的变化。
n -XX:+PrintGCTimeStamps:了解这些垃圾收集发生的时间,自JVM启动以后以秒计量。
n -xx:+PrintHeapAtGC:了解堆的更详细的信息。
此外还包括通过jmap+MAT的方式分析可能发生的内存泄露的原因,哪些对象占用内存较大等。
手段2:针对新生代和旧生代的比例
如果新生代太小,会导致频繁GC,而且大对象对直接进入旧生代引发full gc
如果新生代太大,会诱发旧生代full gc,而且新生代的gc耗时会延长
建议新生代占整个堆1/3合适,相关JVM参数如下:
n -Xms:初始堆大小
n -Xmx:最大堆大小
n - Xmn:新生代大小
n -XX:PermSize=n:持久代最大值
n -XX:MaxPermSize=n:持久代最大值
n -XX:NewRatio=n:设置新生代和旧生代的比值。如:为3,表示新生代与旧生代比值为1:3,新生代占整个新生代旧生代和的1/4
手段3:针对Eden和Survivor的比例
如果Eden太小,会导致频繁GC
如果Eden太大,会导致大对象直接进入旧生代,降低对象在新生代存活时间
n -XX:SurvivorRatio=n:新生代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
n -XX:PretenureSizeThreshold:直接进入旧生代中的对象大小,设置此值后,大于这个参数的对象将直接在旧生代中进行内存分配。
n -XX:MaxTenuringThreshold:对象转移到旧生代中的年龄,每个对象经历过一次新生代GC(Minor GC)后,年龄就加1,到超过设置的值后,对象转移到旧生代。
手段4:采用正确的垃圾收集器
通过JVM参数设置所使用的垃圾收集器参考前面的介绍,这里关注其他一些设置。
并行收集器设置
n -XX:ParallelGCThreads=n:设置并行收集器收集时并行收集线程数
n -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间,仅对ParallelScavenge生效
n -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比,仅对Parallel Scavenge生效
并发收集器设置
n -XX:CMSInitiatingOccupancyFraction:默认设置下,CMS收集器在旧生代使用了68%的空间后就会被激活。此参数就是设置旧生代空间被使用多少后触发垃圾收集。注意要是CMS运行期间预留的内存无法满足程序需要,就会出现concurrent mode failure,这时候就会启用Serial Old收集器作为备用进行旧生代的垃圾收集。
n -XX:+UseCMSCompactAtFullCollection:空间碎片过多是标记-清除算法的弊端,此参数设置在FULL GC后再进行一个碎片整理过程
n -XX:CMSFullGCsBeforeCompaction:设置在若干次垃圾收集之后再启动一次内存碎片整理