前言
入门JVM垃圾回收机制后,接下来可以学习性能调优了。主要有两部分内容:
- JDK工具的使用。
- 调优策略。
兵器谱
jps
列出正在运行的虚拟机进程,用法如下:
代码语言:javascript复制jps [-option] [hostid]
选项 | 作用 |
---|---|
q | 只输出LVMID,省略主类的名称 |
m | 输出main method的参数 |
l | 输出完全的包名,应用主类名,jar的完全路径名 |
v | 输出jvm参数 |
jstat
监视虚拟机运行状态信息,使用方式:
选项 | 作用 |
---|---|
gc | 输出每个堆区域的当前可用空间以及已用空间,GC执行的总次数,GC操作累计所花费的时间。 |
gccapactiy | 输出每个堆区域的最小空间限制(ms)/最大空间限制(mx),当前大小,每个区域之上执行GC的次数。(不输出当前已用空间以及GC执行时间)。 |
gccause | 输出-gcutil提供的信息以及最后一次执行GC的发生原因和当前所执行的GC的发生原因。 |
gcnew | 输出新生代空间的GC性能数据。 |
gcnewcapacity | 输出新生代空间的大小的统计数据。 |
gcold | 输出老年代空间的GC性能数据。 |
gcoldcapacity | 输出老年代空间的大小的统计数据。 |
gcpermcapacity | 输出持久带空间的大小的统计数据。 |
gcutil | 输出每个堆区域使用占比,以及GC执行的总次数和GC操作所花费的事件。 |
比如:
代码语言:javascript复制jstat -gc 28389 1s
每隔1秒输出一次JVM运行信息:
jmap
生成堆存储快照,使用方式:
jstack
生成虚拟机当前时刻的线程快照,帮助定位线程出现长时间停顿的原因,用法:
代码语言:javascript复制jstack <pid>
Monitor
Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者Class的锁。每一个对象都有,也仅有一个 monitor。下面这个图,描述了线程和 Monitor之间关系,以及线程的状态转换图:
进入区(Entrt Set):表示线程通过synchronized要求获取对象的锁,但并未得到。
拥有者(The Owner):表示线程成功竞争到对象锁。
等待区(Wait Set):表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒。
线程状态
- NEW,未启动的。不会出现在Dump中。
- RUNNABLE,在虚拟机内执行的。
- BLOCKED,等待获得监视器锁。
- WATING,无限期等待另一个线程执行特定操作。
- TIMED_WATING,有时限的等待另一个线程的特定操作。
- TERMINATED,已退出的。
举个例子:
线程状态:
t1没有抢到锁,所以显示BLOCKED。t2抢到了锁,但是处于睡眠中,所以显示TIMED_WAITING,有限等待某个条件来唤醒。 把睡眠的代码去掉,线程状态变成了:
t1显示RUNNABLE,说明正在运行,这里需要额外说明一下,如果这个线程正在查询数据库,但是数据库发生死锁,虽然线程显示在运行,实际上并没有工作,对于IO型的线程别只用线程状态来判断工作是否正常。 把MyTask的代码小改一下,线程拿到锁之后执行wait,释放锁,进入等待区。
线程状态如下:
两个线程都显示WAITING,这次是无限期的,需要重新获得锁,所以后面跟了on object monitor。
再来个死锁的例子:
线程状态:
这个有点像哲学家就餐问题,每个线程都持有对方需要的锁,那就运行不下去了。
调优策略
两个基本原则:
- 将转移到老年代的对象数量降到最少。
- 减少Full GC的执行时间。目标是Minor GC时间在100ms以内,Full GC时间在1s以内。
主要调优参数:
设定堆内存大小,这是最基本的。
- -Xms:启动JVM时的堆内存空间。
- -Xmx:堆内存最大限制。
设定新生代大小。
新生代不宜太小,否则会有大量对象涌入老年代。
- -XX:NewRatio:新生代和老年代的占比。
- -XX:NewSize:新生代空间。
- -XX:SurvivorRatio:伊甸园空间和幸存者空间的占比。
- -XX:MaxTenuringThreshold:对象进入老年代的年龄阈值。
设定垃圾回收器
年轻代:-XX: UseParNewGC。
老年代:-XX: UseConcMarkSweepGC。
CMS可以将STW时间降到最低,但是不对内存进行压缩,有可能出现“并行模式失败”。比如老年代空间还有300MB空间,但是一些10MB的对象无法被顺序的存储。这时候会触发压缩处理,但是CMS GC模式下的压缩处理时间要比Parallel GC长很多。
G1采用”标记-整理“算法,解决了内存碎片问题,建立了可预测的停顿时间类型,能让使用者指定在一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。