VisualVM使用与调优案例
1.JVM构成
先看两张图,第一张是1.8版本之前的JVM,第二张图是JVM1.8版本
图一
图二
对比两张图可以发现:JDK1.8之后移除了堆内存中的永久代(Permanent),改为了存储在本地内存的元空间(MetaSpace)
为何要移除永久代
永久代启动时大小是固定的,不方便对其进行调优,增加了垃圾收集的复杂度以及降低回收的效率
类及方法的大小很难确定,因此永久代到底要设置多大并不好把握,如果设置过小那么就容易导致永久代溢出,设置过大则容易导致老年代溢出
2.VisualVM远程监控的配置连接
参考博客:https://www.cnblogs.com/sunshine-2015/p/5547128.html
连接远程Tomcat
hostname -i 查阅自己的IP是否是公网IP/内网IP,如果外网使用,请用外网的IP
通过jstatd启动RMI
远程访问jstatd需要授权,在JAVA_HOME/bin路径下创建jstatd.all.policy文件,内容:grant codebase “flie:${java.home}/../lib/tools.jar” {
Permission java.security.AllPermission;};
然后敲命令
jstatd -J-Djava.security.policy=jstatd.all.policy &
配置连接jstatd方式
配置参数,输入IP,端口默认1099
调优完毕,关闭授权
由于jstatd.all.policy授权访问,因为安全问题,调优之后关闭(删除该文件/关闭提供远程连接的进程[pid:10824])
案例分析
- isualVM案例分析
① 堆内存分析
写了一段代码,这段代码主要的逻辑是:
- 有四次循环且每次循环的次数是递增的
- 每个循环里面都是new一个对象,将new出来的对象按key,value形式存入HashMap当中
如图5.1:黑色标明的1/2/3/4分别对应了代码里的四次循环,每次循环次数是增加的,因此耗用的系统资源会更多,图5.1-1表明,CPU耗用量逐次增加,堆内存逐次增加,并且你看到堆内存没有被进行回收,没有进行GC,正常情况如图5.1-2表明内存分配以及回收
如图5.2:内存中占用量最大的类型是:char[],hashMap和String以及TestMemory。根据这张图内存占用量的排序,你应该可以推测出,应该是TestMemory对象过多,应该又是存到hashMap当中的,这时候就大概可以知道代码位置了
贴上代码:
package com.test; import java.util.HashMap; import java.util.Map; public class CyclicDependencies { private static final Map map = new HashMap(); public static void main(String args[]){ try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0; i<1000000;i++){ TestMemory t = new TestMemory(); map.put("key"+i,t); } System.out.println("first"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0; i<1000000;i++){ TestMemory t = new TestMemory(); map.put("key"+i,t); } System.out.println("second"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0; i<3000000;i++){ TestMemory t = new TestMemory(); map.put("key"+i,t); } System.out.println("third"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0; i<4000000;i++){ TestMemory t = new TestMemory(); map.put("key"+i,t); } System.out.println("forth"); try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("qqqq"); } }
②线程死锁分析
写了一段死锁代码,在eclipse里运行
VisualVM会自动监测到死锁发生,并弹出红色警告文字“检测到死锁!生成一个线程Dump以获取更多信息
生成的Dump文件可以看到deadlock信息 Thead-1和Thead-2以及引发死锁的代码位置
然后我们回看一下代码,Thead - 1想要锁住b,但b被Thead - 2锁住了,同样Thead - 2想要锁住a,但a被Thead - 1锁住了,这就形成了死锁
请看代码:
public class DeadLock { public static void main(String[] args) { final Object a = new Object(); final Object b = new Object(); new Thread() { @Override public void run() { try { synchronized (a) { System.out.println("Thread 1 got the lock of a"); Thread.sleep(1000); System.out.println("Thread 1 was trying to get the lock of b"); synchronized (b) { System.out.println("Thread 1 win"); } } } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); new Thread() { @Override public void run() { try { synchronized (b) { System.out.println("Thread 2 got the lock of b"); Thread.sleep(1000); System.out.println("Thread 2 was trying to get the lock of a"); synchronized (a) { System.out.println("Thread 2 win"); } } } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); } }
3.调优建议
JVM调优没有固定的模板配置,它需要根据应用实际情况区别对待
但是会有以下的通用建议
a.Xms的值与Xmx的值要尽量保持一致
在很多情况下,-Xms和-Xmx设置成一样的。这么设置,是因为当Heap不够用时,会发生内存抖动,影响程序运行稳定性
b.最大内存设置不能超过物理内存
机器物理内存16G,给堆分配的内存肯定不可以设置成16G或者更多。因为除了堆内存还有非堆内存,应当视具体情况合理分配内存
c.尽量减少Minor GC/Full GC的频率以及持续的时间
理想的结果是:YGC发生次数较少且每次时间较快&&很长时间不发生FGC
Eden区分配空间过少,GC的频率会较高
Eden区分配空间过多,GC的时候持续时间会较长
尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间
尽量不要创建过大的对象及数组