1.1调优的目的
原因:
1、代码中可能存在大对象分配
2、可能存在内存泄漏,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。
解决方法:
1、检查是否存在大对象的分配,最有可能的是大数组分配
2、通过jmap命令,把堆内存dump下来,使用MAT等工具分析一下,检查是否存在内存泄漏的问题
3、如果没有找到明显的内存泄漏,使用-Xmx加大堆内存
4、还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性
案例:
/*** 案例1&#xff1a;模拟线上环境OOM* 参数设置:* -XX:&#43;PrintGCDetails -XX:MetaspaceSize&#61;64m* -XX:&#43;HeapDumpOnOutOfNemoryError -XX:HeapDumpPath&#61;heap/heapdump.hprof* -XX:&#43;PrintGCDateStamps -Xms30M -Xmx30M -Xloggc:log/gc-oomHeap.log**/&#64;RequestMapping("/oomTest")public void addObject(){System.err.println("oomTest"&#43;peopleSevice);ArrayList<People> people &#61; new ArrayList<>();while (true){people.add(new People());}}
原因&#xff1a;
1.运行期间生成了大量的代理类&#xff0c;导致方法区被撑爆&#xff0c;无法卸载
2.应用长时间运行&#xff0c;没有重启
3.元空间内存设置过小
解决方法&#xff1a;
1.运行期间生成了大量的代理类&#xff0c;导致方法区被撑爆&#xff0c;无法卸载
2.应用长时间运行&#xff0c;没有重启
3.元空间内存设置过小
案例&#xff1a;
/*** 案例2:模拟元空间OOM溢出*参数设置:* -XX:&#43;PrintGCDetails -XX:MetaspaceSize&#61;60m -XX:MaxMetaspaceSize&#61;60m-XSS512K -XX:&#43;HeapDumponOutOfMemoryErrorl* -XX:HeapDumpPath&#61;heap/heapdumpMeta.hprof -xx:SurvivorRatio&#61;8* -XX:&#43;TraceClassLoading -XX:&#43;TraceClassUnloading -XX:&#43;PrintGCDateStamps-Xms60M -Xmx60M -Xloggc:log/gc-oomMeta.log*/&#64;RequestMapping("/metaSpaceOom")public void metaSpaceOom(){ClassLoadingMXBean classLoadingMXBean &#61; ManagementFactory.getClassLoadingMXBean();while (true){Enhancer enhancer &#61; new Enhancer();enhancer.setSuperclass(People.class);enhancer.setUseCache(false);//enhancer.setUseCache(true);enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {System.out.println("我是加强类&#xff0c;输出print之前的加强方法");return methodProxy.invokeSuper(o,objects);});People people &#61; (People)enhancer.create();people.print();System.out.println(people.getClass());System.out.println("totalClass:" &#43; classLoadingMXBean.getTotalLoadedClassCount());System.out.println("activeClass:" &#43; classLoadingMXBean.getLoadedClassCount());System.out.println("unloadedClass:" &#43; classLoadingMXBean.getUnloadedClassCount());}}
原因&#xff1a;
这个是DK6新加的错误类型&#xff0c;一般都是堆太小导致的。Sun官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。本质是一个预判性的异常&#xff0c;抛出该异常时系统没有真正的内存溢出
解决&#xff1a;
1&#xff0e;检查项目中是否有大量的死循环或有使用大内存的代码&#xff0c;优化代码。
2&#xff0e;添加参数-XX:-UseGCOverheadLimit
禁用这个检查&#xff0c;其实这个参数解决不了内存问题&#xff0c;只是把错误的信息延后&#xff0c;最终出现 java.lang.OutOfMemoryError: Java heap
space。
3. dump内存&#xff0c;检查是否存在内存泄漏&#xff0c;如果没有&#xff0c;加大内存。
测试&#xff1a;
/**** 测试 GC overhead limit exceeded* 参数设置&#xff1a;* -XX:&#43;PrintGCDetails -XX:&#43;HeapDumpOnOutOfMemoryError* -XX:HeapDumpPath&#61;heap/dumpExceeded.hprof* -XX:&#43;PrintGCDateStamps -Xms10M -Xmx1OM-xloggc:log/gc-oomExceeded.log*/public static void main(String[] args) {test1();// test2();}public static void test1() {int i &#61; 0;List<String> list &#61; new ArrayList<>();try {while (true) {list.add(UUID.randomUUID().toString().intern());i&#43;&#43;;}} catch (Throwable e) {System.out.println("************i: " &#43; i);e.printStackTrace();throw e;}}//回收效率大于2%所以只会出现堆空间不足public static void test2() {String str &#61; "";Integer i &#61; 1;try {while (true) {i&#43;&#43;;str &#43;&#61; UUID.randomUUID();}} catch (Throwable e) {System.out.println("************i: " &#43; i);e.printStackTrace();throw e;}}
注意&#xff1a;windos试不出来&#xff0c;超过windos上线会重启
线程创建公式&#xff1a;
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) &#61; Numberof threads
MaxProcessMemory 指的是进程可寻址的最大空间
VMMemory JVM内存
ReservedOsMemory 保留的操作系统内存
ThreadStackSize 线程栈的大小
注意&#xff1a;在32位操作系统下当前公式遵守的 在64位操作系统下MaxProcessMemory &#xff08;最大寻址空间&#xff09;这个值接近无限大&#xff0c;所以ThreadStackSize不影响公式的值
Linux查看线程数&#xff1a;
cat /proc/sys/kernel/pid_max 系统最大pid值&#xff0c;在大型系统里可适当调大
cat /proc/sys/kernel/threads-max 系统允许的最大线程数
maxuserprocess (ulimit -u&#xff09;系统限制某用户下最多可以运行多少进程或线程
cat /proc/sys/vm/max_map_count
程序
public class TestNativeOutOfMemoryError {public static void main(String[] args) {for (int i &#61; 0; ; i&#43;&#43;) {System.out.println("i &#61; " &#43; i);new Thread(new HoldThread()).start();}}
}class HoldThread extends Thread {CountDownLatch cdl &#61; new CountDownLatch(1);&#64;Overridepublic void run() {try {cdl.await();} catch (InterruptedException e) {}}
}
一种以非强行或者入侵方式收集或查看应用运营性能数据的活动。
监控前&#xff0c;设置好回收器组合&#xff0c;选定CPU&#xff08;主频越高越好&#xff09;&#xff0c;设置年代比例&#xff0c;设置日志参数&#xff08;生产环境中通常不会只设置一个日志文件&#xff09;。比如:
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log
-XX:&#43;UseGCLogFileRotation
-XX:NumberOfGCLogFiles&#61;5
-XX:GCLogFileSize&#61;20M
-XX:&#43;PrintGCDetails
-XX:&#43;PrintGCDateStamps
-XX:&#43;PrintGcCause
问题&#xff1a;
测试参数设置&#xff1a;
setenv.sh文件中写入&#xff08;大小根据自己情况修改): setenv.sh内容如下:
export CATALINA_OPTS&#61;"$CATALINA_OPTS -Xms30m"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:SurvivorRatio&#61;8"
export CATALINA_OPTS&#61;"$CATALINA_OPTS-Xmx30m"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:&#43;UseParallelGC"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:&#43;PrintGCDetails"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:MetaspaceSize&#61;64m"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:&#43;PrintGCDateStamps"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -Xloggc:/opt/tomcat8.5/logs/gc.log"
打印信息
//查看运行进程id
jps
//查看运行信息
jstat -gc 进程id 间隔时间&#xff08;毫秒&#xff09;次数
例如 jstat -gc 5397 1000 5
优化参数&#xff08;调大堆内存&#xff09;
export CATALINA_OPTS&#61;"$CATALINA_OPTS -xms120m"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:SurvivorRatio&#61;8"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -Xmx120m"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:&#43;UseParallelGC"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:&#43;PrintGCDetails"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:MetaspaceSize&#61;64m"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:&#43;PrintGCDateStamps"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -Xloggc:/opt/tomcat8.5/logs/gc.log"
结果&#xff1a;FullGC次数大幅降低
/*** 栈上分配测试* -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:&#43;PrintGCDetails** 只要开启了逃逸分析&#xff0c;就会判断方法中的变量是否发生了逃逸。如果没有发生了逃逸&#xff0c;则会使用栈上分配*/
public class StackAllocation {public static void main(String[] args) {long start &#61; System.currentTimeMillis();for (int i &#61; 0; i < 10000000; i&#43;&#43;) {alloc();}// 查看执行时间long end &#61; System.currentTimeMillis();System.out.println("花费的时间为&#xff1a; " &#43; (end - start) &#43; " ms");// 为了方便查看堆内存中对象个数&#xff0c;线程sleeptry {Thread.sleep(1000000);} catch (InterruptedException e1) {e1.printStackTrace();}}private static void alloc() {User user &#61; new User();//是否发生逃逸&#xff1f; 没有&#xff01;}static class User {}
}
jdk6之后默认开启栈上分配,测试需要关闭
-XX:-DoEscapeAnalysis
关闭栈上分配测试&#xff08;会在堆内存中分配对象&#xff09;
开启栈上分配测试
同步消除。如果一个对象被发现只能从一个线程被访问到&#xff0c;那么对于这个对象的操作可以不考虑同步。
public class SynchronizedTest {public void f() {/** 代码中对hollis这个对象进行加锁&#xff0c;但是hollis对象的生命周期只在f()方法中&#xff0c;* 并不会被其他线程所访问到&#xff0c;所以在JIT编译阶段就会被优化掉。** 问题&#xff1a;字节码文件中会去掉hollis吗&#xff1f;* 不会&#xff0c;因为只会由解释器&#xff0c;不会经过JIT编译器* */Object hollis &#61; new Object();synchronized(hollis) {System.out.println(hollis);}/** 优化后&#xff1b;* Object hollis &#61; new Object();* System.out.println(hollis);* */}
}
标量(Scalar&#xff09;是指一个无法再分解成更小的数据的数据。Java中的原始数据类型就是标量
相对的&#xff0c;那些还可以分解的数据叫做聚合量&#xff08;Aggregate) ,Java中的对象就是聚合量&#xff0c;因为他可以分解成其他聚合量和标量。
在JIT阶段&#xff0c;如果经过逃逸分析&#xff0c;发现一个对象不会被外界访问的话&#xff0c;那么经过JIT优化&#xff0c;就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。这个过程就是标量替换。
参数设置&#xff08;默认开启&#xff09;true
-XX:&#43;EliminateAllocations:
代码体现&#xff1a;
public static void main (string [ ] args){alloc ( ) ;
}
private static void alloc (){Point point &#61; new Point ( 1,2&#xff09;;system.out.println ( "point.x&#61;"&#43;point.x&#43;"; point.y&#61;"&#43;point.y);
}
class Point {private int x;private int y;以上代码&#xff0c;经过标量替换后&#xff0c;就会变成:private static void alloc() {int x &#61; l;Iint y &#61; 2;System.out.println ( "point.x&#61;"&#43;x&#43;"; point.y&#61;"&#43;y);
}
测试代码
/*** 标量替换测试* -Xmx100m -Xms100m -XX:&#43;DoEscapeAnalysis -XX:&#43;PrintGCDetails -XX:-EliminateAllocations** 结论&#xff1a;Java中的逃逸分析&#xff0c;其实优化的点就在于对栈上分配的对象进行标量替换。** &#64;author shkstart shkstart&#64;126.com* &#64;create 2021 12:01*/
public class ScalarReplace {public static class User {public int id;public String name;}public static void alloc() {User u &#61; new User();//未发生逃逸u.id &#61; 5;u.name &#61; "www.atguigu.com";}public static void main(String[] args) {long start &#61; System.currentTimeMillis();for (int i &#61; 0; i < 10000000; i&#43;&#43;) {alloc();}long end &#61; System.currentTimeMillis();System.out.println("花费的时间为&#xff1a; " &#43; (end - start) &#43; " ms");}
}
逃逸分析总结:
强制触发Full GC的方法
1、jmap -dump:live,format&#61;b,file&#61;heap.bin 将当前的存活对象dump到文件&#xff0c;此时会触发FullGc
2、jmap -histo:live 打印每个class的实例数目,内存占用,类全名信息.live子参数加上后,只统计活的对象数量.此时会触发FullGd
3、在性能测试环境&#xff0c;可以通过Java监控工具来触发FullGC&#xff0c;比如使用VisualVM和3Console,VisualVM集成了JConsole&#xff0c;VisualVM或者JConsole上面有一个触发GC的按钮。
估算GC频率
比如从数据库获取一条数据占用128个字节&#xff0c;需要获取1000条数据&#xff0c;那么一次读取到内存的大小就是128 B/1024 Kb/1024M&#xff09; * 1000 &#61; 0.122M&#xff0c;那么我们程序可能需要并发读取&#xff0c;比如每秒读取100次&#xff0c;那么内存占用就是0.122100 &#61; 12.2M&#xff0c;如果堆内存设置1个G&#xff0c;那么年轻代大小大约就是333M&#xff0c;那么333M80%/12.2M &#61;21.84s &#xff0c;也就是说我们的程序几乎每分钟进行两到三次youngGC。
ParallelGC默认是6:1:1
调整参数设置
注意&#xff1a;对于面向外部的大流量、低延迟系统&#xff0c;不建议启用此参数&#xff0c;建议关闭该参数。
1、ps aux / grep java 查看到当前java进程使用cpu、内存、磁盘的情况获取使用量异常的进程
2、top -Hp 进程pid检查当前使用异常线程的pid
3、把线程pid变为16进制如31695-》 7bcf 然后得到Ox7bcf
4、查看信息&#xff08;2种方式&#xff09;
测试参数设置
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:&#43;UseG1GC"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -xms 30m"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -xm×30m"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:&#43;PrintGCDetails"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:MetaspaceSize&#61;64m"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:&#43;PrintGCDateStamps"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -Xloggc :/opt/tomcat8.5/logs/gc.log"
export CATALINA_OPTS&#61;"$CATALINA_OPTS-XX:ConcGCThreads&#61;1"
增加线程数会增大吞吐量&#xff08;-XX:ConcGCThreads设置为2效果和4,8差不多 因为最多为并行垃圾回收的1/4&#xff09;
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:&#43;UseG1GC"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -xms 30m"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -xm×30m"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:&#43;PrintGCDetails"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:MetaspaceSize&#61;64m"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:&#43;PrintGCDateStamps"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -Xloggc :/opt/tomcat8.5/logs/gc.log"
export CATALINA_OPTS&#61;"$CATALINA_OPTS -XX:ConcGCThreads&#61;2"
根据服务器的cpu和性能合理使用垃圾回收器
响应时间控制在100ms怎么保证&#xff1f;
做压测控制延迟时间