一、引言
生产上告警,交易堵塞,服务无响应,使用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命令