一、引言
生产上告警,交易堵塞,服务无响应,使用jstack、jmap、jhat命令进行故障分析。
Java虚拟机(Java Virtual Machine,简称JVM)作为Java语言的核心组件,为Java程序提供了运行环境和内存管理机制。本文将系统地介绍JVM的基本架构、工作原理以及相关实战案例,旨在帮助读者对JVM有更全面且深入的理解。
二、JVM概述
JVM是Java平台的一部分,负责将Java字节码转换为机器指令并在不同的操作系统上执行。它屏蔽了底层硬件和操作系统的差异,使得“一次编写,到处运行”的理念得以实现。
三、JVM架构
四、垃圾回收
使用jstat 查看内存变化情况以及垃圾回收次数、时间,可以看到Eden的内存满了之后,就会做一次YGC,老年代的内存满了之后,会做FGC。
jstat -gcutil 1992 1000 1000
代码如下:
import java.util.HashMap;
import java.util.Map;
/**
* @Author: thinkpad
* @Date: 2024-02-04 22:19
*/
public class GCDemo {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(10000);
Map gc1Map = new HashMap();
Map gc2Map = new HashMap();
for(int i = 0; i < 10000; i++){
// 每个byte占1字节,所以为了得到1M需要大约1024 * 1024个字节
int oneMegabyte = 1024 * 1024;
// 创建一个1MB的byte数组
byte[] largeObject = new byte[oneMegabyte];
gc1Map.put(i, largeObject);
gc2Map.put(i, largeObject);
System.out.println("i = " + i);
Thread.sleep(200);
}
}
}
五、实战案例
本次生产上的故障是由于使用POI进行Excel操作所引起的,现在,我们来分析POI导致的原因。
生产上的故障,使用XSSFWorkbook操作十几万条记录,代码大致如下:
import org.apache.commons.compress.utils.Lists;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.util.List;
/**
* @Author: thinkpad
* @Date: 2024-02-24 21:53
*/
public class XssfWorkbookDemo {
public static void main(String[] args) {
try {
File testcaseFile = new File("E:/testcase.xlsx");
XSSFWorkbook xssfWorkbook = new XSSFWorkbook(testcaseFile);
XSSFSheet sheet = xssfWorkbook.getSheetAt(0);
List<TestCase> testCaseList = Lists.newArrayList();
int lastRowNum = sheet.getLastRowNum();
System.out.println("lastRowNum: " + lastRowNum);
for(int rowIndex = 1; rowIndex <= lastRowNum; rowIndex++){
XSSFRow row = sheet.getRow(rowIndex);
String[] cellArray = new String[row.getLastCellNum() - row.getFirstCellNum()];
for(int cellNum = row.getFirstCellNum(); cellNum < row.getLastCellNum(); cellNum++){
cellArray[cellNum] = row.getCell(cellNum).toString();
}
TestCase testCase = new TestCase();
testCase.setCaseId(cellArray[0]);
testCase.setUrl(cellArray[1]);
testCase.setLevel(cellArray[2]);
testCase.setDescribe(cellArray[3]);
testCaseList.add(testCase);
}
testCaseList.stream().forEach(testcase->{
System.out.println(testcase.toString());
});
}catch (Exception e){
e.printStackTrace();
}
}
}
导致内存溢出,通过jmap和jhat分析,使用jmap导出内存,命令如下
jmap -dump:live,format=b,file=heap-dump-pid.bin pid
通过jhat分析,关注 show heap histogram(内存实例的分布)
通过show heap histogram分析,发现内存占用多的如下分布, org.apache.xmlbeans.impl.store.Xobj占用了大量的内存,这是因为使用POI读取excel时,会产生大量的xml解析,因此如果有很多记录时,就会导致内存溢出。
由于使用XSSFWorkbook读取大量excel记录时,会内存溢出,因此尝试SXSSFWorkbook读取excel,代码大致如下
package com.fd.demo;
import org.apache.commons.compress.utils.Lists;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileInputStream;
import java.util.List;
/**
* @Author: thinkpad
* @Date: 2024-02-24 21:53
*/
public class SXssfWorkbookDemo {
public static void main(String[] args) {
try {
File testcaseFile = new File("E:/testcase.xlsx");
FileInputStream fis = new FileInputStream(testcaseFile);
XSSFWorkbook xssfWorkbook = new XSSFWorkbook(fis);
SXSSFWorkbook workbook = new SXSSFWorkbook(xssfWorkbook, 1000);
Sheet sheet = workbook.getXSSFWorkbook().getSheetAt(0);
List<TestCase> testCaseList = Lists.newArrayList();
int lastRowNum = sheet.getLastRowNum();
System.out.println("lastRowNum: " + lastRowNum);
for(int rowIndex = 1; rowIndex <= lastRowNum; rowIndex++){
Row row = sheet.getRow(rowIndex);
String[] cellArray = new String[row.getLastCellNum() - row.getFirstCellNum()];
for(int cellNum = row.getFirstCellNum(); cellNum < row.getLastCellNum(); cellNum++){
cellArray[cellNum] = row.getCell(cellNum).toString();
}
TestCase testCase = new TestCase();
testCase.setCaseId(cellArray[0]);
testCase.setUrl(cellArray[1]);
testCase.setLevel(cellArray[2]);
testCase.setDescribe(cellArray[3]);
testCase.setDescribe1(cellArray[4]);
testCase.setDescribe2(cellArray[5]);
testCase.setDescribe3(cellArray[6]);
testCase.setDescribe4(cellArray[7]);
testCase.setDescribe5(cellArray[8]);
testCase.setDescribe6(cellArray[9]);
testCase.setDescribe7(cellArray[10]);
testCase.setDescribe8(cellArray[11]);
testCase.setDescribe9(cellArray[12]);
testCase.setDescribe10(cellArray[13]);
testCase.setDescribe11(cellArray[14]);
testCase.setDescribe12(cellArray[15]);
testCase.setDescribe13(cellArray[16]);
testCase.setDescribe14(cellArray[17]);
testCase.setDescribe15(cellArray[18]);
testCase.setDescribe16(cellArray[19]);
testCase.setDescribe17(cellArray[20]);
testCase.setDescribe18(cellArray[21]);
testCaseList.add(testCase);
}
for(int i = 0; i < testCaseList.size(); i++){
System.out.println(testCaseList.get(i).toString());
}
Thread.sleep(1000000);
}catch (Exception e){
e.printStackTrace();
}
}
}
导致内存溢出,通过jmap和jhat分析,使用jmap导出内存,命令如下
jmap -dump:live,format=b,file=heap-dump-pid.bin pid
通过jhat分析,关注 show heap histogram(内存实例的分布)
六、总结
内存溢出的分析需要用到jmap,jhat命令