一、调优的概述
1.1调优的目的
- 防止出现OOM,进行JVM规划和预调优
- 解决程序运行中各种OOM
- 减少Full GC出现的频率,解决运行慢、卡顿问题
二、生产环境的问题
2.1、堆溢出
原因: 1、代码中可能存在大对象分配 2、可能存在内存泄漏,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。
解决方法: 1、检查是否存在大对象的分配,最有可能的是大数组分配 2、通过jmap命令,把堆内存dump下来,使用MAT等工具分析一下,检查是否存在内存泄漏的问题 3、如果没有找到明显的内存泄漏,使用-Xmx加大堆内存 4、还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性
案例:
/**
* 案例1:模拟线上环境OOM
* 参数设置:
* -XX:+PrintGCDetails -XX:MetaspaceSize=64m
* -XX:+HeapDumpOnOutOfNemoryError -XX:HeapDumpPath=heap/heapdump.hprof
* -XX:+PrintGCDateStamps -Xms30M -Xmx30M -Xloggc:log/gc-oomHeap.log
*
*/
@RequestMapping("/oomTest")
public void addObject(){
System.err.println("oomTest"+peopleSevice);
ArrayList<People> people = new ArrayList<>();
while (true){
people.add(new People());
}
}
2.2、元空间的溢出
原因: 1.运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载 2.应用长时间运行,没有重启 3.元空间内存设置过小
解决方法: 1.运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载 2.应用长时间运行,没有重启 3.元空间内存设置过小
案例:
/**
* 案例2:模拟元空间OOM溢出
*参数设置:
* -XX:+PrintGCDetails -XX:MetaspaceSize=60m -XX:MaxMetaspaceSize=60m-XSS512K -XX:+HeapDumponOutOfMemoryErrorl
* -XX:HeapDumpPath=heap/heapdumpMeta.hprof -xx:SurvivorRatio=8
* -XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:+PrintGCDateStamps-Xms60M -Xmx60M -Xloggc:log/gc-oomMeta.log
*/
@RequestMapping("/metaSpaceOom")
public void metaSpaceOom(){
ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
while (true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(People.class);
enhancer.setUseCache(false);
//enhancer.setUseCache(true);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
System.out.println("我是加强类,输出print之前的加强方法");
return methodProxy.invokeSuper(o,objects);
});
People people = (People)enhancer.create();
people.print();
System.out.println(people.getClass());
System.out.println("totalClass:" + classLoadingMXBean.getTotalLoadedClassCount());
System.out.println("activeClass:" + classLoadingMXBean.getLoadedClassCount());
System.out.println("unloadedClass:" + classLoadingMXBean.getUnloadedClassCount());
}
}
2.3、GC overhead limit exceeded
原因: 这个是DK6新加的错误类型,一般都是堆太小导致的。Sun官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。本质是一个预判性的异常,抛出该异常时系统没有真正的内存溢出
解决:
1.检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。
2.添加参数-XX:-UseGCOverheadLimit禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,最终出现 java.lang.OutOfMemoryError: Java heap
space。
3. dump内存,检查是否存在内存泄漏,如果没有,加大内存。
测试:
/**
*
* 测试 GC overhead limit exceeded
* 参数设置:
* -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
* -XX:HeapDumpPath=heap/dumpExceeded.hprof
* -XX:+PrintGCDateStamps -Xms10M -Xmx1OM-xloggc:log/gc-oomExceeded.log
*/
public static void main(String[] args) {
test1();
// test2();
}
public static void test1() {
int i = 0;
List<String> list = new ArrayList<>();
try {
while (true) {
list.add(UUID.randomUUID().toString().intern());
i++;
}
} catch (Throwable e) {
System.out.println("************i: " + i);
e.printStackTrace();
throw e;
}
}
//回收效率大于2%所以只会出现堆空间不足
public static void test2() {
String str = "";
Integer i = 1;
try {
while (true) {
i++;
str += UUID.randomUUID();
}
} catch (Throwable e) {
System.out.println("************i: " + i);
e.printStackTrace();
throw e;
}
}
2.4 、线程溢出
注意:windos试不出来,超过windos上线会重启
线程创建公式: (MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Numberof threads MaxProcessMemory 指的是进程可寻址的最大空间 VMMemory JVM内存 ReservedOsMemory 保留的操作系统内存 ThreadStackSize 线程栈的大小
注意:在32位操作系统下当前公式遵守的 在64位操作系统下MaxProcessMemory (最大寻址空间)这个值接近无限大,所以ThreadStackSize不影响公式的值
Linux查看线程数: cat /proc/sys/kernel/pid_max 系统最大pid值,在大型系统里可适当调大 cat /proc/sys/kernel/threads-max 系统允许的最大线程数 maxuserprocess (ulimit -u)系统限制某用户下最多可以运行多少进程或线程 cat /proc/sys/vm/max_map_count
程序
public class TestNativeOutOfMemoryError {
public static void main(String[] args) {
for (int i = 0; ; i++) {
System.out.println("i = " + i);
new Thread(new HoldThread()).start();
}
}
}
class HoldThread extends Thread {
CountDownLatch cdl = new CountDownLatch(1);
@Override
public void run() {
try {
cdl.await();
} catch (InterruptedException e) {
}
}
}
三、性能优化
3.1性能监控
一种以非强行或者入侵方式收集或查看应用运营性能数据的活动。 监控前,设置好回收器组合,选定CPU(主频越高越好),设置年代比例,设置日志参数(生产环境中通常不会只设置一个日志文件)。比如: -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGcCause
问题:
- GC频繁
- cpu load过高
- OOM
- 内存泄漏
- 死锁
- 程序响应时间过长
3.2性能分析
- 打印GC日志,通过GCviewer或者gceasy.io来分析日志信息
- 灵活运用命令行工具,jstack, jmap, jinfo等
- dump出堆文件,使用内存分析工具分析文件(jconsole/jvisualvm/jprofiler)
- 使用阿里Arthas,或jconsole,JVisualVM来实时查看JVM状态
- jstack查看堆栈信息
3.3性能调优
- 适当增加内存,根据业务背景选择垃圾回收器
- 优化代码,控制内存使用
- 增加机器,分散节点压力
- 合理设置线程池线程数量
- 使用中间件提高程序效率,比如缓存,消息队列等