目录
jvm参数常用配置
1. 堆设置
- -Xms:初始堆大小
- -Xmx:最大堆大小
- -XX:NewSize=n:设置年轻代大小
- -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3
- -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
- -XX:MaxPermSize=n:设置持久代大小
2. 收集器设置
- -XX:+UseSerialGC:设置串行收集器
- -XX:+UseParallelGC:设置并行收集器
- -XX:+UseParalledlOldGC:设置并行年老代收集器
- -XX:+UseConcMarkSweepGC:设置并发收集器
- -XX:+UseParNewGC对年轻代采用多线程并行回收
3. 垃圾回收统计信息
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintGCTimeStamps
- -Xloggc:filename
JVM性能调优监控工具(jdk自带)
- jps:用来输出JVM中运行的进程状态信息。
- jstack:用于生成虚拟机当前时刻的线程快照。当cpu高怀疑有死循环或者死锁时可以使用。
- jstat:监视虚拟机各种运行状态信息,可以显示本地或者是远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。可以使用此工具查看gc情况,分析是否有内存泄漏。
- jmap:生成虚拟机的堆内存转储快照。
- JVisualVM:是一种集成了多个JDK命令行工具的可视化工具,它囊括的命令行工具包括jstat,JConsole,jstack,jmap和jinfo,可以查看cpu、内存、类、线程的情况,查看虚拟机运行参数,可以导出堆dump文件,也可以分析dump文件,还可以安装Visual GC插件查看gc情况。
jvm垃圾收集器
- serial是默认的新生代收集器,但会stop the world带来不良体验。
- parnew是多线程的serial,公司目前使用,多核下优于serial。
- parallel scavenge关注吞吐量。
- serial old
- parallel old
- cms注重最短回收停顿时间,思想体现在回收线程与用户线程并行。
- g1最先进但尚未成熟,思想是将堆划分为多个块,化整为零,避免全堆扫描,但由于各块之间对象互相引用,所以具体实现特别复杂。
"高吞吐量"和"低暂停时间"是一对相互竞争的目标(矛盾)。应用程序在GC期间必须停止(或者仅在GC的特定阶段,这取决于所使用的算法),然而这会增加额外的线程调度开销:直接开销是上下文切换,间接开销是因为缓存的影响。 加上JVM内部安全措施的开销,这意味着GC及随之而来的不可忽略的开销,将增加GC线程执行实际工作的时间。 因此我们可以通过尽可能少运行GC来最大化吞吐量。然而,仅仅偶尔运行GC意味着每当GC运行时将有许多工作要做,因为在此期间积累在堆中的对象数量很高。 单个GC需要花更多时间来完成, 从而导致更高的平均和最大暂停时间。 因此,考虑到低暂停时间,最好频繁地运行GC以便更快速地完成。 这反过来又增加了开销并导致吞吐量下降,我们又回到了起点。综上所述,在设计(或使用)GC算法时,我们必须确定我们的目标:一个GC算法只可能针对两个目标之一(即只专注于最大吞吐量或最小暂停时间),或尝试找到一个二者的折衷。在注重吞吐量和cpu资源敏感的场合优先考虑parallel scavenge和parallel old组合。parnew和cms组合优先考虑与用户交互较多的场景,希望系统停顿时间最短,注重服务的响应速度,如常见WEB、B/S系统的服务器上的应用。
GC机制(分代收集)
新生代采用标记复制算法,老年代采用标记清除(cms)或标记整理算法。
对象首先在eden区分配,当Eden区没有足够空间进行分配时,虚拟机会发起一次 Minor GC,把存活的对象复制到to space区域,如果to space区域不够,则利用担保机制进入老年代区域。
对象进入老年代的方法:
- 大对象直接进入老年代,可以指定阈值。
- 长期存活的对象进入老年代。
- 空间分配担保(eden区放不下,触发young gc,eden区存活对象又大于survivor区,所以通过分配担保提前转移到老年代)。
- 同龄对象总和大于survivor空间的一半也能进老年代。
GC触发时机:
- Minor GC触发条件:eden区满时,触发MinorGC。即申请一个对象时,发现eden区不够用,则触发一次MinorGC。在MinorGC时,会把存活的对象复制到to space区域,如果to space区域不够,则利用担保机制进入老年代区域。
- Full GC触发条件:老生代空间不够分配新的内存、System.gc()、通过Minor GC后进入老年代的平均大小大于老年代的可用内存、Metaspace空间不足
jvm调优案例
问题:新部署的测试环境卡顿
使用gc viewer分析日志发现:
- full gc非常多,比例超过minor gc,导致卡顿
使用jinfo或者visualvm观察jvm配置信息:
- 发现其使用的垃圾收集器是parallel scavenge + serial old
- -XX:MaxTenuringThreshold=0 经过多少次minor gc 后进入年老代,设置为0的话直接进入年老代,这是不太合理的,正常应该在年轻代多呆一段时间,真正需要到年老代的才转过去。默认为15。
- -XX:SurvivorRatio=100 年轻代中eden和一块suvivor区的空间比例,这里设置成100有问题,新生代和老年代的大小都是512M,suvivor区空间大概为5M,一次minor gc后基本都转到年老代了,年轻代没有起到过滤作用
通过分析可以发现:由于survivor区内存和老年代晋升年龄设置不合理,导致新生代没有起到过滤作用,不常用的对象也进入了老年代,而且 serial old是单线程收集器,导致了卡顿问题。因此我们将收集器改为parnew + cms(最短暂停时间)、晋升年龄-XX:MaxTenuringThreshold=15、Eden区与Survivor区的比值-XX:SurvivorRatio=8。