1. 程式人生 > >[JVM]生產環境下jvm調優概述

[JVM]生產環境下jvm調優概述

JVM相關的典型面試問題:

Java生產環境下效能監控與調優詳解

  1. 生產環境發生了記憶體溢位如何處理?

  2. 生產環境應該給伺服器分配多少記憶體合適?

  3. 如何對垃圾收集器的效能進行調優?

4.生產環境CPU負載飆高該如何處理?

5.生產環境應該給應用分配多少執行緒合適?

6.不加log如何確定請求是否執行了某一行程式碼?

7.不加log如何實時檢視某個方法的入參與返回值?

8.JVM的位元組碼是什麼東西?

9.字串效能問題

10Spring執行緒池

11熟悉使用各種監控和除錯工具

12從容應對生產環境中遇到的各種除錯和效能問題

13.熟悉JVM的位元組碼指令

14深入理解JVM的自動記憶體回收機制,學會GC調優

本章關鍵詞:JVM引數、jps、jstat、記憶體溢位、MAT、jstack

一、JVM的基本引數

1. 標準引數

這類引數相對比較穩定

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

例如:

  • java -version
  • java -help

2. X引數

非標準化引數(可能在JVM的各個版本中會有變化,但是變化的比較小)

  1. -Xint:解釋執行
  2. -Xcomp:第一次使用就編譯成原生代碼
  3. -Xmixed:混合模式,JVM自己來決定是否編譯成原生代碼

3. XX引數

非標準化引數,相對不穩定,主要用於JVM調優和Debug

  • Boolean型別

格式:-XX:[+-]<name> 表示啟用或者禁用name屬性

  • 比如: -XX:+UseConcMarkSweepGC -XX:+UseG1GC
  • 非Boolean型別

格式:-XX:<name>=<value> 表示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(列印命令列引數)

     

    引數.png

  • java -XX:+PrintFlagsFinal -version > flags.txt 將內容列印到flags.txt檔案中

     

    檔案截圖.png

2. jps(專門用來檢視java程序的id,類似Linux上的ps)

jsp例子.png

jdk8工具集: 相關命令都有完整的文件,不明白的可以檢視此文件

3.jinfo(檢視一個正在執行的JVM裡的引數值)

jinfo示例.png

jinfo示例2.png

三、jstat檢視JVM統計資訊

1.可以檢視資訊

  • 類載入
  • 垃圾收集
  • JIT編譯

2.命令格式

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

options:-class,-compiler,-gc,-printcompilation等,詳情參考相關文件

jstat示例.png

注: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記憶體結構

JVM記憶體結構.png

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<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引數

idea設定專案的jvm引數方法:run->Edit Configurations,詳情見下圖

idea設定jvm引數.png

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引數

-XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M

  • 啟動專案,訪問nonheap,過一段時間會出現報錯

     

    Metaspace記憶體溢位報出.png

     

    Metaspace記憶體溢位錯誤.png

注意:我這邊用idea,將-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M都設定了64M,如果設定32M的話,專案啟動就報錯

 

啟動報錯.png

五、匯出記憶體映像檔案

1.JAVA記憶體洩漏和C++ 記憶體洩漏區別

C++中記憶體洩漏: new了一個物件之後,把這個物件的指標丟失,這塊記憶體就永遠達不到釋放了 JAVA中記憶體洩漏:new了一個物件之後,一直不釋放,佔著記憶體

2.如何匯出記憶體映像檔案

  • 記憶體溢位自動匯出

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./

設定了這兩個引數後,出現記憶體溢位後,會自動下載檔案到當前目錄,詳情如下:

下載檔案.png

  • 使用jmap命令手動匯出

jmap -dump:format=b,file=heap.hprof 10148

jmap匯出.png

  • 取捨 兩種方式都可以用,但是記憶體比較大的時候,自動匯出可能會導不出來,所以jmap比較常用

六、MAT分析記憶體溢位

1.下載MAT

官網下載對應版本MAT

2.開啟.hprof檔案

MAT分析.png

  • Leak Suspects指懷疑有記憶體洩漏

     

    分析報告.png

  • 檢視物件數量

     

    檢視物件數量.png

Retained Heap 所佔記憶體大小 Shallow Heap不包含內部物件,所佔記憶體大小

檢視是誰引用了該物件,排除虛引用,只看強引用

是誰引用該物件.png

  • 檢視物件所佔位元組數

     

    檢視所佔位元組數.png

常用的兩個分析方式就是以上兩種,線上環境肯定更為複雜,需要更為仔細的分析與考證

七、jstack實戰死迴圈與死鎖

前面介紹了jmap來匯出記憶體映像,MAT分析記憶體溢位原因,jstack則用來列印JVM內部所有的執行緒

1.命令格式

命令格式.png

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<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";
    }

注意:使用nohup java -jar XXX.jar啟動,表示把日誌輸出到nohup.out檔案中 實時檢視日誌檔案:tail -f nohup.out

  • 定位問題 檢視程序id jstack pid >pid.txt 檔案最後會幫我們定位到一個死鎖的問題(Found 1 deadlock)

--------------------- 本文來自 majiawenzzz 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/majiawenzzz/article/details/81175379?utm_source=copy