[JVM]生產環境下jvm調優概述
JVM相關的典型面試問題:
Java生產環境下效能監控與調優詳解
-
生產環境發生了記憶體溢位如何處理?
-
生產環境應該給伺服器分配多少記憶體合適?
-
如何對垃圾收集器的效能進行調優?
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. 標準引數
這類引數相對比較穩定
- -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> 表示啟用或者禁用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 狀態解釋檢視官方文件
- 執行緒狀態轉換
可以參考文章怎樣瞭解你的執行緒在幹嘛?
執行緒狀態轉換.png
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