1、首先准备一份代码
package com.icode;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.OutputStream;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
/**
* 模拟JVM内存溢出
* 原理: 开放了一个 HTTP 接口,当请求之后,将每秒钟生成 1MB 的数据。由于它和 GC Roots 的强关联性,每次都不能被回收。
*/
public class OOMTest {
public static final int _1MB = 1024 * 1024;
static List<byte[]> byteList = new ArrayList<>();// 放在堆里
private static void oom(HttpExchange exchange) {
try {
String response = "oom begin!";
exchange.sendResponseHeaders(200, response.getBytes().length);
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
} catch (Exception ex) {
}
for (int i = 0; ; i++) {
byte[] bytes = new byte[_1MB];
byteList.add(bytes);
System.out.println(i + "MB");
memPrint();
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
static void memPrint() {
for (MemoryPoolMXBean memoryPoolMXBean : ManagementFactory.getMemoryPoolMXBeans()) {
System.out.println(memoryPoolMXBean.getName() +
" committed:" + memoryPoolMXBean.getUsage().getCommitted() +
" used:" + memoryPoolMXBean.getUsage().getUsed());
}
}
private static void srv() throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress(8888), 0);
HttpContext context = server.createContext("/");
context.setHandler(OOMTest::oom);
server.start();
}
public static void main(String[] args) throws Exception{
srv();
}
}
以上代码注释中什么是GC Roots?
GC Roots一般都是些堆外指向堆内的引用,例如:
- JVM栈中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中引用的对象
2、编译,使用JAVA命令运行字节码文件
F:\cmder_mini
λ E:
E:\
λ cd E:\procedure\java\JVMDemo
E:\procedure\java\JVMDemo
λ java -classpath ".\target\classes" -Xmx20m -Xmn4m -XX:+UseConcMarkSweepGC -verbose:gc -XX:+PrintGCDetails -Xloggc:".\gclog.log" com.icode.OOMTest
参数解释:
-Xmx 最大堆大小
-Xmn 年轻代大小
-XX:+UseConcMarkSweepGC 使用CMS内存收集
-verbose:gc //在控制台输出GC情况
-XX:+PrintGCDetails //在控制台输出详细的GC情况
-Xloggc: filepath //将GC日志输出到指定文件中
先把-Xloggc去掉我们直接打印到控制台
输入如下信息:
[GC (Allocation Failure) [ParNew: 3328K->384K(3712K), 0.0114718 secs] 3328K->1132K(20096K), 0.0158854 secs] [Times: user=0.13 sys=0.00, real=0.02 secs]
日志解析:
GC: 表明进行了一次垃圾回收,前面没有Full修饰,表明这是一次Minor GC
Allocation Failure: 表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了。
ParNew:表明本次GC发生在年轻代并且使用的是ParNew垃圾收集器。ParNew是一个Serial收集器的多线程版本,会使用多个CPU和线程完成垃圾收集工作(默认使用的线程数和CPU数相同,可以使用-XX:ParallelGCThreads参数限制)。该收集器采用复制算法回收内存,期间会停止其他工作线程,即Stop The World。
3328K->384K(3712K):单位是KB 三个参数分别为:GC前该内存区块使用容量,GC后该内存区块使用容量,该内存区域总容量。
0.0114718 secs: 该内存区块GC耗时,单位是秒
3328K->1132K(20096K): 三个参数分别为:堆区垃圾回收前的大小,堆区垃圾回收后的大小,堆区总大小。
.0158854 secs: 该内存区域GC耗时,单位是秒
[Times: user=0.13 sys=0.00, real=0.02 secs]: 分别表示用户态耗时,内核态耗时和总耗时,一般用户只关心总耗时
由上可知:老年代占用内存为 1132K-384K=748K