pull/1/head
595208882@qq.com 4 years ago
parent e1c2e1953d
commit 0288979bc4

@ -4,971 +4,6 @@
[TOC]
# JDK Tools
## jps
用于显示当前用户下的所有java进程信息
```shell
# jps [options] [hostid]
# q:仅输出VM标识符, m: 输出main method的参数,l:输出完全的包名, v:输出jvm参数
[root@localhost ~]# jps -l
28729 sun.tools.jps.Jps
23789 cn.ms.test.DemoMain
23651 cn.ms.test.TestMain
```
## jstat
用于监视虚拟机运行时状态信息类装载、内存、垃圾收集、JIT编译等运行数据
**-gc**:垃圾回收统计(大小)
```shell
# 每隔2000ms输出<pid>进程的gc情况一共输出2次
[root@localhost ~]# jstat -gc <pid> 2000 2
# 每隔2s输出<pid>进程的gc情况每个3条记录就打印隐藏列标题
[root@localhost ~]# jstat -gc -t -h3 <pid> 2s
Timestamp S0C S1C S0U S1U ... YGC YGCT FGC FGCT GCT
1021.6 1024.0 1024.0 0.0 1024.0 ... 1 0.012 0 0.000 0.012
1023.7 1024.0 1024.0 0.0 1024.0 ... 1 0.012 0 0.000 0.012
1025.7 1024.0 1024.0 0.0 1024.0 ... 1 0.012 0 0.000 0.012
Timestamp S0C S1C S0U S1U ... YGC YGCT FGC FGCT GCT
1027.7 1024.0 1024.0 0.0 1024.0 ... 1 0.012 0 0.000 0.012
1029.7 1024.0 1024.0 0.0 1024.0 ... 1 0.012 0 0.000 0.012
# 结果说明: C即Capacity 总容量U即Used 已使用的容量
##########################
# S0C年轻代中第一个survivor幸存区的容量 (kb)
# S1C年轻代中第二个survivor幸存区的容量 (kb)
# S0U年轻代中第一个survivor幸存区目前已使用空间 (kb)
# S1U年轻代中第二个survivor幸存区目前已使用空间 (kb)
# EC年轻代中Eden伊甸园的容量 (kb)
# EU年轻代中Eden伊甸园目前已使用空间 (kb)
# OCOld代的容量 (kb)
# OUOld代目前已使用空间 (kb)
# PCPerm(持久代)的容量 (kb)
# PUPerm(持久代)目前已使用空间 (kb)
# YGC从应用程序启动到采样时年轻代中gc次数
# YGCT从应用程序启动到采样时年轻代中gc所用时间(s)
# FGC从应用程序启动到采样时old代(全gc)gc次数
# FGCT从应用程序启动到采样时old代(全gc)gc所用时间(s)
# GCT从应用程序启动到采样时gc用的总时间(s)
```
**-gcutil**:垃圾回收统计(百分比)
```shell
[root@localhost bin]# jstat -gcutil <pid>
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 99.80 16.21 26.18 93.34 90.74 9 0.056 2 0.045 0.102
# 结果说明
##########################
# S0年轻代中第一个survivor幸存区已使用的占当前容量百分比
# S1年轻代中第二个survivor幸存区已使用的占当前容量百分比
# E年轻代中Eden伊甸园已使用的占当前容量百分比
# Oold代已使用的占当前容量百分比
# Pperm代已使用的占当前容量百分比
# YGC从应用程序启动到采样时年轻代中gc次数
# YGCT从应用程序启动到采样时年轻代中gc所用时间(s)
# FGC从应用程序启动到采样时old代(全gc)gc次数
# FGCT从应用程序启动到采样时old代(全gc)gc所用时间(s)
# GCT从应用程序启动到采样时gc用的总时间(s)
```
**-gccapacity**:堆内存统计
```shell
[root@localhost ~]# jstat -gccapacity <pid>
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC
84480.0 1349632.0 913408.0 54272.0 51200.0 502784.0 168448.0 2699264.0 168448.0 168448.0 21504.0 83968.0 51712.0 51712.0 9 0
# 结果说明
##########################
# NGCMN年轻代(young)中初始化(最小)的大小 (kb)
# NGCMX年轻代(young)的最大容量 (kb)
# NGC年轻代(young)中当前的容量 (kb)
# S0C年轻代中第一个survivor幸存区的容量 (kb)
# S1C年轻代中第二个survivor幸存区的容量 (kb)
# EC年轻代中Eden伊甸园的容量 (kb)
# OGCMNold代中初始化(最小)的大小 (kb)
# OGCMXold代的最大容量 (kb)
# OGCold代当前新生成的容量 (kb)
# OCOld代的容量 (kb)
# PGCMNperm代中初始化(最小)的大小 (kb)
# PGCMXperm代的最大容量 (kb)
# PGCperm代当前新生成的容量 (kb)
# PCPerm(持久代)的容量 (kb)
# YGC从应用程序启动到采样时年轻代中gc次数
# GCT从应用程序启动到采样时gc用的总时间(s)
```
**-gccause**:垃圾收集统计概述(同-gcutil附加最近两次垃圾回收事件的原因
```shell
[root@localhost ~]# jstat -gccause <pid>
S0 S1 E O P YGC YGCT FGC FGCT GCT LGCC GCC
0.00 79.23 39.37 39.92 99.74 9 0.198 0 0.000 0.198 Allocation Failure No GC
# 结果说明
##########################
# LGCC最近垃圾回收的原因
# GCC当前垃圾回收的原因
```
## jstack
jstack(Java Stack Trace)主要用于打印线程的堆栈信息是JDK自带的很强大的线程分析工具可以帮助我们排查程序运行时的线程状态、死锁状态等。
```shell
# dump出进程<pid>的线程堆栈快照至/data/1.log文件中
jstack -l <pid> >/data/1.log
# 参数说明:
# -F如果正常执行jstack命令没有响应比如进程hung住了可以加上此参数强制执行thread dump
# -m除了打印Java的方法调用栈之外还会输出native方法的栈帧
# -l打印与锁有关的附加信息。使用此参数会导致JVM停止时间变长在生产环境需慎用
```
jstack dump文件中值得关注的线程状态有
- **死锁Deadlock —— 重点关注**
- 执行中Runnable
- **等待资源Waiting on condition —— 重点关注**
- 等待某个资源或条件发生来唤醒自己。具体需结合jstacktrace来分析如线程正在sleep网络读写繁忙而等待
- 如果大量线程在“waiting on condition”并且在等待网络资源可能是网络瓶颈的征兆
- **等待获取监视器Waiting on monitor entry —— 重点关注**
- 如果大量线程在“waiting for monitor entry”可能是一个全局锁阻塞住了大量线程
- 暂停Suspended
- 对象等待中Object.wait() 或 TIMED_WAITING
- **阻塞Blocked —— 重点关注**
- 停止Parked
**注意**如果某个相同的call stack经常出现 我们有80%的以上的理由确定这个代码存在性能问题(读网络的部分除外)。
**场景一分析BLOCKED问题**
```shell
"RMI TCP Connection(267865)-172.16.5.25" daemon prio=10 tid=0x00007fd508371000 nid=0x55ae waiting for monitor entry [0x00007fd4f8684000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.apache.log4j.Category.callAppenders(Category.java:201)
- waiting to lock <0x00000000acf4d0c0> (a org.apache.log4j.Logger)
at org.apache.log4j.Category.forcedLog(Category.java:388)
at org.apache.log4j.Category.log(Category.java:853)
at org.apache.commons.logging.impl.Log4JLogger.warn(Log4JLogger.java:234)
at com.tuan.core.common.lang.cache.remote.SpyMemcachedClient.get(SpyMemcachedClient.java:110)
```
- 线程状态是 Blocked阻塞状态。说明线程等待资源超时
- “ waiting to lock <0x00000000acf4d0c0>”指,线程在等待给这个 0x00000000acf4d0c0 地址上锁英文可描述为trying to obtain 0x00000000acf4d0c0 lock
- 在 dump 日志里查找字符串 0x00000000acf4d0c0发现有大量线程都在等待给这个地址上锁。如果能在日志里找到谁获得了这个锁如locked < 0x00000000acf4d0c0 >),就可以顺藤摸瓜了
- “waiting for monitor entry”说明此线程通过 synchronized(obj) {……} 申请进入了临界区从而进入了下图1中的“Entry Set”队列但该 obj 对应的 monitor 被其他线程拥有,所以本线程在 Entry Set 队列中等待
- 第一行里,"RMI TCP Connection(267865)-172.16.5.25"是 Thread Name 。tid指Java Thread id。nid指native线程的id。prio是线程优先级。[0x00007fd4f8684000]是线程栈起始地址。
**场景二分析CPU过高问题**
1.top命令找出最高占用的进程Shift+P
2.查看高负载进程下的高负载线程top -Hp <PID>或ps -mp <PID> -o THREAD,tid,time
3.找出最高占用的线程并记录thread_id把线程号进行换算成16进制编号printf "%X\n" thread_id
4.可选执行查看高负载的线程名称jstack 16143 | grep 3fb6
5.导出进程的堆栈日志找到3fb6 这个线程号jstack 16143 >/home/16143.log
6.根据找到的堆栈信息关联到代码进行定位分析即可
## jmap
jmap(Java Memory Map)主要用于打印内存映射。常用命令:
`jmap -dump:live,format=b,file=xxx.hprof <pid> `
**查看JVM堆栈的使用情况**
```powershell
[root@localhost ~]# jmap -heap 7243
Attaching to process ID 27900, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 20.45-b01
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration: #堆内存初始化配置
MinHeapFreeRatio = 40 #-XX:MinHeapFreeRatio设置JVM堆最小空闲比率
MaxHeapFreeRatio = 70 #-XX:MaxHeapFreeRatio设置JVM堆最大空闲比率
MaxHeapSize = 100663296 (96.0MB) #-XX:MaxHeapSize=设置JVM堆的最大大小
NewSize = 1048576 (1.0MB) #-XX:NewSize=设置JVM堆的新生代的默认大小
MaxNewSize = 4294901760 (4095.9375MB) #-XX:MaxNewSize=设置JVM堆的新生代的最大大小
OldSize = 4194304 (4.0MB) #-XX:OldSize=设置JVM堆的老生代的大小
NewRatio = 2 #-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
SurvivorRatio = 8 #-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值
PermSize = 12582912 (12.0MB) #-XX:PermSize=<value>:设置JVM堆的持久代的初始大小
MaxPermSize = 67108864 (64.0MB) #-XX:MaxPermSize=<value>:设置JVM堆的持久代的最大大小
Heap Usage:
New Generation (Eden + 1 Survivor Space): #新生代区内存分布,包含伊甸园区+1个Survivor区
capacity = 30212096 (28.8125MB)
used = 27103784 (25.848182678222656MB)
free = 3108312 (2.9643173217773438MB)
89.71169693092462% used
Eden Space: #Eden区内存分布
capacity = 26869760 (25.625MB)
used = 26869760 (25.625MB)
free = 0 (0.0MB)
100.0% used
From Space: #其中一个Survivor区的内存分布
capacity = 3342336 (3.1875MB)
used = 234024 (0.22318267822265625MB)
free = 3108312 (2.9643173217773438MB)
7.001809512867647% used
To Space: #另一个Survivor区的内存分布
capacity = 3342336 (3.1875MB)
used = 0 (0.0MB)
free = 3342336 (3.1875MB)
0.0% used
PS Old Generation: #当前的Old区内存分布
capacity = 67108864 (64.0MB)
used = 67108816 (63.99995422363281MB)
free = 48 (4.57763671875E-5MB)
99.99992847442627% used
PS Perm Generation: #当前的 “持久代” 内存分布
capacity = 14417920 (13.75MB)
used = 14339216 (13.674942016601562MB)
free = 78704 (0.0750579833984375MB)
99.45412375710227% used
```
新生代内存回收就是采用空间换时间方式如果from区使用率一直是100% 说明程序创建大量的短生命周期的实例使用jstat统计jvm在内存回收中发生的频率耗时以及是否有full gc使用这个数据来评估一内存配置参数、gc参数是否合理。
**统计一【jmap -histo】**:统计所有类的实例数量和所占用的内存容量
```powershell
[root@localhost ~]# jmap -histo 7243
num #instances #bytes class name
----------------------------------------------
1: 8969 19781168 [B
2: 1835 2296720 [I
3: 19735 2050688 [C
4: 3448 385608 java.lang.Class
5: 3829 371456 [Ljava.lang.Object;
6: 14634 351216 java.lang.String
7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node
8: 6257 100112 java.lang.Object
9: 2155 68960 java.util.HashMap$Node
10: 723 63624 java.lang.reflect.Method
11: 49 56368 [Ljava.util.concurrent.ConcurrentHashMap$Node;
12: 830 46480 java.util.zip.ZipFile$ZipFileInputStream
13: 1146 45840 java.lang.ref.Finalizer
......
```
**统计二【jmap -histo】**查看对象数最多的对象并过滤Map关键词然后按降序排序输出
```shell
[root@localhost ~]# jmap -histo 7243 |grep Map|sort -k 2 -g -r|less
Total 96237 26875560
7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node
9: 2155 68960 java.util.HashMap$Node
18: 563 27024 java.util.HashMap
21: 505 20200 java.util.LinkedHashMap$Entry
16: 337 34880 [Ljava.util.HashMap$Node;
27: 336 16128 gnu.trove.THashMap
56: 163 6520 java.util.WeakHashMap$Entry
60: 127 6096 java.util.WeakHashMap
38: 127 10144 [Ljava.util.WeakHashMap$Entry;
53: 126 7056 java.util.LinkedHashMap
......
```
**统计三【jmap -histo】**统计实例数量最多的前10个类
```shell
[root@localhost ~]# jmap -histo 7243 | sort -n -r -k 2 | head -10
num #instances #bytes class name
----------------------------------------------
Total 96237 26875560
3: 19735 2050688 [C
6: 14634 351216 java.lang.String
1: 8969 19781168 [B
7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node
8: 6257 100112 java.lang.Object
5: 3829 371456 [Ljava.lang.Object;
4: 3448 385608 java.lang.Class
9: 2155 68960 java.util.HashMap$Node
2: 1835 2296720 [I
```
**统计四【jmap -histo】**统计合计容量最多的前10个类
```shell
[root@localhost ~]# jmap -histo 7243 | sort -n -r -k 3 | head -10
num #instances #bytes class name
----------------------------------------------
Total 96237 26875560
1: 8969 19781168 [B
2: 1835 2296720 [I
3: 19735 2050688 [C
4: 3448 385608 java.lang.Class
5: 3829 371456 [Ljava.lang.Object;
6: 14634 351216 java.lang.String
7: 6695 214240 java.util.concurrent.ConcurrentHashMap$Node
8: 6257 100112 java.lang.Object
9: 2155 68960 java.util.HashMap$Node
```
**dump注意事项**
- 在应用快要发生FGC的时候把堆数据导出来
老年代或新生代used接近100%时就表示即将发生GC也可以再JVM参数中指定触发GC的阈值。
- 查看快要发生FGC使用命令jmap -heap < pid >
- 数据导出jmap -dump:format=b,file=heap.bin < pid >
- 通过命令查看大对象jmap -histo < pid >|less
**使用总结**
- 如果程序内存不足或者频繁GC很有可能存在内存泄露情况这时候就要借助Java堆Dump查看对象的情况
- 要制作堆Dump可以直接使用jvm自带的jmap命令
- 可以先使用`jmap -heap`命令查看堆的使用情况,看一下各个堆空间的占用情况
- 使用`jmap -histo:[live]`查看堆内存中的对象的情况。如果有大量对象在持续被引用,并没有被释放掉,那就产生了内存泄露,就要结合代码,把不用的对象释放掉
- 也可以使用 `jmap -dump:format=b,file=<fileName>`命令将堆信息保存到一个文件中再借助jhat命令查看详细内容
- 在内存出现泄露、溢出或者其它前提条件下建议多dump几次内存把内存文件进行编号归档便于后续内存整理分析
- 在用cms gc的情况下执行jmap -heap有些时候会导致进程变T因此强烈建议别执行这个命令如果想获取内存目前每个区域的使用状况可通过jstat -gc或jstat -gccapacity来拿到
## jhat
jhatJVM Heap Analysis Tool命令是与jmap搭配使用用来分析jmap生成的dumpjhat内置了一个微型的HTTP/HTML服务器生成dump的分析结果后可以在浏览器中查看。在此要注意一般不会直接在服务器上进行分析因为jhat是一个耗时并且耗费硬件资源的过程一般把服务器生成的dump文件复制到本地或其他机器上进行分析。
```powershell
# 解析Java堆转储文件,并启动一个 web server
jhat heapDump.dump
```
## jconsole
jconsole(Java Monitoring and Management Console)是一个Java GUI监视工具可以以图表化的形式显示各种数据并可通过远程连接监视远程的服务器VM。用java写的GUI程序用来监控VM并可监控远程的VM非常易用而且功能非常强。命令行里打jconsole选则进程就可以了。
**第一步**在远程机的tomcat的catalina.sh中加入配置
```powershell
JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=192.168.202.121 -Dcom.sun.management.jmxremote"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.port=12345"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=true"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.ssl=false"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.pwd.file=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.101-3.b13.el7_2.x86_64/jre/lib/management/jmxremote.password"
```
**第二步**:配置权限文件
```powershell
[root@localhost bin]# cd /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.101-3.b13.el7_2.x86_64/jre/lib/management/
[root@localhost management]# cp jmxremote.password.template jmxremote.password
[root@localhost management]# vi jmxremote.password
```
monitorRole QED
controlRole chenqimiao
**第三步**配置权限文件为600
```powershell
[root@localhost management]# chmod 600 jmxremote.password jmxremote.access
```
这样基本配置就结束了下面说两个坑第一个就是防火墙的问题要开放指定端口的防火墙我这里配置的是12345端口第二个是hostname的问题
![jconsole-ip2](images/DevOps/jconsole-ip2.png)
请将127.0.0.1修改为本地真实的IP,我的服务器IP是192.168.202.121
![jconsole-ip](images/DevOps/jconsole-ip.png)
**第四步**查看JConsole
![JConsole-新建连接](images/DevOps/JConsole-新建连接.png)
![JConsole-Console](images/DevOps/JConsole-Console.png)
## jvisualvm
jvisualvm(JVM Monitoring/Troubleshooting/Profiling Tool)同jconsole都是一个基于图形化界面的、可以查看本地及远程的JAVA GUI监控工具Jvisualvm同jconsole的使用方式一样直接在命令行打入Jvisualvm即可启动不过Jvisualvm相比界面更美观一些数据更实时。 jvisualvm的使用VisualVM进行远程连接的配置和JConsole是一摸一样的最终效果图如下
![jvisualvm](images/DevOps/jvisualvm.png)
**Visual GC(监控垃圾回收器)**
Java VisualVM默认没有安装Visual GC插件需要手动安装JDK的安装目录的bin目露下双击 jvisualvm.sh即可打开Java VisualVM点击菜单栏 **工具->插件** 安装Visual GC最终效果如下图所示
![Visual-GC](images/DevOps/Visual-GC.png)
**大dump文件**
从服务器dump堆内存后文件比较大5.5G左右加载文件、查看实例对象都很慢还提示配置xmx大小。表明给VisualVM分配的堆内存不够找到$JAVA_HOME/lib/visualvm}/etc/visualvm.conf这个文件修改
```shell
default_options="-J-Xms24m -J-Xmx192m"
```
再重启VisualVM就行了。
## jmc
jmcJava Mission Control是JDK自带的一个图形界面监控工具监控信息非常全面。JMC打开性能日志后主要包括**一般信息、内存、代码、线程、I/O、系统、事件** 功能。
![jmc-main](images/DevOps/jmc-main.jpg)
JMC的最主要的特征就是JFRJava Flight Recorder是基于JAVA的飞行记录器JFR的数据是一些列JVM事件的历史纪录可以用来诊断JVM的性能和操作收集后的数据可以使用JMC来分析。
### 启动JFR
在商业版本上面JFR默认是关闭的可以通过在启动时增加参数 `-XX:+UnlockCommercialFeatures -XX:+FlightRecorder` 来启动应用。启动之后也只是开启了JFR特性但是还没有开始进行事件记录。这就要通过GUI和命令行了。
- **通过Java Mission Control启动JFR**
打开Java Mission Control点击对应的JVM启动即可事件记录有两种模式如果选择第2种模式那么JVM会使用一个循环使用的缓存来存放事件数据
- 记录固定一段时间的事件比如1分钟
- 持续进行记录
- **通过命令行启动JFR**
通过在启动的时候,增加参数:`-XX:+FlightRecorderOptions=string` 来启动真正地事件记录,这里的 `string` 可以是以下值下列参数都可以使用jcmd命令在JVM运行的时候进行动态调整[参考地址](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html)
- `name=name`标识recording的名字一个进程可以有多个recording存在它们使用名字来进行区分
- `defaultrecording=<ture|false>`是否启动recording默认是false我们要分析必须要设置为true
- `setting=paths`包含JFR配置的文件名字
- `delay=time`启动之后经过多长时间比如30s1h开始进行recording
- `duration=time`做多长时间的recording
- `filename=path`recordding记录到那个文件里面
- `compress=<ture|false>`是否对recording进行压缩gzip,默认为false
- `maxage=time`:在循环使用的缓存中,事件数据保存的最大时长
- `maxsize=size`事件数据缓存的最大大小比如1024k1M
### 常用JFR命令
- 启动recording
命令格式:`jcmd process_id JFR.start [options_list]`其中options_list就是上述的参数值。
- dump出循环缓存中的数据
命令格式:`jcmd process_id JFR.dump [options_list]`其中options_list参数的可选值如下
- `name=name`recording的名字
- `recording=n`JFR recording的数字一个标识recording的随机数
- `filename=path`dump文件的保存路径
- 查看进程中所有recording
命令格式:` jcmd process_id JFR.check [verbose]`不同recording使用名字进行区分同时JVM还为它分配一个随机数。
- 停止recording
命令格式:` jcmd process_id JFR.stop [options_list]`其中options_list参数的可选值如下
- `name=name`要停止的recording名字
- `recording=n`要停止的recording的标识数字
- `discard=boolean`如果为true数据被丢弃而不是写入下面指定的文件当中
- `filename=path`:写入数据的文件名称
### 命令启动JFR案例
- **第一步**创建一个包含了你自己配置的JFR模板文件`template.jfc`。运行jmc, 然后Window->Flight Recording Template Manage菜单。准备好档案后就可以导出文件并移动到要排查问题的环境中
![jmc-jfc](images/DevOps/jmc-jfc.png)
- **第二步**由于JFR需要JDK的商业证书这一步需要解锁jdk的商业特性
```powershell
[root@localhost bin]# jcmd 12234 VM.unlock_commercial_features
12234: Commercial Features already unlocked.
```
- **第三步**最后你就可以启动JFR命令格式如下
```powershell
jcmd <PID> JFR.start name=test duration=60s [settings=template.jfc] filename=output.jfr
```
上述命令立即启动JFR并开始使用 `template.jfc`(在 `$JAVA_HOME/jre/lib/jfr` 下有 `default.jfc``profile.jfc` 模板)的配置收集 `60s` 的JVM信息输出到 `output.jfr` 中。一旦记录完成之后,就可以复制.jfr文件到你的工作环境使用jmc GUI来分析。它几乎包含了排查jvm问题需要的所有信息包括堆dump时的异常信息。使用案例如下
```powershell
[root@localhost bin]# jcmd 12234 JFR.start name=test duration=60s filename=output.jfr
12234: Started recording 6. The result will be written to: /root/zookeeper-3.4.12/bin/output.jfr
[root@localhost bin]# ls -l
-rw-r--r-- 1 root root 298585 6月 29 11:09 output.jfr
```
**JFRJava Flight Recorder**
- Java Mission Control的最主要的特征就是Java Flight Recorder。正如它的名字所示JFR的数据是一些列JVM事件的历史纪录可以用来诊断JVM的性能和操作
- JFR的基本操作就是开启哪些事件比如线程由于等待锁而阻塞的事件。当开启的事件发生了事件相关的数据会记录到内存或磁盘文件上。记录事件数据的缓存是循环使用的只有最近发生的事件才能够从缓存中找到之前的都因为缓存的限制被删除了。Java Mission Control能够对这些事件在界面上进行展示从JVM的内存中读取或从事件数据文件中读取我们可以通过这些事件来对JVM的性能进行诊断
- 事件的类型、缓存的大小、事件数据的存储方式等等都是通过JVM参数、Java Mission Control的GUI界面、jcmd命令来控制的。JFR默认是编译进程序的因为它的开销很小一般来说对应用的影响小于1%。不过如果我们增加了事件的数目、修改了记录事件的阈值都有可能增加JFR的开销
### JFR概况
下面对GlassFish web服务器进行JFR记录的例子在这个服务器上面运行着在第2章介绍的股票servlet。Java Mission Control加载完JFR获取的事件之后大概是下面这个样子
![jfr-main](images/DevOps/jfr-main.jpg)
我们可以看到通过上图可以看到CPU使用率Heap使用率JVM信息System PropertiesJFR的记录情况等等。
### JFR内存视图
Java Mission Control 可以看到非常多的信息下图只显示了一个标签的内容。下图显示了JVM 的内存波动非常频繁因为新生代经常被清除有意思的是head的大小并没有增长。下面左边的面板显示了最近一段时间的垃圾回收情况包括GC的时长和垃圾回收的类型。如果我们点击一个事件右边的面板会展示这个事件的具体情况包括垃圾垃圾回收的各个阶段及其统计信息。从面板的标签可以看到还有很多其它信息比如有多少对象被清除了花了多长时间GC算法的配置分配的对象信息等等。在第5章和第6章中我们会详细介绍。
![jfr-memory](images/DevOps/jfr-memory.jpg)
### JFR 代码视图
这张图也有很多tab可以看到各个包的使用频率和类的使用情况、异常、编译、代码缓存、类加载情况等等
![jfr-code](images/DevOps/jfr-code.jpg)
### JFR事件视图
下图显示了事件的概述视图:
![jfr-event](images/DevOps/jfr-event.jpg)
## EclipseMAT
虽然Java虚拟机可以帮我们对内存进行回收但是其回收的是Java虚拟机不再引用的对象。很多时候我们使用系统的IO流、Cursor、Receiver如果不及时释放就会导致内存泄漏OOM。但是很多时候内存泄漏的现象不是很明显比如内部类、Handler相关的使用导致的内存泄漏或者你使用了第三方library的一些引用比较消耗资源但又不是像系统资源那样会引起你足够的注意去手动释放它们。以下通过内存泄漏分析、集合使用率、Hash性能分析和OQL快读定位空集合来使用MAT。
**GC Roots**
JAVA虚拟机通过可达性Reachability)来判断对象是否存活,基本思想:`以”GC Roots”的对象作为起始点向下搜索搜索形成的路径称为引用链当一个对象到GC Roots没有任何引用链相连即不可达的则该对象被判定为可以被回收的对象反之不能被回收`。GC Roots可以是以下任意对象
- 一个在current thread当前线程的call stack调用栈上的对象如方法参数和局部变量
- 线程自身或者system class loader系统类加载器加载的类
- native code本地代码保留的活动对象
**内存泄漏**
当对象无用了,但仍然可达(未释放),垃圾回收器无法回收。
**Java四种引用类型**
- Strong References强引用
普通的java引用我们通常new的对象就是`StringBuffer buffer = new StringBuffer();` 如果一个对象通过一串强引用链可达,那么它就不会被垃圾回收。你肯定不希望自己正在使用的引用被垃圾回收器回收吧。但对于集合中的对象,应在不使用的时候移除掉,否则会占用更多的内存,导致内存泄漏。
- Soft Reference软引用
当对象是Soft Reference可达时gc会向系统申请更多内存而不是直接回收它当内存不足的时候才回收它。因此Soft Reference适合用于构建一些缓存系统比如图片缓存。
- Weak Reference弱引用
WeakReference不会强制对象保存在内存中。它拥有比较短暂的生命周期允许你使用垃圾回收器的能力去权衡一个对象的可达性。在垃圾回收器扫描它所管辖的内存区域过程中一旦gc发现对象是Weak Reference可达就会把它放到 `Reference Queue`等下次gc时回收它。
系统为我们提供了WeakHashMap和HashMap类似只是其key使用了weak reference。如果WeakHashMap的某个key被垃圾回收器回收那么entity也会自动被remove。由于WeakReference被GC回收的可能性较大因此在使用它之前你需要通过weakObj.get()去判断目的对象引用是否已经被回收。一旦WeakReference.get()返回null它指向的对象就会被垃圾回收那么WeakReference对象就没有用了意味着你应该进行一些清理。比如在WeakHashMap中要把回收过的key从Map中删除掉避免无用的的weakReference不断增长。
ReferenceQueue可以让你很容易地跟踪dead references。WeakReference类的构造函数有一个ReferenceQueue参数当指向的对象被垃圾回收时会把WeakReference对象放到ReferenceQueue中。这样遍历ReferenceQueue可以得到所有回收过的WeakReference。
- Phantom Reference虚引用
其余Soft/Weak Reference区别较大是它的get()方法总是返回null。这意味着你只能用Phantom Reference本身而得不到它指向的对象。当Weak Reference指向的对象变得弱可达(weakly reachable时会立即被放到ReferenceQueue中这在finalization、garbage collection之前发生。理论上你可以在finalize()方法中使对象“复活”使一个强引用指向它就行了gc不会回收它。但没法复活PhantomReference指向的对象。而PhantomReference是在garbage collection之后被放到ReferenceQueue中的没法复活。
**MAT视图与概念**
- **Shallow Heap**
Shallow Size就是对象本身占用内存的大小不包含其引用的对象内存实际分析中作用不大。 常规对象非数组的Shallow Size由其成员变量的数量和类型决定。数组的Shallow Size有数组元素的类型对象类型、基本类型和数组长度决定。案例如下
```java
public class String {
public final class String {8 Bytes header
private char value[]; 4 Bytes
private int offset; 4 Bytes
private int count; 4 Bytes
private int hash = 0; 4 Bytes
// ......
}
// "Shallow size“ of a String ==24 Bytes12345678
```
Java的对象成员都是些引用。真正的内存都在堆上看起来是一堆原生的byte[]、char[]、int[]对象本身的内存都很小。所以我们可以看到以Shallow Heap进行排序的Histogram图中排在第一位第二位的是byte和char。
- **Retained Heap**
Retained Heap值的计算方式是将Retained Set中的所有对象大小叠加。或者说由于X被释放导致其它所有被释放对象包括被递归释放的所占的Heap大小。当X被回收时哪些将被GC回收的对象集合。比如:
一个ArrayList持有100000个对象每一个占用16 bytes移除这些ArrayList可以释放16×100000+XX代表ArrayList的Shallow大小。相对于Shallow HeapRetained Heap可以更精确的反映一个对象实际占用的大小因为如果该对象释放Retained Heap都可以被释放
- **Histogram**
可列出每一个类的实例数。支持正则表达式查找也可以计算出该类所有对象的Retained Size。
![mat-histogram](images/DevOps/mat-histogram.jpg)
- **Dominator Tree**
对象之间dominator关系树。如果从GC Root到达Y的的所有path都经过X那么我们称X dominates Y或者X是Y的Dominator Dominator Tree由系统中复杂的对象图计算而来。从MAT的dominator tree中可以看到占用内存最大的对象以及每个对象的dominator。 我们也可以右键选择Immediate Dominator”来查看某个对象的dominator。
![mat-dominator-tree](images/DevOps/mat-dominator-tree.jpg)
- **Path to GC Roots**
查看一个对象到RC Roots的引用链通常在排查内存泄漏的时候我们会选择exclude all phantom/weak/soft etc.references,
意思是查看排除虚引用/弱引用/软引用等的引用链,因为被虚引用/弱引用/软引用的对象可以直接被GC给回收我们要看的就是某个对象否还存在Strong 引用链在导出HeapDump之前要手动出发GC来保证如果有则说明存在内存泄漏然后再去排查具体引用。
![mat-path-to-gc-roots](images/DevOps/mat-path-to-gc-roots.jpg)
查看当前Object所有引用,被引用的对象:
- List objects with 以Dominator Tree的方式查看
- incoming references 引用到该对象的对象
- outcoming references 被该对象引用的对象
- Show objects by class 以class的方式查看
- incoming references 引用到该对象的对象
- outcoming references 被该对象引用的对象
- **OQL(Object Query Language)**
类似SQL查询语言ClassesTable、ObjectsRows、FiledsCols
```mysql
select * from com.example.mat.Listener
# 查找size0并且未使用过的ArrayList
select * from java.util.ArrayList where size=0 and modCount=01
# 查找所有的Activity
select * from instanceof android.app.Activity
```
- **内存快照对比**
方式一Compare To Another Heap Dump直接进行比较
![mat-compare-to-another-heap-dump-1](images/DevOps/mat-compare-to-another-heap-dump-1.jpg)
![mat-compare-to-another-heap-dump-2](images/DevOps/mat-compare-to-another-heap-dump-2.jpg)
![mat-compare-to-another-heap-dump-3](images/DevOps/mat-compare-to-another-heap-dump-3.jpg)
方式二Compare Baseket更全面可以直接给出百分比
![mat-compare-baseket-1](images/DevOps/mat-compare-baseket-1.jpg)
![mat-compare-baseket-2](images/DevOps/mat-compare-baseket-2.jpg)
![mat-compare-baseket-3](images/DevOps/mat-compare-baseket-3.jpg)
![mat-compare-baseket-4](images/DevOps/mat-compare-baseket-4.jpg)
**MAT内存分析实战**
- **实战一:内存泄漏分析**
查找导致内存泄漏的类。既然环境已经搭好heap dump也成功倒入接下来就去分析问题。
- 查找目标类
如果在开发过程中你的目标很明确比如就是查找自己负责的服务那么通过包名或者Class筛选OQL搜索都可以快速定位到。点击OQL图标在窗口输入并按Ctrl + F5或者!按钮执行:
`select * from instanceof android.app.Activity`
- Paths to GC Rootsexclude all phantom/weak/soft etc.references
查看一个对象到RC Roots是否存在引用链。要将虚引用/弱引用/软引用等排除,因为被虚引用/弱引用/软引用的对象可以直接被GC给回收
- 分析具体的引用为何没有被释放,并进行修复
**小技巧:**
- 当目的不明确时可以直接定位到RetainedHeap最大的ObjectSelect incoming references查看引用链定位到可疑的对象然后Path to GC Roots进行引用链分析
- 如果大对象筛选看不出区别可以试试按照class分组再寻找可疑对象进行GC引用链分析
- 直接按照包名直接查看GC引用链可以一次性筛选多个类但是如下图所示选项是 Merge Shortest Path to GCRoots这个选项具体不是很明白不过也能筛选出存在GC引用链的类这种方式的准确性还待验证
![mat-实践一](images/DevOps/mat-实践一.jpg)
所以有时候进行MAT分析还是需要一些经验能够帮你更快更准确的定位。
- **实战二:集合使用率分析**
集合在开发中会经常使用到如何选择合适的数据结构的集合初始容量是多少太小可能导致频繁扩容太大又会开销跟多内存。当这些问题不是很明确时或者想查看集合的使用情况时可以通过MAT来进行分析。
- **筛选目标对象**
![mat-实践二-1](images/DevOps/mat-实践二-1.jpg)
- **Show Retained Set查找当X被回收时那些将被GC回收的对象集合**
![mat-实践二-2](images/DevOps/mat-实践二-2.jpg)
- **筛选指定的ObjectHash MapArrayList并按照大小进行分组**
![mat-实践二-3](images/DevOps/mat-实践二-3.jpg)
- **查看指定类的Immediate dominators**
![mat-实践二-4](images/DevOps/mat-实践二-4.jpg)
**Collections fill ratio**
这种方式只能查看那些具有预分配内存能力的集合比如HashMapArrayList。计算方式”size / capacity”
![mat-实践二-5](images/DevOps/mat-实践二-5.jpg)
![mat-实践二-6](images/DevOps/mat-实践二-6.jpg)
- **实战三Hash相关性能分析**
当Hash集合中过多的对象返回相同Hash值的时候会严重影响性能Hash算法原理自行搜索这里来查找导致Hash集合的碰撞率较高的罪魁祸首。
- **Map Collision Ratio**
检测每一个HashMap或者HashTable实例并按照碰撞率排序**碰撞率 = 碰撞的实体/Hash表中所有实体**
![mat-实践三-1](images/DevOps/mat-实践三-1.jpg)
- **查看Immediate dominators**
![mat-实践三-2](images/DevOps/mat-实践三-2.jpg)
![mat-实践三-3](images/DevOps/mat-实践三-3.jpg)
- **通过HashEntries查看key value**
![mat-实践三-4](images/DevOps/mat-实践三-4.jpg)
- **Array等其它集合分析方法类似**
- **实战四通过OQL快速定位未使用的集合**
- **通过OQL查询empty并且未修改过的集合**
```mysql
select * from java.util.ArrayList where size=0 and modCount=01
select * from java.util.HashMap where size=0 and modCount=0
select * from java.util.Hashtable where count=0 and modCount=012
```
![mat-实践四-1](images/DevOps/mat-实践四-1.jpg)
- **Immediate dominators(查看引用者)**
![mat-实践四-2](images/DevOps/mat-实践四-2.jpg)
- **计算空集合的Retained Size值查看浪费了多少内存**
![mat-实践四-3](images/DevOps/mat-实践四-3.jpg)
## 🔥火焰图
火焰图是用来分析程序运行瓶颈的工具。火焰图也可以用来分析 Java 应用。
### 环境安装
确认你的机器已经安装了**git、jdk、perl、c++编译器**。
#### 安装Perl
```shell
wget http://www.cpan.org/src/5.0/perl-5.26.1.tar.gz
tar zxvf perl-5.26.1.tar.gz
cd perl-5.26.1
./Configure -de
make
make test
make install
```
wget后面的路径可以按需更改。安装过程比较耗时间安装完成后可通过**perl -version**查看是否安装成功。
#### C++编译器
```shell
apt-get install g++
```
一般用于编译c++程序缺少这个编译器进行make编译c++代码时会报“g++: not found”的错误。
#### clone相关项目
下载下来所需要的两个项目这里建议放到data目录下
```shell
git clone https://github.com/jvm-profiling-tools/async-profiler
git clone https://github.com/brendangregg/FlameGraph
```
#### 编译项目
下载好以后需要打开async-profiler文件输入make指令进行编译
```shell
cd async-profiler
make
```
### 生成文件
#### 生成火焰图数据
可以从 github 上下载 [async-profiler](https://github.com/jvm-profiling-tools/async-profiler) 的压缩包进行相关操作。进入async-profiler项目的目录下然后输入如下指令
```shell
./profiler.sh -d 60 -o collapsed -f /tmp/test_01.txt ${pid}
```
上面的-d表示的是持续时长后面60代表持续采集时间60s-o表示的是采集规范这里用的是collapsed-f后面的路径表示的是数据采集后生成的数据存放的文件路径这里放在了/tmp/test_01.txt${pid}表示的是采集目标进程的pid回车运行运行期间阻塞知道约定时间完成。运行完成后去tmp下看看有没有对应文件。
#### 生成svg文件
上一步产生的文件里的内容,肉眼是很难看懂的,所以现在[FlameGraph](https://github.com/brendangregg/FlameGraph)的作用就体现出来了,它可以读取该文件并生成直观的火焰图,现在进入该项目目录下面,执行如下语句:
```shell
perl flamegraph.pl --colors=java /tmp/test_01.txt > test_01.svg
```
因为是perl文件这里使用perl指令运行该文件后面--colors表示着色风格这里是java后面的是数据文件的路径这里是刚刚上面生成的那个文件/tmp/test_01.txt最后的test_01.svg就是最终生成的火焰图文件存放的路径和文件命名这里是命名为test_01.svg并保存在当前路径运行后看到该文件已经存在于当前目录下。
#### 展示火焰图
现在下载下来该文件,使用浏览器打开,效果如下:
![火焰图案例](images/DevOps/火焰图案例.svg)
### 火焰图案例
生成的[火焰图案例](images/DevOps/火焰图案例.svg)如下:
![火焰图案例](images/DevOps/火焰图案例.jpg)
#### 瓶颈点1
CoohuaAnalytics$KafkaConsumer:::send方法中Gzip压缩占比较高。已经定位到方法级别再看代码就快速很多直接找到具体位置找到第一个消耗大户**Gzip压缩**
![火焰图-瓶颈点1](images/DevOps/火焰图-瓶颈点1.jpg)
#### 瓶颈点2
展开2这个波峰查看到这个getOurStackTrace方法占用了大比例的CPU怀疑代码里面频繁用丢异常的方式获取当前代码栈
![火焰图-瓶颈点2](images/DevOps/火焰图-瓶颈点2.jpg)
直接看代码:
![火焰图-瓶颈点2-代码](images/DevOps/火焰图-瓶颈点2-代码.jpg)
果然如推断找到第二个CPU消耗大户new Exception().getStackTrace()。
#### 瓶颈点3
展开波峰3可以看到是这个Gzip解压缩
![火焰图-瓶颈点3](images/DevOps/火焰图-瓶颈点3.jpg)
定位到具体的代码可以看到对每个请求的参数进行了gzip解压缩
![火焰图-瓶颈点3-代码](images/DevOps/火焰图-瓶颈点3-代码.jpg)
# Linux Command

1369
JAVA.md

File diff suppressed because it is too large Load Diff

2342
JVM.md

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 250 KiB

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before

Width:  |  Height:  |  Size: 229 KiB

After

Width:  |  Height:  |  Size: 229 KiB

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Before

Width:  |  Height:  |  Size: 387 KiB

After

Width:  |  Height:  |  Size: 387 KiB

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Before

Width:  |  Height:  |  Size: 311 KiB

After

Width:  |  Height:  |  Size: 311 KiB

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Before

Width:  |  Height:  |  Size: 253 KiB

After

Width:  |  Height:  |  Size: 253 KiB

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 186 KiB

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 186 KiB

Before

Width:  |  Height:  |  Size: 303 KiB

After

Width:  |  Height:  |  Size: 303 KiB

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before

Width:  |  Height:  |  Size: 416 KiB

After

Width:  |  Height:  |  Size: 416 KiB

Before

Width:  |  Height:  |  Size: 662 KiB

After

Width:  |  Height:  |  Size: 662 KiB

Before

Width:  |  Height:  |  Size: 468 KiB

After

Width:  |  Height:  |  Size: 468 KiB

Before

Width:  |  Height:  |  Size: 553 KiB

After

Width:  |  Height:  |  Size: 553 KiB

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Before

Width:  |  Height:  |  Size: 488 KiB

After

Width:  |  Height:  |  Size: 488 KiB

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Before

Width:  |  Height:  |  Size: 270 KiB

After

Width:  |  Height:  |  Size: 270 KiB

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Before

Width:  |  Height:  |  Size: 407 KiB

After

Width:  |  Height:  |  Size: 407 KiB

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 244 KiB

Before

Width:  |  Height:  |  Size: 253 KiB

After

Width:  |  Height:  |  Size: 253 KiB

Before

Width:  |  Height:  |  Size: 339 KiB

After

Width:  |  Height:  |  Size: 339 KiB

Before

Width:  |  Height:  |  Size: 424 KiB

After

Width:  |  Height:  |  Size: 424 KiB

Before

Width:  |  Height:  |  Size: 335 KiB

After

Width:  |  Height:  |  Size: 335 KiB

Before

Width:  |  Height:  |  Size: 415 KiB

After

Width:  |  Height:  |  Size: 415 KiB

Before

Width:  |  Height:  |  Size: 324 KiB

After

Width:  |  Height:  |  Size: 324 KiB

Before

Width:  |  Height:  |  Size: 318 KiB

After

Width:  |  Height:  |  Size: 318 KiB

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 274 KiB

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 282 KiB

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 266 KiB

Before

Width:  |  Height:  |  Size: 301 KiB

After

Width:  |  Height:  |  Size: 301 KiB

Before

Width:  |  Height:  |  Size: 343 KiB

After

Width:  |  Height:  |  Size: 343 KiB

Before

Width:  |  Height:  |  Size: 259 KiB

After

Width:  |  Height:  |  Size: 259 KiB

Before

Width:  |  Height:  |  Size: 323 KiB

After

Width:  |  Height:  |  Size: 323 KiB

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Before

Width:  |  Height:  |  Size: 349 KiB

After

Width:  |  Height:  |  Size: 349 KiB

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before

Width:  |  Height:  |  Size: 616 KiB

After

Width:  |  Height:  |  Size: 616 KiB

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 184 KiB

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Before

Width:  |  Height:  |  Size: 934 KiB

After

Width:  |  Height:  |  Size: 934 KiB

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Loading…
Cancel
Save