目录
一 提出问题
问题
- 生产环境发生了内存溢出该如何处理?
- 生产环境应该给服务器分配多少内存?
- 如何对垃圾收集器的性能进行调优?
- 生产环境CPU负载飙升如何处理?
- 生产环境应该给应用分配多少线程?
- 如何不加log就确定是否执行了某一行代码?
- 如何不加log就能实时查看某个方法的参数值和返回值?
- JVM的字节码
- 循环体中用"+"做字符串拼接为什么效率低,“+”的底层一定是StringBuilder.append()吗?
- String常量池
- i++与++i哪种写法效率更高?
- jvm监控和调试工具的使用
- 理解JVM的GC机制,学会GC调优
- TomCat性能监控与调优
二 基于JDK命令行工具的监控
1. JVM的三种参数类型
1.1 标准参数
jvm的标准参数,在jvm各个版本中基本不变。
使用java
获取所有标准参数
- -help
- -server -client ^①^
- -version -showversion
- -cp -classpath
使用java -version
命令可以输出java的版本信息,从第4行可以看到:
JVM的名字(HotSpot)、类型(Server)、build ID(24.79-b02),JVM以混合模式(mixed mode)在运行,这是HotSpot默认的运行模式,意味着JVM在运行时可以动态的把字节码编译为本地代码。关于切换类型(Server Client)以及二者的区别详见参考文档1。
C:\Users\Administrator>java -version
java version "1.8.0_51"
Java(TM) SE Runtime Environment (build 1.8.0_51-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)
1.2 X 参数
非标准化参数,在jvm各个版本中会有一些小变化。
使用java -X
获取所有X参数
-Xint:JVM以解释方式执行所有的字节码,会显著降低运行速度
-Xcomp:JVM在第一次使用时就把所有字节码编译为本地代码
-Xmixed:混合模式(默认),由jvm自己决定是否编译为本地代码,会将字节码中多次被调用的部分便以为本地代码以提高执行效率;被调用很少的方法会在解释模式下执行,减少编译和优化成本
# 默认是mixed mode(混合模式),在java的版本信息中显示 C:\Users\Administrator>java -version java version "1.8.0_51" Java(TM) SE Runtime Environment (build 1.8.0_51-b16) Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode) # 修改为interpreted mode(解释执行) C:\Users\Administrator>java -Xint -version java version "1.8.0_51" Java(TM) SE Runtime Environment (build 1.8.0_51-b16) Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, interpreted mode) # 修改为compiled mode(编译执行) C:\Users\Administrator>java -Xcomp -version java version "1.8.0_51" Java(TM) SE Runtime Environment (build 1.8.0_51-b16) Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, compiled mode
测试三种JVM工作模式可以使用死循环代码测试运行时间,具体命令如下:
javac HelloWorld.java java -Xint HelloWorld java -Xcomp HelloWorld java -Xmixed HelloWorld
1.3 XX 参数
非标准化参数,在jvm各个版本中变化较大,主要用于jvm调优和debug,也是最常用的参数
使用java -XX:+PrintFlagsFinal -version > flags1.txt
命令获取所有XX参数^③^,大约有700+参数
Boolean 类型
格式:-XX:[+-] 表示启用或者禁用name属性
举例:-XX:UseConcMarkSweepGC 启用CMS垃圾收集器
-XX:+UseG1GC 启用G1垃圾收集器K-V 类型
格式:-XX:[+-]= 表示name属性的值是value
举例:-XX:InitialHeapSize=3116367872 初始化堆内存,常用
-Xms512m
表示,不要误认为是X参数-XX:MaxHeapSize=3116367872
最大堆内存,常用-Xmx1024m
表示-XX:ThreadStackSize=1024
线程堆栈大小,常用-Xss128k
表示-XX:MaxGCPauseMills=500
表示GC的最大停顿时间是500ms-XX:GCTimeRatio=19
1.4 常用命令
jps
:查看java进程。-m
显示传递给main
方法的参数。-l
显示应用程序main
类的完整包名称或应用程序的JAR文件的完整路径名。-v
显示传递给JVM的参数jinfo
:查看Java进程的修改过的jvm参数jinfo -flags 2345
查看进程id为2345的jvm参数jinfo -flag MaxheapSize 2345
查看进程id为2345的jvm参数最大堆内存MaxheapSize
的值
2. jstat查看虚拟机统计信息
2.1 类加载信息
# 进程id21640 输出间隔1000ms 输出次数3
# Loaded加载类的数量 Bytes加载的kB数 Unloaded:卸载的类数 Bytes:卸载的Kbytes数Time:执行类加载和卸载操作所花费的时间
C:\Users\Administrator>jstat -class 21640 1000 3
Loaded Bytes Unloaded Bytes Time
3010 5823.5 52 77.1 3.48
3010 5823.5 52 77.1 3.48
3010 5823.5 52 77.1 3.48
2.2 垃圾回收信息
# 输出结果各项指标详见参考文档2
C:\Users\Administrator>jstat -gc 21640
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
18432.0 19456.0 0.0 0.0 77824.0 48459.9 126976.0 10715.1 19456.0 18288.3 2304.0 1968.8 13 0.204 1 0.139 0.342
2.3 JIT编译信息
# 查看JIT编译信息
C:\Users\Administrator>jstat -compiler 21640
Compiled Failed Invalid Time FailedType FailedMethod
3054 1 0 12.35 1 org/apache/tomcat/util/IntrospectionUtils setProperty
3. jmap + MAT分析内存溢出 [实战]
3.1 模拟内存溢出
/**
* Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit
* exceeded
*/
private static void heapSize2() {
Map<Object,Object> map = new HashMap();
Random r = new Random();
Integer i = 0;
while (true) {
map.put(i++, "aaa");
}
}
/**
* Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
*/
private static void heapSize() {
List<String> names = new ArrayList();
for (;;) {
names.add("bbb");
}
}
3.2 导出内存影像文件
使用命令行参数,内存溢出时自动导出(最常用)
-XX+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./
另外也可以使用
-Xrunhprof:head=site
参数生成java.hprof.txt 文件,不过这样会影响 JVM的运行效率,不建议在生产环境中使用使用jmap命令手动导出
jmap -dump:format=b,file=heap.hprof 2345
format表示以二进制形式导出,file表示文件名称,2345表示要操作的java进程id
更多jmap命令选项详见参考文档2
- 使用JConsole生成Dump文件
3.3 使用MAT分析dump文件
首先下载MAT (Memory Analyzer),参考MAT教程^④⑤^,分析dump文件
Shallow Heap表示对象本身所占的内存^④⑤^,Retained Heap表示对象和对象中的引用总共占的内存大小。比如User对象包含name属性,会引用String对象。
排除虚引用,根据GC Roots查看对象引用
4. jstack分析死循环与死锁 [实战]
CPU负载(load average)^⑦^和使用率过高,很可能的原因就是死循环。
首先可以使用top命令查看占用CPU最高的进程信息,获取进程pid
使用
jstack [pid] > myStack.txt
命令打印指定java进程的所有线程堆栈信息使用Linux命令
top -p [pid] -H
监控指定进程中所有线程信息,然后找到CPU占用率高的线程nid(windows中可以process exlporer工具查看指定进程中所有线程信息)如图可以看到
8247(0x2037)
线程的CPU使用率达到了96.7%,此时我们就可以确认是这个线程的问题了在myStack.txt文件中查看线程id为nid的堆栈信息(myStack.txt线程id是16进制,top -p命令线程id是10进制),就可以找到导致CPU飙升的线程的堆栈信息了,然后分析问题,解决问题。
如图可以看到
8247(0x2037)
线程的堆栈信息另外jstack命令能自动检测到死锁,在myStack.txt末尾会有 “found 1 deadlock”,也会打印死锁线程的相关信息,根据这些信息就可以分析问题,解决问题了。另外,也可以使用JConsole来检测死锁
三 基于JVisualVM的可视化监控
四 基于Btrace的监控调试
4.1 简介
BTrace可以动态地向目标应用程序的字节码注入追踪代码,用到的技术JavaCompilerAPI,JVMTI,Agent,Instrumentation+ASM
1、接口性能变慢,分析每个方法的耗时情况;
2、当在Map中插入大量数据,分析其扩容情况;
3、分析哪个方法调用了System.gc(),调用栈如何;
4、执行某个方法抛出异常时,分析运行时参数;
4.2 环境准备
下载JVisualVM BTrace插件https://visualvm.github.io/pluginscenters.html
下载BTracehttps://github.com/btraceio/btrace/releases/tag/v1.3.11
jar包
4.2 使用BTrace
4.3.1 简单使用
import com.sun.btrace.AnyType;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
@BTrace
public class PrintArgSimple {
// 在Ch4Controller类的fun1方法的入口处(ENTRY)进行追踪
@OnMethod(clazz = "com.imooc.monitor_tuning.chapter4.Ch4Controller",
method = "fun1", location = @Location(Kind.ENTRY))
public static void anyRead(@ProbeClassName String className, @ProbeMethodName String methodName, AnyType[] args) {
BTraceUtils.printArray(args); //fun1方法的所有参数
BTraceUtils.println(className + ":" + methodName);
}
}
btrace [pid] MyBtrace.java
4.3.2 拦截构造函数,重载方法
4.3.3 拦截返回值,异常,行号
4.3.4 拦截复杂参数,环境变量
4.3.5 注意事项
五 Tomcat性能监控与调优
七 JVM层GC调优
八 JVM字节码与Java代码层调优
九 总结
参考文档: