Java web应用性能分析之【Linux服务器性能监控分析概叙】-CSDN博客
Java web应用性能分析之【sysbench基准测试】-CSDN博客
Java web应用性能分析之【CPU飙升分析概述】-CSDN博客
Java web应用性能分析之【CPU飙高分析之MySQL】-CSDN博客
常见的6中OOM 1. 堆溢出-java.lang.OutOfMemoryError: Java heap space。 检查大对象 2. 栈溢出-java.lang.OutOfMemorryError:unable to create new native thread 检查多线程 3. 栈溢出-java.lang.StackOverFlowError。 检查递归 4. 元信息溢出-java.lang.OutOfMemoryError: Metaspace。 5. 直接内存溢出-java.lang.OutOfMemoryError: Direct buffer memory。 排查nio,一般重点检查netty,它里面的nio用直接内存用的多 6. GC超限-java.lang.OutOfMemoryError: GC overhead limit exceeded。
验证效果
启动脚本
web@zhouxx:~$ JAVA_OPTS=" -server -Dfile.encoding=UTF-8 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8899 -Dcom.sun.management.jmxremote.authenticate=false -Dapp.config=/home/web/demo/application.yaml -Xms64m -Xmx64m -Xss1m -XX:MetaspaceSize=16m -XX:MaxMetaspaceSize=64m -XX:+UseG1GC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintCommandLineFlags -Xloggc:log/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=log/"
web@zhouxx:~$ cd demo/
web@zhouxx:~/demo$ java $JAVA_OPTS -jar mydemo-1.0.0-SNAPSHOT.jar
-XX:CompressedClassSpaceSize=58720256 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=log/ -XX:InitialHeapSize=67108864 -XX:+ManagementServer -XX:MaxHeapSize=67108864 -XX:MaxMetaspaceSize=67108864 -XX:MetaspaceSize=16777216 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:ThreadStackSize=1024 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.2.RELEASE)
2024-04-25 19:53:44.288[] mydemo [main] INFO com.zxx.study.web.AppMain-55- Starting AppMain on zhouxx with PID 1014 (/home/web/demo/mydemo-1.0.0-SNAPSHOT.jar started by web in /home/web/demo)
2024-04-25 19:53:44.292[] mydemo [main] INFO com.zxx.study.web.AppMain-651- No active profile set, falling back to default profiles: default
2024-04-25 19:53:45.675[] mydemo [main] INFO o.s.boot.web.embedded.tomcat.TomcatWebServer-92- Tomcat initialized with port(s): 6002 (http)
2024-04-25 19:53:45.687[] mydemo [main] INFO org.apache.coyote.http11.Http11NioProtocol-173- Initializing ProtocolHandler ["http-nio-0.0.0.0-6002"]
2024-04-25 19:53:45.689[] mydemo [main] INFO org.apache.catalina.core.StandardService-173- Starting service [Tomcat]
2024-04-25 19:53:45.689[] mydemo [main] INFO org.apache.catalina.core.StandardEngine-173- Starting Servlet engine: [Apache Tomcat/9.0.29]
2024-04-25 19:53:45.744[] mydemo [main] INFO o.a.c.core.ContainerBase.[Tomcat].[localhost].[/]-173- Initializing Spring embedded WebApplicationContext
2024-04-25 19:53:45.745[] mydemo [main] INFO org.springframework.web.context.ContextLoader-284- Root WebApplicationContext: initialization completed in 1391 ms
2024-04-25 19:53:45.962[] mydemo [main] INFO com.zxx.study.web.config.DependsOn.SpringContext-45- SpringContext我被创建
2024-04-25 19:53:45.964[] mydemo [main] INFO com.zxx.study.web.config.DependsOn.DependsService-14- DependsService我在springContext后被创建
2024-04-25 19:53:45.969[] mydemo [main] INFO com.zxx.study.web.config.DependsOn.CurrentServiceA-14- CurrentServiceA我在dependsService后被创建
2024-04-25 19:53:45.971[] mydemo [main] INFO com.zxx.study.web.config.DependsOn.CurrentServiceB-14- CurrentServiceB我在springContext后被创建
2024-04-25 19:53:45.971[] mydemo [main] INFO com.zxx.study.web.config.DependsOn.CurrentServiceB-17- com.zxx.study.web.config.DependsOn.CurrentServiceA
2024-04-25 19:53:45.971[] mydemo [main] INFO com.zxx.study.web.config.DependsOn.CurrentServiceB-21- CurrentServiceB 出错:No bean named 'CurrentServiceC' available
2024-04-25 19:53:45.972[] mydemo [main] INFO com.zxx.study.web.config.DependsOn.CurrentServiceC-13- CurrentServiceC我在后被创建
2024-04-25 19:53:45.974[] mydemo [main] INFO com.zxx.study.web.config.DependsOn.CurrentServiceC-17- com.zxx.study.web.config.DependsOn.CurrentServiceA
2024-04-25 19:53:46.284[] mydemo [main] INFO o.s.scheduling.concurrent.ThreadPoolTaskExecutor-171- Initializing ExecutorService 'applicationTaskExecutor'
2024-04-25 19:53:46.497[] mydemo [main] INFO o.s.b.actuate.endpoint.web.EndpointLinksResolver-58- Exposing 2 endpoint(s) beneath base path '/actuator'
2024-04-25 19:53:46.542[] mydemo [main] INFO org.apache.coyote.http11.Http11NioProtocol-173- Starting ProtocolHandler ["http-nio-0.0.0.0-6002"]
2024-04-25 19:53:46.624[] mydemo [main] INFO o.s.boot.web.embedded.tomcat.TomcatWebServer-204- Tomcat started on port(s): 6002 (http) with context path ''
2024-04-25 19:53:46.631[] mydemo [main] INFO com.zxx.study.web.AppMain-61- Started AppMain in 3.073 seconds (JVM running for 4.315)
6种OOM示例
package com.zxx.study.web.controller;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 常见的6中OOM
* 1. 堆溢出-java.lang.OutOfMemoryError: Java heap space。
* 2. 栈溢出-java.lang.OutOfMemorryError:unable to create new native thread
* 3. 栈溢出-java.lang.StackOverFlowError。
* 4. 元信息溢出-java.lang.OutOfMemoryError: Metaspace。
* 5. 直接内存溢出-java.lang.OutOfMemoryError: Direct buffer memory。
* 6. GC超限-java.lang.OutOfMemoryError: GC overhead limit exceeded。
*
* @author zhouxx
* @Description:
* @date 2024/4/25 11:49
*/
@RestController
@Slf4j
@Validated
@RequestMapping("/api/v1/oom")
public class OomController {
@Value("${upload.file-path}")
private String filePath;
@Value("${server.port}")
private String serverPort;
@GetMapping("/javaHeapSpace")
public String javaHeapSpace(@RequestParam(value ="name", required = false) String name) {
//log.info("hi==========");
List<OOMObject> list = new ArrayList<>();
while(true) {
// 方法不结束,对象不回收,模拟堆溢出
list.add(new OOMObject());
if(name!=null&&"zhouxx".equals(name)){
break;
}
}
return "("+Thread.currentThread().getName()+")serverPort: " + serverPort + " name:" + name;
}
@GetMapping("/stackOOM")
public String stackOOM(@RequestParam(value ="name", required = false) String name) {
//log.info("hi==========");
while(true) {
// -Xss 创建线程时申请的栈空间
// 方法不结束,对象不回收,死循环创建线程模拟栈溢出
Thread thread = new Thread(() -> {
while (true) {
try {
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
if(name!=null&&"zhouxx".equals(name)){
break;
}
}
return "("+Thread.currentThread().getName()+")serverPort: " + serverPort + " name:" + name;
}
@GetMapping("/stackOverFlow")
public String stackOverFlow(@RequestParam(value ="name", required = false) String name) {
//log.info("hi==========");
// 死循环递归,每次递归时将数据写入栈,模拟栈溢出
OOMObject.stackOverFlowErrorMethod(name);
return "("+Thread.currentThread().getName()+")serverPort: " + serverPort + " name:" + name;
}
@GetMapping("/metaspaceOOM")
public String metaspaceOOM (@RequestParam(value ="name", required = false) String name) {
//log.info("hi==========");
while(true) {
// 方法不结束,对象不回收,装在元数据,模拟元数据区溢出
// (jdk1.8)通过CBLIG大量生成类,导致Meta信息满了;(jdk1.8)
// (jdk1.7)JDK7的时候使用String.intern()不当,会产生大量常量数据;加载大量的jsp以及动态生成jsp文件。
// todo jdk 1.8之后才单独将永久代从堆中独立出来,去掉了永久代。 jdk1.7将字符串常量池从永久代移到堆;jdk1.8把方法区的运行时常量池等信息全部迁移到了本地内存(native memory)
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
if(name!=null&&"zhouxx".equals(name)){
break;
}
}
return "("+Thread.currentThread().getName()+")serverPort: " + serverPort + " name:" + name;
}
@GetMapping("/directBufferOOM")
public String directBufferOOM(@RequestParam(value ="name", required = false) String name) {
//log.info("hi==========");
final int _1M = 1024 * 1024 * 1;
List<ByteBuffer> buffers = new ArrayList<>();
while(true) {
// 方法不结束,对象不回收,模拟直接内存溢出
// 发生直接内存溢出时,重点排查程序里边是否使用的NIO及NIO,比如Netty,里边的直接内存的配置。
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1M);
buffers.add(byteBuffer);
if(name!=null&&"zhouxx".equals(name)){
break;
}
}
return "("+Thread.currentThread().getName()+")serverPort: " + serverPort + " name:" + name;
}
@GetMapping("/GCOverheadOOM")
public String GCOverheadOOM(@RequestParam(value ="name", required = false) String name) {
// 创建了一个线程池,如果线程池执行的时候如果核心线程处理不过来的时候会把数据放到LinkedBlockingQueue里边,
// 也就是堆内存当中。这个时候我们需要检查-Xms -Xmx最小最大堆配置是否合理。
// 再一个dump出现当前内存来分析一下是否使用了大量的循环或使用大量内存代码。
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(() -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do nothing
}
});
}
return "("+Thread.currentThread().getName()+")serverPort: " + serverPort + " name:" + name;
}
}
@Data
class OOMObject {
String name;
int age;
public static void stackOverFlowErrorMethod(String name) {
if(name!=null&&"zhouxx".equals(name)){
return;
}
stackOverFlowErrorMethod( name);
}
}