本文主要参考慕课网若鱼老师课程Java生产环境下性能监控与调优详解
请支持正版,抵制盗版,维护每一位辛苦付出的人的合法权益!
本章关键词:JVM参数、jps、jstat、内存溢出、MAT、jstack
一、JVM的基本参数
1. 标准参数
这类参数相对比较稳定
- -help
- -server -client
- -version -showversion
- -cp -classpath
例如:
- java -version
- java -help
2. X参数
非标准化参数(可能在JVM的各个版本中会有变化,但是变化的比较小)
- -Xint:解释执行
- -Xcomp:第一次使用就编译成本地代码
- -Xmixed:混合模式,JVM自己来决定是否编译成本地代码
3. XX参数
非标准化参数,相对不稳定,主要用于JVM调优和Debug
- Boolean类型
格式:-XX:[+-]
表示启用或者禁用name属性
- 比如:
-XX:+UseConcMarkSweepGC
-XX:+UseG1GC
- 非Boolean类型
格式:-XX:
= 表示name属性的值是value
- 比如:-XX:MaxGCPauseMillis=500
XX:GCTimeRatio=19
- -Xmx -Xms -Xss属于XX参数
- -Xms等价于-XX:InitialHeapSize(初始化堆大小)
- -Xmx等价于-XX:MaxHeapSize(最大的堆大小)
- -Xss等价于-XX:ThreadStackSize(堆栈内存大小)
二、查看JVM运行时参数
1. 相关参数
- -XX:+PrintFlagsInitial(初始值,有可能被修改)
- -XX:+PrintFlagsFinal(最终值)
- -XX:+UnlockExperimentalVMOptions(解锁实验参数)
- -XX:+UnlockDiagnosticVMOptions(解锁诊断参数)
-
-XX:+PrintCommandLineFlags(打印命令行参数)
-
java -XX:+PrintFlagsFinal -version > flags.txt 将内容打印到flags.txt文件中
2. jps(专门用来查看java进程的id,类似Linux上的ps)
jdk8工具集: 相关命令都有完整的文档,不明白的可以查看此文档
3.jinfo(查看一个正在运行的JVM里的参数值)
三、jstat查看JVM统计信息
1.可以查看信息
- 类加载
- 垃圾收集
- JIT编译
2.命令格式
- jstat -help|-options
- jstat -
options:-class,-compiler,-gc,-printcompilation等,详情参考相关文档
注:1000代表每隔1000毫秒(1秒) 10代表输出十次
查看垃圾回收信息:-gc,-gcutil,-gccause,-gcnew,-gcold
- -gc输出结果
s0c、s1c、s0u、s1u:s0与s1的总量和使用量;
EC,EU:eden区总量和使用量;
OC,OU:old区总量和使用量;
MC,MU:Metaspace区总量和使用量;
CCSC,CCSU:压缩类空间总量和使用量;
YGC,YGCT:YoungGC的次数与时间;
FGC,FGCT:FullGC的次数与时间;
GCT:总的GC时间。
3.JVM内存结构
JDK8中非堆区叫Metaspace,1.7之前是没有的,1.7之前有一个区域叫PermGen space(Permanent Generation space,是指内存的永久保存区域),JDK8中完全移除了PermGen space
s0又叫From Survivor
s1又叫To Survivor
4.JIT编译(了解)
- -compiler
- -printcompilation
四、演示内存溢出
1.代码演示堆内存溢出
- 新建一个SpringBoot项目
- 新建一个用户实体类
public class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 建一个Controller做测试
/**
* @author Qinxianyun
* @version V1.0
* @time 2018/7/15.14:51
* @description 堆内存溢出
*/
@RestController
public class MemoryController {
private List userList = new ArrayList<>();
/**
* -Xmx32M -Xms32M
* @return
*/
@GetMapping("heap")
public String heap(){
int i= 0;
while (true){
userList.add(new User(i++,UUID.randomUUID().toString()));
}
}
}
- 修改jvm参数
idea设置项目的jvm参数方法:run->Edit Configurations,详情见下图
- 访问地址http://localhost:8080/heap
-
很快控制台会报出如下错误
2.代码演示Metaspcace内存溢出
- pom文件引入jar
asm
asm
3.3.1
- 编写动态生成类代码
package com.qinxianyun.monitor_tuning.chapter2;
import org.hibernate.validator.constraints.EAN;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.util.ArrayList;
import java.util.List;
/**
* @author Qinxianyun
* @version V1.0
* @time 2018/7/15.15:20
* @description 动态创建类
*/
public class MetaSpace extends ClassLoader{
public static List> createClasses(){
//类持有
List> classes = new ArrayList<>();
//循环1000w次生成1000w个不同的类
for (int i = 0;i < 10000000; ++i){
ClassWriter cw = new ClassWriter(0);
//定义一个类名称为Class{i} 它的访问域为public 父类为java.lang.Object 不实现任何接口
cw.visit(Opcodes.V1_1,Opcodes.ACC_PUBLIC,"Class" + i,null,"java/lang/Object",null);
//定义构造函数方法
MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC,"","()V",null,null);
//第一个指令为加载this
mw.visitVarInsn(Opcodes.ALOAD,0);
//第二个指令为调用父类Object的构造函数
mw.visitMethodInsn(Opcodes.INVOKESPECIAL,"java/lang/Object","","()V");
//第三条指令为return
mw.visitInsn(Opcodes.RETURN);
mw.visitMaxs(1,1);
mw.visitEnd();
MetaSpace test = new MetaSpace();
byte[] code = cw.toByteArray();
//定义类
Class> exampleClass = test.defineClass("Class" + i,code,0,code.length);
classes.add(exampleClass);
}
return classes;
}
}
- controller测试代码
/**
* MetaSpace内存溢出
* -XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M
* @return
*/
@GetMapping("nonheap")
public String nonheap(){
while (true){
classArrayList.addAll(MetaSpace.createClasses());
}
}
- 设置jvm参数
-XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M
-
启动项目,访问nonheap,过一段时间会出现报错
注意:我这边用idea,将-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M都设置了64M,如果设置32M的话,项目启动就报错
五、导出内存映像文件
1.JAVA内存泄漏和C++ 内存泄漏区别
C++中内存泄漏: new了一个对象之后,把这个对象的指针丢失,这块内存就永远达不到释放了
JAVA中内存泄漏:new了一个对象之后,一直不释放,占着内存
2.如何导出内存映像文件
- 内存溢出自动导出
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./
设置了这两个参数后,出现内存溢出后,会自动下载文件到当前目录,详情如下:
- 使用jmap命令手动导出
jmap -dump:format=b,file=heap.hprof 10148
- 取舍
两种方式都可以用,但是内存比较大的时候,自动导出可能会导不出来,所以jmap比较常用
六、MAT分析内存溢出
1.下载MAT
到官网下载对应版本MAT
2.打开.hprof文件
-
Leak Suspects指怀疑有内存泄漏
-
查看对象数量
Retained Heap 所占内存大小
Shallow Heap不包含内部对象,所占内存大小
查看是谁引用了该对象,排除虚引用,只看强引用
-
查看对象所占字节数
常用的两个分析方式就是以上两种,线上环境肯定更为复杂,需要更为仔细的分析与考证
七、jstack实战死循环与死锁
前面介绍了jmap来导出内存映像,MAT分析内存溢出原因,jstack则用来打印JVM内部所有的线程
1.命令格式
2.示例
C:\Users\Administrator\Desktop>jps -l
7200 org.jetbrains.jps.cmdline.Launcher
7888 org.jetbrains.idea.maven.server.RemoteMavenServer
10148 com.qinxianyun.monitor_tuning.MonitorTuningApplication
3636
2136 D:\mat\plugins/org.eclipse.equinox.launcher_1.5.0.v20180512-1130.jar
2984 sun.tools.jps.Jps
C:\Users\Administrator\Desktop>jstack 10148 > 10148.txt
输出了10148.txt文件到桌面
java线程状态
NEW/RUNNABLE/BLOCKED/WAITING/TIMED_WAITING/TERMINATED
状态解释查看官方文档
- 线程状态转换
可以参考文章怎样了解你的线程在干嘛?
3.实战死循环导致CPU飚高
package com.qinxianyun.monitor_tuning.chapter2;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* @author Qinxianyun
* @version V1.0
* @time 2018/7/15.17:19
* @description 死循环
*/
@RestController
public class CpuController {
@RequestMapping("/loop")
public List loop(){
String data = "{\"data\":[{\"partnerid\":]";
return getPartneridsFromJson(data);
}
public static List getPartneridsFromJson(String data){
//{\"data\":[{\"partnerid\":982,\"count\":\"10000\",\"cityid\":\"11\"},{\"partnerid\":983,\"count\":\"10000\",\"cityid\":\"11\"},{\"partnerid\":984,\"count\":\"10000\",\"cityid\":\"11\"}]}
//上面是正常的数据
List list = new ArrayList<>(2);
if(data == null || data.length() <= 0){
return list;
}
int datapos = data.indexOf("data");
if(datapos < 0){
return list;
}
int leftBracket = data.indexOf("[",datapos);
int rightBracket= data.indexOf("]",datapos);
if(leftBracket < 0 || rightBracket < 0){
return list;
}
String partners = data.substring(leftBracket+1,rightBracket);
if(partners == null || partners.length() <= 0){
return list;
}
while(partners!=null && partners.length() > 0){
int idpos = partners.indexOf("partnerid");
if(idpos < 0){
break;
}
int colonpos = partners.indexOf(":",idpos);
int commapos = partners.indexOf(",",idpos);
if(colonpos < 0 || commapos < 0){
//partners = partners.substring(idpos+"partnerid".length());//1
continue;
}
String pid = partners.substring(colonpos+1,commapos);
if(pid == null || pid.length() <= 0){
//partners = partners.substring(idpos+"partnerid".length());//2
continue;
}
try{
list.add(Long.parseLong(pid));
}catch(Exception e){
//do nothing
}
partners = partners.substring(commapos);
}
return list;
}
}
- 定位问题
首先top命令查看cpu使用率,找到使用最高的pid
使用jstack pid > pid.txt
sz pid.txt下载文件
top -p pid -h 打印所有线程,查看占用cpu最高的几个线程
4.死锁导致CPU飚高
private Object lock1 = new Object();
private Object lock2 = new Object();
/**
* 死锁
* @return
*/
@RequestMapping("/deadlock")
public String deadlock(){
new Thread(()->{
synchronized (lock1){
try {
Thread.sleep(1000);
}catch (Exception e){
}
synchronized (lock2){
System.out.println("Thread1 over");
}
}
}).start();
new Thread(()->{
synchronized (lock2){
try {
Thread.sleep(1000);
}catch (Exception e){
}
synchronized (lock1){
System.out.println("Thread1 over");
}
}
}).start();
return "deadlock";
}
注意:使用nohup java -jar XXX.jar启动,表示把日志输出到nohup.out文件中
实时查看日志文件:tail -f nohup.out
- 定位问题
查看进程id
jstack pid >pid.txt
文件最后会帮我们定位到一个死锁的问题(Found 1 deadlock)
jstack详细使用参考官方文档