文章目录
我们为什么要对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进程。