- 921. Minimum Add to Make Parentheses Valid 使括号有效的最少添加
- 915. Partition Array into Disjoint Intervals 分割数组
- 932. Beautiful Array 漂亮数组
- 940. Distinct Subsequences II 不同的子序列 II
一般来说,中大型公司都会有自己的应用监控系统,比如开源的 Zabbix、Open-Falcon、Prometheus等,也可能一些公司自己实现了监控或者告警系统;这些系统可以监控所有在线上的各种应用的运行情况,一旦发生异常(比如CPU利用率过高、FullGC频繁等)会直接通过短信、邮箱或者IM工具发送告警给管理员。
但是对于开发人员来说,并不应该依赖这些监控系统;熟练的使用各种命令行工具,在命令行中就能实现问题的发现定位及解决,是一个优秀工程师的必备技能。
所以我们需要掌握一些简单易用,且高效实用的命令行jvm监控工具,来让我们日常的开发和解决问题更加高效。
jstat 就是JDK中自动的一个非常有用的命令,它可以展示出当前运行系统的JVM的Eden、Survivor、老年代等的内存使用情况,还能展示出Young CG、Full GC等的执行情况及耗时。
通过这些指标,我们就可以轻松的分析出当前系统的运行情况、GC情况、内存分配是否合理等。
在服务器上执行jps(不懂的自行百度)命令,就可以看到当前服务器中正在运行的java进程,每个进程前面会有一个进程ID,也就是这里的PID。
然后使用这个PID来执行 jstat -gc PID
命令:
jstat -gc PID
: 只能得到当前系统运行情况的一行指标;
jstat -gc PID 1000 10
:每1000毫秒(1秒)执行一次,共执行10次;
jstat -gc PID 1000
:每秒执行一次,一直不停的执行;
示例:
到这里,如果JVM基础好一点的同学,你应该能够发现:根据这些指标,我们就能完成JVM的各项监控了;
这个命令也是 最完整、最常用和最实用的jstat的命令。
这些命令一看也就知道,是分别针对单独的某个区域进行分析和统计的,可以都自己使用看看。
首先用一段代码来进行GC的模拟:
/**
* jvm options:
* -Xms20m -Xmx20m -Xmn5m -XX:SurvivorRatio=8 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
*/
public class JstatDemo {
public static void main(String[] args) throws InterruptedException {
TimeUnit.SECONDS.sleep(15); // 便于等待执行jps, jstat命令...
for (int i = 0; i < 10; i++) {
call();
}
}
private static void call() throws InterruptedException {
List<byte[]> list = new ArrayList<>();
for (int j = 0; j < 4; j++) {
list.add(new byte[1024 * 1024]);
TimeUnit.SECONDS.sleep(1);
}
}
}
注意代码上面的jvm参数:堆空间20m,新生代5m(按比例Eden区4m),老年代15m。
按照预想,代码的执行情况应该为:
在call()方法的for循环中,每秒生成1m对象,由于Eden区是4m,则在生成第4m对象的时候,应该发生 Young GC;(则每第4秒发生一次YGC)
并且使用了list引用生成的对象,使得它们此时并不是垃圾对象,不会直接被GC掉;
而Survivor区只有0.5m,肯定是不够存放Young GC后存活的3M对象的,所以此时这3M对象会通过分配担保进入到老年代;
老年代的大小为15m,但是空间分配担保有特定的比较规则(不知道的可以看看前面的文章),所以不会等到老年代真正快要满的时候才发生 Full GC;有可能在使用10m左右,甚至一半的时候就发生Full GC;(发生Full GC时,list都成为了垃圾对象,可以被回收)
理想情况下,假设按照10m发生Full GC计算,,每3秒会有3M对象进入老年代,则在第四次进入老年代的时候,老年代空间不够,发生Full GC;此时应该是第15秒(从第一次进入老年代开始计算,此时新生代中已经消耗了3秒);
后面则应该每12秒,老年代发生一次Full GC;
当然这些都是很理想的情况,实际中会受机器性能,JDK版本等多种因素影响,导致执行情况跟理想情况有差别。
执行情况:
第3行:代码正式开始执行;
可以看到EU列也就是Eden区是以每秒增大1M的速率进行分配的;
第6行:EU列由 3498变成了1024;
就是上面说的Eden区大小为4m,经过3秒后使用了3m,再继续时放不下了,发生YGC;再看YGC列由0变为了1;
并且OU列由 0变成了3074,则是通过分配担保进入到老年代的3M对象;
第9行:第2个3秒,再次发生YGC,YGC列由1变成了2;
OU列由 3074变成了5407;(这里没有按我们预想的进行晋升,预想的这里该又晋升3m,不过不影响jstat的分析)
第18行:第5个3秒,再次发生YGC,YGC列由4变成了5;
OU列由6431变成了3346,也就是发生了Full GC,回收了老年代的对象;(这里也没有按我们预想的10m左右才回收,不过不影响jstat的分析)
FGC列由0变成了2,这里也是直接就发生了2次 FGC…
第31行:第9个3秒,再次发生FGC,FGC列由2变成了4;
这里也看到了,真正的执行情况跟逻辑分析的情况有不小的出入。
一个运行在线上的应用系统,对于JVM方面我们需要关注的是它的什么呢?我觉得是以下几类指标:
1、 对象的增长速率(每秒或者每分钟生成多大内存的对象);
2、 YoungGC的发生频率和耗时;
3、 每次YoungGC之后,有多少对象能够存活;
4、 每次YoungGC之后,有多少对象进入了老年代;
5、 老年代对象增长的速率;
6、 FullGC的发生频率和耗时;
那我们如何运用 jstat这个命令来得到这些指标呢?
1、 对象的增长速率:;
解释:这个指标,也是我们平时对于jvm第一个要了解的东西,也就是随着系统运行,每秒会在新生代的Eden区分配多少对象;
测算:执行jstat -gc PID 1000 10
命令;
解释:其实在知道了对象的增长速率的情况下,根据你设定的Eden区内存大小,可以很容易推算出来多久发生一次Young GC;
测算:当然我们也可以通过 执行jstat -gc PID 1000
命令进行真实测算;
解释:对于每次Young GC后有多少对象能够存活,我们是没法直接看出来的,但是可以进行推算;
测算:那我们此时执行 jstat -gc PID 80000 10
,每80秒执行一次,连续执行10次;
解释:跟Young GC的发生频率和耗时一样,在知道了老年代对象的增长速率的情况下,根据你设定的老年代内存大小,也可以很容易推算出多久发生一次Full GC;
对于这些指标的获取与分析,都可以使用 jstat这个命令实现;并且结合jvm的运行原理,可以比较容易的掌握到线上系统的jvm的运行情况,也可以对于jvm的具体运行情况进行针对性的优化。
我是一名优秀的程序员,十分优秀!