jvm优化基础

我们为什么要对jvm做优化?

在本地开发环境中我们很少会遇到需要对jvm进行优化的需求,但是到了生产环境,我们可能将有下面的需求:

  • 运行的应用 “卡住了”,日志不输出,程序没有反应
  • 服务器的 CPU负载突然升高
  • 在多线程应用下,如何分配线程的数量?

对jvm有更深入的学习,我们不仅要让程序能跑起来,而且是可以跑的更快!可以分析解决在生产环境中所遇到的各种“棘手”的问题。

jvm的运行参数

测试用例:
TestJVM.java

public class TestJVM {
    public static void main(String[] args) {
        String str = System.getProperty("str");
        if (str == null) {
            System.out.println("itcast");
        } else {
            System.out.println(str);
        }
    }
}

标准参数

输出所有的标准参数:java -help
查看jvm版本:java ‐version
通过-D设置系统属性参数:java ‐Dstr=123 TestJVM

[root@node01 test]# java TestJVM
itcast
[root@node01 test]# java ‐Dstr=123 TestJVM
123

server运行模式:java -server TestJVM
先打印版本信息,再执行后面的命令:‐showversion

[root@node01 test]# java ‐server ‐showversion TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)
Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, mixed mode)
itcast

-X参数

查看非标准参数:java -X
在解释模式 (interpreted mode)运行:java ‐Xint TestJVM
在编译模式(compiled mode)运行:java ‐Xcomp TestJVM
在混合模式(mixed mode)运行:java TestJVM

#强制设置为解释模式
[root@node01 test]# java  ‐showversion ‐Xint TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)
Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, interpreted mode)
itcast
#强制设置为编译模式
[root@node01 test]# java  ‐showversion ‐Xcomp TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)
Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, compiled mode)
itcast
#注意:编译模式下,第一次执行会比解释模式下执行慢一些,注意观察。
#默认的混合模式
[root@node01 test]# java  ‐showversion TestJVM
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141‐b15)
Java HotSpot(TM) 64‐Bit Server VM (build 25.141‐b15, mixed mode)
itcast

-XX参数(重点)

-XX参数的使用有2种方式,一种是boolean类型,一种是非boolean类型:
boolean 类型:
如: -XX:+DisableExplicitGC 表示禁用手动调用gc操作,也就是说调用System.gc()无效
非 boolean类型:
如: -XX:NewRatio=1 表示新生代和老年代的比值

-Xms与-Xmx参数(最常用)

-Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小。
-Xmx2048m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为2048M。
-Xms512m:等价于-XX:InitialHeapSize,设置JVM初始堆内存为512M。

[root@node01 test]# java ‐Xms512m ‐Xmx2048m TestJVM
itcast

查看jvm的运行参数jinfo

运行java命令时打印参数:java ‐XX:+PrintFlagsFinal ‐version
值的操作符是=或:=,分别代表默认值和被修改的值
查看正在运行的jvm:jinfo ‐flags 6219,其中6219是进程pid
通过ps -ef |grep java也看可以查看java进程,但是jdk的工具jps更方便

#通过jps 或者  jps ‐l 查看java进程
[root@node01 bin]# jps ‐l
6358 sun.tools.jps.Jps
6219 org.apache.catalina.startup.Bootstrap
[root@node01 bin]#
[root@node01 bin]# jinfo ‐flags 6219

#查看某一参数的值,用法:jinfo ‐flag <参数名> <进程id>
[root@node01 bin]# jinfo ‐flag MaxHeapSize 6219
‐XX:MaxHeapSize=488636416

jvm的内存模型

jdk1.7的堆内存模型

在这里插入图片描述

jdk1.8的堆内存模型

在这里插入图片描述

为什么要废弃1.7中的永久区?

官网给出了解释:http://openjdk.java.net/jeps/122

This is part of the JRockit and Hotspot convergence effort. JRockit 
customers do not need to configure the permanent generation (since JRockit
does not have a permanent generation) and are accustomed to not
configuring the permanent generation.
移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,
不需要配置永久代。

有三大JVM称霸天下,分别是SUN 的HotSpot VM,BEA的JRocket和IBM的J9,ORACLE收购了SUN和BEA,三大JVM得其二,而JRocket没有永久代,为了整合2个JVM,jdk8中去掉了永久代。

查看堆内存使用情况jstat

查看class加载统计:jstat ‐class 6219
查看编译统计:jstat ‐compiler 6219
垃圾回收统计:jstat ‐gc 6219

内存溢出分析jmap

查看内存使用情况:jmap ‐heap 6219
查看内存中对象数量及大小:jmap ‐histo:live 6219 | more
将内存使用情况dump到文件:jmap ‐dump:live,format=b,file=/tmp/dump.dat 6219
通过jhat对dump文件进行分析:jhat ‐port 9999 dump.dat
打开浏览器进行访问:http://ip:9999,在最后面有 OQL查询功能

实战:内存溢出的定位与分析

内存溢出在实际的生产环境中经常会遇到,比如,不断的将数据写入到一个集合中,出现了死循环,读取超大的文件等等,都可能会造成内存溢出。
模拟内存溢出
编写代码,向List集合中添加100万个字符串,每个字符串由1000个UUID组成。如果程序能够正常执行,最后打印ok。

public class TestJvmOutOfMemory {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            String str = "";
            for (int j = 0; j < 1000; j++) {
                str += UUID.randomUUID().toString();
            }
            list.add(str);
        }
        System.out.println("ok");
    }
}

我们将设置执行的参数:‐Xms8m ‐Xmx8m ‐XX:+HeapDumpOnOutOfMemoryError

可以看到,当发生内存溢出时,会dump文件到java_pid5348.hprof
使用基于eclipse的分析工具MAT来辅助分析:
在这里插入图片描述
在这里插入图片描述
根据MAT的分析报告,得知main线程中有一个数组对象占用了超过90%内存,很可能就是引起OOM的原因。ArrayList底层是Object数组实现的,没毛病。

查看下jvm中的线程执行情况jstack

比如,发现服务器的CPU的负载突然增高了、出现了死锁、死循环等,我们该如何分析?
将正在运行的jvm的线程情况进行快照并且打印出来:jstack 2203

线程的状态

在 Java中线程的状态一共被分成6种:
在这里插入图片描述

实战:死锁问题

如果在生产环境发生了死锁,我们将看到的是部署的程序没有任何反应了,这个时候我们可以借助jstack进行分析,下面我们实战下查找死锁的原因

  public class TestDeadLock {
    private static Object obj1 = new Object();
    private static Object obj2 = new Object();
    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        new Thread(new Thread2()).start();
    }
    private static class Thread1 implements Runnable{
        @Override
        public void run() {
            synchronized (obj1){
                System.out.println("Thread1 拿到了 obj1 的锁!");
                try {
                    // 停顿2秒的意义在于,让Thread2线程拿到obj2的锁
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj2){
                    System.out.println("Thread1 拿到了 obj2 的锁!");
                }
            }
        }
    }
    private static class Thread2 implements Runnable{
        @Override
        public void run() {
            synchronized (obj2){
                System.out.println("Thread2 拿到了 obj2 的锁!");
                try {
                    // 停顿2秒的意义在于,让Thread1线程拿到obj1的锁
                     Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj1){
                    System.out.println("Thread2 拿到了 obj1 的锁!");
                }
            }
        }
    }
}

使用jstack进行分析:jstack 3256
在输出的信息中,已经看到,发现了 1个死锁,关键看这个:

Java stack information for the threads listed above:
===================================================
"Thread‐1":
at TestDeadLock$Thread2.run(TestDeadLock.java:47)    
‐ waiting to lock <0x00000000f655dc40> (a java.lang.Object)    
‐ locked <0x00000000f655dc50> (a java.lang.Object)    
at java.lang.Thread.run(Thread.java:748)    
"Thread‐0":
at TestDeadLock$Thread1.run(TestDeadLock.java:27)    
‐ waiting to lock <0x00000000f655dc50> (a java.lang.Object)    
‐ locked <0x00000000f655dc40> (a java.lang.Object)    
at java.lang.Thread.run(Thread.java:748)    
Found 1 deadlock.

可以清晰的看到:
Thread2 获取了 <0x00000000f655dc50> 的锁,等待获取 <0x00000000f655dc40>这个锁
Thread1 获取了 <0x00000000f655dc40> 的锁,等待获取 <0x00000000f655dc50>这个锁
由此可见,发生了死锁

VisualVM工具的使用

VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的
对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的),在jdk的安装目录的bin目录下,找到jvisualvm.exe,双击打开即可。
在这里插入图片描述
VisualVM使用简单,几乎0配置,功能还是比较丰富的,几乎囊括了其它JDK自带命令的所有功能。
在这里插入图片描述

监控远程的jvm

VisualJVM不仅是可以监控本地jvm进程,还可以监控远程的jvm进程,需要借助于JMX技术实现
什么是JMX?
JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
监控远程的tomcat
想要监控远程的tomcat,就需要在远程的tomcat进行对JMX配置,方法如下:

#在tomcat的bin目录下,修改catalina.sh,添加如下的参数
JAVA_OPTS="‐Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.port=9999 
-Dcom.sun.management.jmxremote.authenticate=false 
-Dcom.sun.management.jmxremote.ssl=false"
#这几个参数的意思是:
#‐Dcom.sun.management.jmxremote :允许使用JMX远程管理
#‐Dcom.sun.management.jmxremote.port=9999 :JMX远程连接端口
#‐Dcom.sun.management.jmxremote.authenticate=false :不进行身份认证,任何用户都可以连接
#‐Dcom.sun.management.jmxremote.ssl=false :不使用ssl

保存退出,重启tomcat
使用VisualJVM连接远程tomcat,添加远程主机:
在这里插入图片描述

在一个主机下可能会有很多的 jvm需要监控,所以接下来要在该主机上添加需要监控的jvm:端口9999就是前面添加的jmx端口
在这里插入图片描述
连接成功。使用方法和前面就一样了,就可以和监控本地 jvm进程一样,监控远程的tomcat进程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值