一般垃圾收集器跟内存大小的对应关系
- Serial 几十兆
- PS 上百兆 - 几个G
- CMS - 20G
- G1 - 上百G
- ZGC - 4T - 16T(JDK13)
常见垃圾回收器组合参数设定
:
-XX:+UseSerialGC = Serial + Serial Old
-XX:+UseParNewGC = ParNew + SerialOld
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认)
-XX:+UseG1GC = G1
HotSpot参数分类:
标准: - 开头,所有的HotSpot都支持
非标准:-X 开头,特定版本HotSpot支持特定命令
不稳定:-XX 开头,下个版本可能取消
常见的调优用的是 -X命令。
如:java -XX:+PrintFlagsFinal
命令可以查看所有的XX参数,如果在Linux环境下,可以+ |grep CMS
来过滤指定命令。
一个测试小demo:
package Basic;
import java.util.List;
import java.util.LinkedList;
public class HelloGC {
public static void main(String[] args) {
System.out.println("HelloGC!");
List list = new LinkedList();
for(;;) {
byte[] b = new byte[1024*1024];
list.add(b);
}
}
}
在Eclipse上编写此程序会,会在项目的bin目录下生成对应的HelloGC.class文件,如在"E:\WorkSpace\HelloWorld\bin\Basic"目录:
在CMD窗口,切换到bin目录:
然后就可以调试各种参数。当然也可以在Eclipse中设置JVM参数,此处以CMD方式为例。执行java -XX:+PrintCommandLineFlags Basic.HelloGC
命令,可以看默认的虚拟机参数设置:
图中的现象是:内存溢出(要区分内存泄露与内存溢出)。图中的参数:
-XX:InitialHeapSize=65738496 起始堆大小
-XX:MaxHeapSize=1051815936 最大堆大小
-XX:+UseCompressedClassPointers 之前提过的类型指针压缩
-XX:+UseCompressedOops 普通指针压缩
假如要设置参数,可以使用类似命令:java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC Basic.HelloGC
。
-Xmn 新生代大小
-Xms 初始堆大小
-Xmx 最大堆大小
-XX:+PrintGC 显示GC回收信息
在打印GC方面,除了PrintGC
,还有一些类似的命令:
PrintGCDetails 打印更详细的信息
PrintGCTimeStamps 打印GC时的时间
PrintGCCauses 打印GC产生的原因
上面命令的执行结果:
发现最大堆大小、最小堆大小、年轻代大小已经设置成功。
如果使用CMS的话,打印的信息会更详细一些,命令:java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags -XX:+PrintGC Basic.HelloGC
,结果:
要查看GC日志的详细意义,可以参考:
一般未显示"Full GC",就代表是YGC;
4544K->259K,分别代表回收前后的年轻代大小;6144K代表总的年轻代大小;
后面的代表此次回收所用的时间;
接着的4544K->4356K代表整个堆回收前后的大小;
19840K代表整个堆的大小;
后面的时间分别表示占了用户态多少时间、内核态多少时间、总共多少时间。
发生OOM时的详细dump信息:
total = eden + 1个survivor。
eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000)
后面的内存地址指的是,起始地址,使用空间结束地址,整体空间结束地址。从第一个地址到第三个地址之间的长度是5632K。从第一个地址到第二个地址代表使用完的地址,占总地址的94%。
Mataspeace,元数据区。最后一个是全元数据区所预留的全部空间,倒数第二个是已经占用的空间,倒数第一个是目前的容量是多少,第一个是真正使用的空间。
1. 吞吐量
:用户代码执行时间 /(用户代码执行时间 + 垃圾回收时间)
2. 响应时间
:STW越短,响应时间越好
所谓调优,首先确定目的?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量?
比如科学计算/数据挖掘,追求吞吐量优先。该种情况一般选用PS + PO。
网站、带界面的程序、对外程序的API,一般是响应时间优先。看JDK版本,优先选G1。
调优可以简单分为三个方面:
根据需求进行JVM规划和预调优
优化JVM运行环境(慢,卡顿)
解决JVM运行过程中出现的各种问题(OOM)
调优,从业务场景开始,没有业务场景的调优都没有意义。
无监控(也就是要进行压力测试,这样能看到结果),不调优。
参考调优步骤:
熟悉业务场景
(没有最好的垃圾回收器,只有最合适的垃圾回收器),根据业务场景来选择垃圾回收器。响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)
吞吐量 = 用户时间 /( 用户时间 + GC时间) [PS]
选择回收器组合
计算内存需求
。该步骤比较难以计算,范围较大,内存较小的话,可以回收的频繁一些。选定CPU
(越高越好)设定年代大小、升级年龄
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
上面的命令意思是:按GC时间来生成滚动日志,最多可以生成5个,每个最大20M。
2)或者每天产生一个日志文件
看一些案例:
一个订单产生多少内存。即new出来订单对象,需要多少内存
。假设一个订单对象为512k,1000订单总和是500M左右。初次设定参数后,就可以进行压测,满足不了要求就扩大参数,再不行就加服务器数量
。CDN -> LVS -> NGINX -> 业务系统 -> 每台机器1W并发(10K问题) 100台机器
一般先从CDN开始,在全国做不同的CDN缓存,接下来是一堆的LVS,接下来就是NGINX,接下来就是Tomcat等服务器。
Redis可以撑得住单机1w并发。
此外,架构设计也是和业务逻辑紧密相关的。
在商城付款流程中,普通电商订单 -> 下单 ->订单系统(IO)减库存,减库存和订单的生成应该是异步进行的,最后一步是用户付款。
在具体的功能模块,比如订单生成,最后还会把压力压到一台服务器,可以做分布式本地库存 + 单独服务器做库存均衡。
大流量的处理方法:分而治之
。
频繁GC
,STW长,响应时间变慢。FGC时间变长
。CPU经常100%,需要考虑:工作线程占比高和垃圾回收线程占比高两种情况。
用一个例子来尝试分析问题:
package com.mashibing.jvm.gc;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 从数据库中读取信用数据,套用模型,并把结果进行记录和传输
*/
public class T15_FullGC_Problem01 {
private static class CardInfo {
BigDecimal price = new BigDecimal(0.0);
String name = "张三";
int age = 5;
Date birthdate = new Date();
public void m() {
}
}
private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
new ThreadPoolExecutor.DiscardOldestPolicy());
public static void main(String[] args) throws Exception {
executor.setMaximumPoolSize(50);
for (;;){
modelFit();
Thread.sleep(100);
}
}
private static void modelFit(){
List<CardInfo> taskList = getAllCardInfo();
taskList.forEach(info -> {
// do something
executor.scheduleWithFixedDelay(() -> {
//do sth with info
info.m();
}, 2, 3, TimeUnit.SECONDS);
});
}
private static List<CardInfo> getAllCardInfo(){
List<CardInfo> taskList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
CardInfo ci = new CardInfo();
taskList.add(ci);
}
return taskList;
}
}
使用java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01
让程序运行,输出GC信息。
一般公司都使用网管监控软件,检测服务器,可以进行告警等操作。一般是运维团队首先受到报警信息(CPU Memory)
接着开发才会进行下一步的定位分析。首先要先使用top
命令找到内存使用和CPU占比较高的进程。
然后用top -Hp + 进程ID
查看该进程内的线程内存使用和CPU占比情况,观察进程中的线程,找到哪个线程CPU和内存占比高。示例:
top命令查看的是所有的进程信息,jps可以查看Java进程信息。
图中第一列的PID就是该进程内的线程号。接下来就要用jstack
命令定位具体的线程情况。"jstack + 进程ID"会把该进程的线程情况都列出来:
上图中的NID是十六进制的线程号,用top -Hp + 进程ID
命令看到的线程号是十进制的。
此时就可以看到每个线程的状况,要重点关注的是线程的异常状态,如:WAITING、BLOCKED
jstack中的主要线程状态:
RUNNABLE 线程运行中或I/O等待
BLOCKED 线程在等待monitor锁(synchronized关键字)
TIMED_WAITING 线程在等待唤醒,但设置了时限
WAITING 线程在无限等待唤醒
看一段关键的日志信息:
图中的"t2"是示例的用户线程名称,状态是WAITING,有这么一段:
waiting on <0x0000000088ca3310> (a java.lang.Object)
即在等待着一把锁的释放。假如有一个进程中100个线程,很多线程都在waiting on
,一定要找到是哪个线程持有这把锁,这时候一般是这个线程长期持有这把锁不释放。怎么找?搜索jstack dump的信息,找
,看哪个线程持有这把锁,状态一般是RUNNABLE。
同时,此时也能看到出问题代码的具体位置:
此时就明白阿里Java开发规范中,线程的名称(尤其是线程池)都要写有意义的名称。在使用线程池时,自定义线程名称的方式是:自定义ThreadFactory。
在上面的实验中,用到了jstack、top、top -Hp等命令,当然还有别的命令可以使用:
jps,查看Java进程信息
jinfo,查看一些配置信息,用法是jinfo+进程号,示例:
jstat,查看一些进程信息,但是内容较乱,不常用:
比如jstat -gc 动态观察gc情况;jstat -gc 4655 500 : 每过500个毫秒,动态打印GC的情况
远程的服务器一般是不安装图形化界面的,所以可以在本地和远程服务器建立连接,此时有个标准的协议JMX。也就是说如果要在本地和远程服务器建立连接,就需要在远程服务器上进行JMX的相关配置。一些配置的示例:
shell
java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false XXX
192.168.17.11 basic localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
service iptables stop
chkconfig iptables off #永久关闭
JConsole是JDK自带的图形化CPU监测工具,如果要连接远程服务器,需要连接远程进程:
JConsole连接成功后的界面:
类似工具JVirtualVM界面:
该工具上添加远程连接,成功后界面:
JVirtualVM能看到CPU、类、堆、线程的一些信息。
下面的这张图是最直观的信息显示:有多少类,占多少个字节,有多少个实 例。了解这些信息,也大致能够进行问题定位了,因为有大量的对象未被回收,一定是相关代码出了问题。通过这种图形化界面工具,能够较简单地定位到OOM问题的原因
。
那怎么定位OOM问题的?不是通过图形化工具。因为如果通过图形化界面定位OOM问题的话,代表在远程服务器上一直有个服务在后台运行。那么此时就会有两个问题:
java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError
,这个参数代表OOM的时候会自动产生堆转储文件。 那要获取和JVirtualVM相似的查看对象数量、占用字节相似效果的话,需要用什么么?jmap。示例:jmap - histo 4655 | head -20
,查看数量排名前20的对象信息:
注意在线上系统中,尽量不使用jmap -dump:format=b,file=xxx pid
类似的命令在线转储日志,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合)。
2. 图形界面到底用在什么地方?测试!测试的时候进行监控!(压测观察)
总结来说:先由运维团队报告问题,如CPU高、内存占用高等--->用top命令查出出问题的线程--->如果是锁相关的问题,就继续用stack进行定位到具体线程--->如果发现频繁GC,就用jmap定位到是什么对象一直占用内存,未被回收
。
如果是数据库连接未释放之类的问题,不容易通过上述调试JVM的方式看出,需要看数据库连接池日志。
为什么需要在线排查?在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因。为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出入参,然后重新打包发布,如果打了日志还是没找到问题,继续加日志,重新打包发布。对于上线流程复杂而且审核比较严的公司,从改代码到上线需要层层的流转,会大大影响问题排查的进度。
线上系统一般不用图形化工具来排查问题,因为远程服务器没有装,如果本地连接远程服务器的话,还需要管理员来权限之类的。
arthas不包含jmap功能
。
arthas是阿里的开源在线分析工具。该工具的下载安装可以在github上寻找:
该工具的文件目录:
可以通过命令的方式,启动:
此时输入"1",就可以将arthas绑定在该进程上,然后就可以使用arthas相关命令来观察该进程。绑定成功:
help
可以查看常用命令:
jvm
命令,可以查看JVM的相关信息:
thread
命令可以查看线程相关情况:
thread + 线程号
,可以查看某个线程的详细情况:
dashboard
命令,观察系统情况,类似于top
命令效果:
heapdump
命令可以导出dump文件:
此时可以用jhat命令分析.hprof(dump)文件:
图中表示用最多512M内存来分析,分析4244718个对象。
jhat命令起了Server,7000端口,所以可以在浏览器进行访问jhat解析过的内容。示例:
该界面对底部,还可以查看其它问题
点击第三个、第四个可以查看对象的数量,类似于jmap:
最底部的"OQL Query",可以查询特定问题对象:
点某个,可以看某个对象所占用的字节数和相关的引用:
生产dump文件后,当然也可以用本地图形化工具分析,比如JVirtualVM:
也可以使用OQL查询。
jad
命令可以用来反编译:OOM产生的原因多种多样,有些程序未必产生OOM,不断FGC(CPU飙高,但内存回收特别少)。如上面的示例代码,使用new ScheduledThreadPoolExecutor创建线程池,是有隐藏风险的。
【需掌握】
【需掌握】
【需掌握】
public class StackOverFlow {
public static void main(String[] args) {
m();
}
static void m() {
m(); }
}
Object o = null;
for(int i=0; i<100; i++) {
o = new Object();
//业务处理
}
for(int i=0; i<100; i++) {
Object o = new Object();
}
第一种写法较好,因为当重新创建对象时,之前创建的对象就可以回收,而第二种不能回收。
假设执行以下命令进行测试:
java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC com.mashibing.jvm.gc.T15_FullGC_Problem01
小例子:
[GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K), 0.0268035 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
ParNew:年轻代收集器
6144->640:收集前后的对比
(6144):整个年轻代容量
6585 -> 2770:整个堆的使用情况
(19840):整个堆大小
[GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
//CMS Initial Mark : 初始标记
//8511 (13696) : 老年代使用(最大)
//9866 (19840) : 整个堆使用(最大)
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
//CMS-concurrent-mark : 并发标记
//这里的时间意义不大,因为是并发执行
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//标记Card为Dirty,也称为Card Marking
[GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
// CMS Final Remark : 重新标记
//STW阶段,YG occupancy:年轻代占用及容量
//[Rescan (parallel):STW下的存活对象标记
//weak refs processing: 弱引用处理
//class unloading: 卸载用不到的class
//scrub symbol(string) table:
//cleaning up symbol and string tables which hold class-level metadata and
//internalized string respectively
//CMS-remark: 8511K(13696K): 阶段过后的老年代占用及容量
//10108K(19840K): 阶段过后的堆占用及容量
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
//标记已经完成,进行并发清理
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//重置内部结构,为下次GC做准备
对于CMS而言,在日志中更多的是,关注GC是否频繁,和耗费的时间在不在允许范围之内。
G1可以设置参数,表明每次回收建议的暂停时间,虚拟机参考这个时间,动态调整年轻代大小(以尽量拟合到设置的时间)。
G1的调优目标:尽量不要FGC
。
在G1的日志中,YGC和Mixed GC常常是混在一起的。
[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]
//young -> 年轻代 Evacuation-> 复制存活对象 (即表示是YGC)
//initial-mark 混合回收的阶段,这里是YGC混合老年代回收
[Parallel Time: 1.5 ms, GC Workers: 1] //一个GC线程
[GC Worker Start (ms): 92635.7]
[Ext Root Scanning (ms): 1.1]
[Update RS (ms): 0.0]
[Processed Buffers: 1]
[Scan RS (ms): 0.0]
[Code Root Scanning (ms): 0.0]
[Object Copy (ms): 0.1]
[Termination (ms): 0.0]
[Termination Attempts: 1]
[GC Worker Other (ms): 0.0]
[GC Worker Total (ms): 1.2]
[GC Worker End (ms): 92636.9]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.0 ms]
[Other: 0.1 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.0 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.0 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)]
//这是此次回收的一个总结:18.8M->18.8M,代表没有回收,有内存泄露。
[Times: user=0.00 sys=0.00, real=0.00 secs]
//以下是混合回收其他阶段
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0000078 secs]
[GC concurrent-mark-start]
//无法evacuation(复制),进行FGC
[Full GC (Allocation Failure) 18M->18M(20M), 0.0719656 secs]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38
76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]
-Xmn -Xms -Xmx -Xss
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-XX:+UseTLAB
-XX:TLABSize
-XX:+DisableExplicitGC
System.gc()默认会触发一次Full GC,如果在代码中不小心调用了System.gc()会导致JVM间歇性的暂停。
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-verbose:class
-XX:+PrintFlagsFinal / -XX:+PrintFlagsInitial
-Xloggc:opt/log/gc.log
-XX:MaxTenuringThreshold
这些不建议更改
-XX:SurvivorRatio
该值默认为8,即Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10。
-XX:+ParallelGCThreads
并行收集器的线程数
,同样适用于CMS,一般设为和CPU核数相同1)在 JDK 1.8 中,如果使用 CMS,无论 UseAdaptiveSizePolicy 如何设置,都会将 UseAdaptiveSizePolicy 设置为 false;不过不同版本的JDK存在差异;
2)UseAdaptiveSizePolicy不要和SurvivorRatio参数显示设置搭配使用,一起使用会导致参数失效;
3)由于AdaptiveSizePolicy会动态调整 Eden、Survivor 的大小,有些情况存在Survivor 被自动调为很小,比如十几MB甚至几MB的可能,这个时候YGC回收掉 Eden区后,还存活的对象进入Survivor 装不下,就会直接晋升到老年代,导致老年代占用空间逐渐增加,从而触发FULL GC,如果一次FULL GC的耗时很长(比如到达几百毫秒),那么在要求高响应的系统就是不可取的。
-XX:+UseConcMarkSweepGC
-XX:ParallelCMSThreads
-XX:GCTimeRatio
-XX:MaxGCPauseMillis
-XX:+UseG1GC
-XX:MaxGCPauseMillis
-XX:G1NewSizePercent
-XX:G1MaxNewSizePercent
-XX:GCTimeRatio
线程和纤程的区别,一个通过内核空间,一个不通过内核空间。目前在Java中纤程可以通过第三方库 Quasar来实现。
1.生产环境中,倾向于将最大堆内存和最小堆内存设置为
:(为什么?)
A: 相同 B:不同
A,好处是:
1) 避免JVM在运行过程中向操作系统申请内存
2)延后启动后首次GC的发生时机
3)减少启动初期的GC次数
什么是响应时间优先?
注重的是垃圾回收时STW的时间最短
。
什么是吞吐量优先?
吞吐量是指应用程序线程用时占程序总用时的比例
,也就是说尽量多让用户程序去执行。
ParNew和PS的区别是什么?
都是年轻代多线程收集器。
ParNew 回收器是通过控制 垃圾回收 的 线程数 来进行参数调整,而 Parallel Scavenge 回收器更关心的是程序运行的吞吐量
。即一段时间内,用户代码 运行时间占 总运行时间 的百分比。
ParNew和ParallelOld的区别是什么?(年代不同,算法不同)
前者是年轻代收集器,后者是老年代收集器,然后解释两者。
长时间计算的场景应该选择:吞吐量优先的收集器和策略。
大规模电商网站应该选择:停顿时间少(即响应时间快)的收集器和策略。
JDK1.7 1.8 1.9的默认垃圾回收器是什么?如何查看?
jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代);
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代);
jdk1.9 默认垃圾收集器G1。
java -XX:+PrintCommandLineFlags -version
命令可以查看使用的垃圾回收器。
所谓调优,到底是在调什么?
本人认为,是根据业务需要,在吞吐量和响应时间之间做出选择。
如果采用PS + ParrallelOld组合,怎么做才能让系统基本不产生FGC
应该和下个问题的答案是有相通之处的。
如果采用ParNew + CMS组合,怎样做才能够让系统基本不产生FGC
1)加大JVM内存
2)加大Young(年轻代)的比例
3)提高Y-O(最大值是15)的年龄
4)提高S(survivor)区比例
5)避免代码内存泄漏
如果G1产生FGC,你应该做什么?
1)扩内存
2)提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)
3)降低MixedGC触发的阈值,让MixedGC提早发生(默认是45%)
。具体的参数是:
-XX:InitiatingHeapOccupancyPercent=45