美文网首页
JVM性能监控与调优

JVM性能监控与调优

作者: saoraozhe3hao | 来源:发表于2018-08-01 10:27 被阅读0次

JVM

Java工具文档:https://docs.oracle.com/javase/8/docs/technotes/tools/index.html

JVM内存结构

JVM规范:https://docs.oracle.com/javase/specs/index.html

1、Heap区(堆区):Young区(Survivor区、Eden区)、Old区
2、Metaspace区(非堆区即Non-Heap区):CCS区、CodeCache区
Heap区:存储对象
Metaspace区:存储类、常量、静态变量、本地方法栈
Survivor区:S0+S1
CodeCache区:缓存编译代码得到的机器码
Perm区:永久代,JDK 1.8已移除该区

对象分配规则
1、优先分配在Eden区
2、大对象分配到Old区:可由 -XX:PretenureSizeThreshold 配置
3、长期存活对象移动到Old区:可由 -XX:MaxTenuringThreshold 配置

总结:
JVM内存 = Heap区 + Non-Heap区
Heap区= Young区(年轻代)+ Old区(年老代、Tenured,年轻代中经过垃圾回收没有回收掉的对象将被Copy到年老代)
Young区 = Eden区(新生代、New,存放新建对象)+ Survivor区(中生代,新生代中经过垃圾回收没有回收掉的对象将被Copy到中生代)

JVM参数

1、标准参数:-help -version -CLASSPATH等
2、X参数:-Xint(解释执行)-Xcomp(编译执行)-Xmixed(JVM决定执行方式)等
3、布尔类型XX参数:-XX:+属性(启用属性)-XX:-属性(禁用属性)
4、非布尔类型XX参数:-XX:属性=值
-Xms512m等价于-XX:InitialHeapSize=512m,堆内存初始值,默认为物理内存的1/64
-Xmx3g等价于-XX:MaxHeapSize=3g,最大堆内存,默认为物理内存的1/4
(经验:-Xms 和 -Xmx设置成一样,使得垃圾回收后不需要重新分隔计算堆区;建议-Xmx设置为可用内存的80%)
-XX:newSize,堆内存里,新生代初始内存
-XX:MaxnewSize,堆内存里,新生代最大内存
-Xmn512m,同时对-XX:newSize和-XX:MaxnewSize进行设置,即让他们都等于512m

-XX:PermSize,非堆内存初始值
-XX:MaxPermSize,非堆内存最大值
-XX:+UseCompressedClassPointer:启用短类指针,即对象中指向类的指针为32位短指针;启用该参数时,类存在CCS区

Java工具

查看Java进程:jps
启动时查看所有JVM参数:java -XX:+PrintFlagsFinal -version
运行时查看参数:jinfo -flag MaxHeapSize 进程ID
查看非默认参数:jinfo -flags 进程ID
查看类加载信息:jstat -class 进程ID
查看垃圾回收信息:jstat -gc 进程ID
查看编译信息:jstat -compiler 进程ID
导出内存映像:jmap -dump:format=b,file=heap.hprof 进程ID
查看线程状态和调用栈:jstack 进程ID

垃圾回收

JVM调优指南:https://docs.oracle.com/javase/10/gctuning/toc.htm
内存泄漏:一个无用对象的指针一直被持有,不能被回收
垃圾回收:从根节点开始,做引用可达性分析,不可达对象即被回收
根节点:类加载器、Thread、本地变量表、static成员、常量引用、本地方法栈的变量等
标记清除法:做可达性分析时标记,分析完后清除,容易产生内存碎片(堆内存不连续)
标记整理法:做可达性分析时标记,同时移动可存活对象到一块,避免了内存碎片,但整理内存比较耗时
复制法:把内存分成两块S0+S1,每次只使用一块,用完时将存活对象复制到另一块,并清理原块,空间利用率低
分带垃圾回收:Young区存放生命周期短的对象,采用复制法;Old区存放生命周期长的对象,用标记法
垃圾收集器
1、串行收集器:单个垃圾收集线程,小内存适用
2、并行收集器(默认):多个垃圾收集线程,但阻塞用户线程,
3、并发收集器(CMS、G1等):多个垃圾收集线程,不阻塞用户线程,-XX:GCTimeRatio=n配置垃圾收集时间占比为 1/(n+1)
-XX:+UseSerialGC:Young区采用串行收集器
-XX:+UseSerialOldGC:Old区采用串行收集器
-XX:+UseParallelGC:Young区采用并行收集器,-XX:MaxGCPauseMillis 配置用户线程最大停顿时间
-XX:+UseParallelOldGC:Old区采用并行收集器,-XX:ParallelGCThreads 配置GC线程数
-XX:+UseG1GC:两区采用G1收集器,性能高停顿小,适用于6G以上内存,-XX:G1HeapRegionSize配置region大小

Young区垃圾回收(minor gc)时机:Eden区满的时候
Old区垃圾回收(major gc 、full gc)时机:Old区太满时

MAT(Eclipse Memory Analyzer)

JVM内存分析工具
官网:https://www.eclipse.org/mat/
使用:打开jmap导出的内存映像文件,可以查看对象数量、引用关系、内存溢出嫌疑点等

CPU使用率异常(飙高)分析

线程状态:new -> runable(ready 和 running) -> Watiting\Timed Waiting\Blocked -> runable -> terminated
1、查看各进程cpu占用率:top
2、查看各线程cpu占用率:top -p 进程ID -H
3、技术线程ID的16进制表示:printf "%x" 线程ID
4、导出线程调用栈:jstack 进程ID > temp.txt # 里面的线程ID是16进制表示
5、在temp.txt中找到嫌疑线程,查看调用栈
死锁(synchronized)分析
1、查看进程ID:jps
2、导出线程分析:jstack 进程ID > temp.txt
3、在temp.txt中找到deadlock、waiting to lock

Java VisualVM

程序:jdk/bin/jvisualvm.exe
官网:https://visualvm.github.io/
功能:Java进程监控工具,可以监控cpu,内存,线程,可以到处内存映像

Java VisualVM 监控远程 JVM(以Tomcat为例)
1、在tomcat/bin/Catalina.sh中添加

JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote     # 开启远程监控
  -Dcom.sun.management.jmxremote.port=9999         # 远程监控端口
  -Dcom.sun.management.jmxremote.ssl=false 
  -Dcom.sun.management.jmxremote.authenticate=false
 -Djava.rmi.server.hostname=10.200.80.172"             # 本机IP

2、Java VisualVM中添加远程监控

GC日志

在tomcat/bin/Catalina.sh中添加

JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps 
-XX:+PrintGCDateStamps
-Xloggc:$CATALINA_HOME/logs/gc.log"  # 日志路径

并行GC日志格式:
2018-12-01T22:12:09.427 +0800: 1.971: [GC (Allocation Failure) [PSYoungGen: 31744K->5113K(36864K)] 31744K-> 10233K(121856K), 0.0200997 secs] [Times: user=0.05 sys=0.01, real=0.02 secs]
即:回收发生时间: [GC (原因) [区域: 回收前占用-> 回收后占用(区域总空间)] 回收前堆占用-> 回收后堆占用(堆总空间), 回收耗时]

分析:Young区回收的空间 可能比 堆回收的空间 大,因为有 一部分占用 从 Young区转移到了Old区
GC类型:GC(Young区GC),Full GC(多区GC)
GC原因:Allocation Failure(内存分配失败触发GC)、Metadata GC Threshold(Metaspace不足触发GC)
GC区域:PSYoungGen(Young区),ParOldGen(Old区)

G1 GC日志格式
2018-12-01T22:12:09.427 +0800: 1.971: [GC pause(G1 Evacuation Pause) (young), 0.0200997 secs]
[Eden: 14.0M(14.0M)->0.0B(17.0M)] Survivors: 0.0B-> 2048.0K Heap: 14.0M(124.0M)->3615.0K(124.0M)]
即:回收发生时间: [GC pause(原因) (区域), 回收耗时]
[Eden: Eden区回收情况 Survivors: Survivor区变化情况 Heap: 堆回收情况]

GC区域:young、mixed

GC 日志分析工具

关键性能指标:Throughput(吞吐量),Pause GC Time(GC停顿时间)
吞吐量:应用程序线程用时占程序总用时的比例

1、在线工具 gceasy
上传GC日志文件,得到分析报告(包含优化建议)
关注指标:Throughput、Pause GC Time
2、桌面工具 GCViewr
是一个可执行jar,打开GC日志文件,得到报告

字节码分析

查看字节码:javap -verbose Test.class
Constant pool:常量池
Literal:字面量
字节码解读:
1、for(int i=0;i<10;i++) 与 for(int i=0;i<10;++i) 的字节码是一样的,效率一样
2、字符串拼接效率低的原因:s + "string" 会编译成 new StringBuffer(s).append('string').toString(),每+一次就要构造一个StringBuffer对象
3、try{return i;}finally{i++;},finally里始终会执行,但是return的是finally前的 i
4、字面量存在常量池中,代码中出现的相同字面量都引用同一个地址

const int b = 10; // b为常量,10为字面量 
string str = "hello world!"; // str 为变量,hello world!为字面量 

JAVA代码优化

1、尽力重用对象
2、ArrayList 由数组实现,HashMap 有链表数组实现,元素增长到一定程度时,都要进行扩容,有耗时的复制过程。ArrayList 和 HashMap 初始化时要指定何时的长度
3、尽量使用基本类型,而非包装类型
4、尽量减小变量的作用域、锁的粒度
5、尽量使用连接池、线程池、对象池
6、尽量少用正则表达式

线程安全

进程与线程:线程是不拥有资源的,进程是拥有资源的。而线程是由进程创建的,一个进程可以创建多个线程,这些线程共享着进程中的资源
线程不安全:存在多个线程同时修改同一数据的可能,会造成数据的不一致
线程安全:不存在多线程同时修改同一数据的情况
线程安全的实现:1、synchronized;2、线程代码不去修改共享变量

相关文章

网友评论

      本文标题:JVM性能监控与调优

      本文链接:https://www.haomeiwen.com/subject/xvelvftx.html