JVM相关的典型面试问题:

Java生产环境下性能监控与调优详解

  1. 生产环境发生了内存溢出如何处理?

  2. 生产环境应该给服务器分配多少内存合适?

  3. 如何对垃圾收集器的性能进行调优?

4.生产环境CPU负载飙高该如何处理?

5.生产环境应该给应用分配多少线程合适?

6.不加log如何确定请求是否执行了某一行代码?

7.不加log如何实时查看某个方法的入参与返回值?

8.JVM的字节码是什么东西?

9.字符串性能问题

10Spring线程池

11熟悉使用各种监控和调试工具

12从容应对生产环境中遇到的各种调试和性能问题

13.熟悉JVM的字节码指令

14深入理解JVM的自动内存回收机制,学会GC调优

 

 

一、JVM的基本参数

1. 标准参数

这类参数相对比较稳定

  1. -help
  2. -server -client
  3. -version -showversion
  4. -cp -classpath

2. X参数

非标准化参数(可能在JVM的各个版本中会有变化,但是变化的比较小)

  1. -Xint:解释执行
  2. -Xcomp:第一次使用就编译成本地代码
  3. -Xmixed:混合模式,JVM自己来决定是否编译成本地代码

3. XX参数

非标准化参数,相对不稳定,主要用于JVM调优和Debug

  • Boolean类型
  • 非Boolean类型
  • -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(打印命令行参数)

     

    [JVM]生产环境下jvm调优概述-LMLPHP

    参数.png

  • java -XX:+PrintFlagsFinal -version > flags.txt 将内容打印到flags.txt文件中

     

    [JVM]生产环境下jvm调优概述-LMLPHP

    文件截图.png

2. jps(专门用来查看java进程的id,类似Linux上的ps)

[JVM]生产环境下jvm调优概述-LMLPHP

jsp例子.png

3.jinfo(查看一个正在运行的JVM里的参数值)

[JVM]生产环境下jvm调优概述-LMLPHP

jinfo示例.png

 

[JVM]生产环境下jvm调优概述-LMLPHP

jinfo示例2.png

三、jstat查看JVM统计信息

1.可以查看信息

  • 类加载
  • 垃圾收集
  • JIT编译

2.命令格式

  • jstat -help|-options
  • jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

[JVM]生产环境下jvm调优概述-LMLPHP

jstat示例.png

  • -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内存结构

[JVM]生产环境下jvm调优概述-LMLPHP

JVM内存结构.png

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<User> 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参数

[JVM]生产环境下jvm调优概述-LMLPHP

idea设置jvm参数.png

[JVM]生产环境下jvm调优概述-LMLPHP

idea设置jvm参数2.png

2.代码演示Metaspcace内存溢出

  • pom文件引入jar
      <dependency>
          <groupId>asm</groupId>
            <artifactId>asm</artifactId>
            <version>3.3.1</version>
        </dependency>
  • 编写动态生成类代码
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<Class<?>> createClasses(){
        //类持有
        List<Class<?>> 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);
            //定义构造函数<init>方法
            MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC,"<init>","()V",null,null);
            //第一个指令为加载this
            mw.visitVarInsn(Opcodes.ALOAD,0);
            //第二个指令为调用父类Object的构造函数
            mw.visitMethodInsn(Opcodes.INVOKESPECIAL,"java/lang/Object","<init>","()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参数
  • 启动项目,访问nonheap,过一段时间会出现报错

     

    [JVM]生产环境下jvm调优概述-LMLPHP

    Metaspace内存溢出报出.png

     

    [JVM]生产环境下jvm调优概述-LMLPHP

    Metaspace内存溢出错误.png

五、导出内存映像文件

1.JAVA内存泄漏和C++ 内存泄漏区别

2.如何导出内存映像文件

  • 内存溢出自动导出

设置了这两个参数后,出现内存溢出后,会自动下载文件到当前目录,详情如下:

 

[JVM]生产环境下jvm调优概述-LMLPHP

下载文件.png

  • 使用jmap命令手动导出

[JVM]生产环境下jvm调优概述-LMLPHP

jmap导出.png

  • 取舍
    两种方式都可以用,但是内存比较大的时候,自动导出可能会导不出来,所以jmap比较常用

六、MAT分析内存溢出

1.下载MAT

2.打开.hprof文件

[JVM]生产环境下jvm调优概述-LMLPHP

MAT分析.png

  • Leak Suspects指怀疑有内存泄漏

     

    [JVM]生产环境下jvm调优概述-LMLPHP

    分析报告.png

  • 查看对象数量

     

    [JVM]生产环境下jvm调优概述-LMLPHP

    查看对象数量.png

查看是谁引用了该对象,排除虚引用,只看强引用

 

[JVM]生产环境下jvm调优概述-LMLPHP

是谁引用该对象.png

  • 查看对象所占字节数

     

    [JVM]生产环境下jvm调优概述-LMLPHP

    查看所占字节数.png

七、jstack实战死循环与死锁

1.命令格式

[JVM]生产环境下jvm调优概述-LMLPHP

命令格式.png

2.示例

  • 输出了10148.txt文件到桌面

  • java线程状态

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<Long> loop(){
        String data = "{\"data\":[{\"partnerid\":]";
        return getPartneridsFromJson(data);

    }
    public static List<Long> getPartneridsFromJson(String data){
        //{\"data\":[{\"partnerid\":982,\"count\":\"10000\",\"cityid\":\"11\"},{\"partnerid\":983,\"count\":\"10000\",\"cityid\":\"11\"},{\"partnerid\":984,\"count\":\"10000\",\"cityid\":\"11\"}]}
        //上面是正常的数据
        List<Long> 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";
    }
  • 定位问题
    查看进程id
    jstack pid >pid.txt
    文件最后会帮我们定位到一个死锁的问题(Found 1 deadlock)

--------------------- 本文来自 majiawenzzz 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/majiawenzzz/article/details/81175379?utm_source=copy

 

 

10-04 12:21