jvm 命令
Old区大小等于 -Xmx 减去 -Xmn
jps 查到pid后
jmap -heap pid 可以获取当前内存的使用状态和最大限制信息
jmap -dump:live,format=b,file=filename.bin 2219
jstat -gccapacity 2219
S0 — Heap上的 Survivor space 0 区已使用空间的百分比
S1 — Heap上的 Survivor space 1 区已使用空间的百分比
E — Heap上的 Eden space 区已使用空间的百分比
O — Heap上的 Old space 区已使用空间的百分比
P — Perm space 区已使用空间的百分比
YGC — 从应用程序启动到采样时发生 Young GC 的次数
YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒)
FGC — 从应用程序启动到采样时发生 Full GC 的次数
FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒)
GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)
CMS,全称Concurrent Low Pause Collector,是jdk1.4后期版本开始引入的新gc算法,在jdk5和jdk6中得到了进一步改进,它的主要适合场景是对响应时间的重要性需求大于对吞吐量的要求,能够承受垃圾回收线程和应用线程共享处理器资源,并且应用中存在比较多的长生命周期的对象的应用。CMS是用于对tenured generation的回收,也就是年老代的回收,目标是尽量减少应用的暂停时间,减少full gc发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代。在我们的应用中,因为有缓存的存在,并且对于响应时间也有比较高的要求,因此希望能尝试使用CMS来替代默认的server型JVM使用的并行收集器,以便获得更短的垃圾回收的暂停时间,提高程序的响应性。
CMS并非没有暂停,而是用两次短暂停来替代串行标记整理算法的长暂停,它的收集周期是这样:
初始标记(CMS-initial-mark) -> 并发标记(CMS-concurrent-mark) -> 重新标记(CMS-remark) -> 并发清除(CMS-concurrent-sweep) ->并发重设状态等待下次CMS的触发(CMS-concurrent-reset)。
其中的1,3两个步骤需要暂停所有的应用程序线程的。第一次暂停从root对象开始标记存活的对象,这个阶段称为初始标记;第二次暂停是在并发标记之后,暂停所有应用程序线程,重新标记并发标记阶段遗漏的对象(在并发标记阶段结束后对象状态的更新导致)。第一次暂停会比较短,第二次暂停通常会比较长,并且 remark这个阶段可以并行标记。
而并发标记、并发清除、并发重设阶段的所谓并发,是指一个或者多个垃圾回收线程和应用程序线程并发地运行,垃圾回收线程不会暂停应用程序的执行,如果你有多于一个处理器,那么并发收集线程将与应用线程在不同的处理器上运行,显然,这样的开销就是会降低应用的吞吐量。Remark阶段的并行,是指暂停了所有应用程序后,启动一定数
弱依赖“并发请求数阀值”这个值设置多少合适?
“并发请求数阀值”在大部分情况下可以理解为同时工作的线程数阀值,这个值不是越大越好,也不是越小越好,而是在最高QPS输出的情况下这个值越小越好。这个也是系统性能优化的一个方向,高QPS,少线程。
线程它是驱动业务逻辑执行的载体,在执行逻辑的生命周期中,它会申请各种各样的资源,会占有CPU,会申请内存对象并持有,会申请锁并持有,会申请IO资源并持有,然后再完成一些工作之后,在恰当的时间释放这些资源。如果这个生命周期越长,那么直接导致这些资源被持有的时间越长。导致资源的竞争更加厉害。在java应用中,一个明显的场景就是由于内存申请之后长时间不能释放,导致FULLgC非常频繁。
另一个角度,因为响应时间变长之后,会直接导致需要更多的线程数来满足不变的QPS,由于线程越多,导致对资源的持有和竞争更加激烈,表现CPU占用很高,Load很高,这是一个恶性循环,需要打破这个环。
以cache访问为例
假设客户端app(相对cache而言)本身的输入QPS为200(平均每秒有100个用户请求到此系统),每次业务请求会调用2次Cache,相当于对cache请求的QPS是200*2=400,也就是说如果要满足客户端app 200的qps,那么需要客户端app对Cache有400qps的访问能力,假设客户端app访问cache的平均响应时间为1ms。
用户–1次请求–>[APP]–2次请求–>[Cache]
根据公式:QPS=1000ms/RT(平均响应时间) threadNum(并行线程数)
threadNum=QPS/1000 RT = 400/1000 1
= 1
Cache响应时间为1ms,那么只需要一个线程就足以提供每秒400次的请求服务,其实理论上1个线程可以提供1000的qps,当然前提条件是cacheServer有非常高的吞吐容量
我们模拟一下故障
此时CacheServer发生了故障,对CacheServer的调用全部超时,响应时间变成了3000ms
根据公式计算:threadNum=QPS/1000 RT = 400/1000 * 3000 =1200
此时需要1200个线程才能满足400QPS的流量 ,换而言之就是客户端会有1200个线程堵塞在CacheServer的访问请求上。这个线程数量早就超过了客户端配置的线程数量,app系统表现为hung住,几乎不能处理任何用户的请求,如果客户端配置的线程数是200,那么根据之前对detail,hesper等java应用系统的压测,200个app线程,会导致系统频繁的发生FullGC(原因是线程数量多,每个线程执行的时间长),此时app的实际QPS反而会比系统极限QPS低很多。
对于APP来说,此时应该是减少对cache的访问量,让少量的线程去试探是否恢复,而不是所有线程都堵塞在这里,原则上来说不管对什么资源的访问,都不能出现大量线程堵塞的情况。
最佳的做法是,限制对cache访问请求的线程数量。比如限制为10: 此时我们再来看一下上面的故障
jvisualvm
jmc
jdb
thread dump:
thread dump文件主要保存的是java应用中各线程在某一时刻的运行的位置,即执行到哪一个类的哪一个方法哪一个行上。thread dump是一个文本文件,打开后可以看到每一个线程的执行栈,以stacktrace的方式显示。通过对thread dump的分析可以得到应用是否“卡”在某一点上,即在某一点运行的时间太长,如数据库查询,长期得不到响应,最终导致系统崩溃。单个的thread dump文件一般来说是没有什么用处的,因为它只是记录了某一个绝对时间点的情况。比较有用的是,线程在一个时间段内的执行情况。
两个thread dump文件在分析时特别有效,困为它可以看出在先后两个时间点上,线程执行的位置,如果发现先后两组数据中同一线程都执行在同一位置,则说明此处可能有问题,因为程序运行是极快的,如果两次均在某一点上,说明这一点的耗时是很大的。通过对这两个文件进行分析,查出原因,进而解决问题。
获取thread dump文件
windows下执行:jstack 2576 > thread.txt
Linux下执行:./jstack 2576 > thread.txt
大量的类加载器 “sun.reflect.DelegatingClassLoader”,用来加载“sun/reflect/GeneratedMethodAccessor”类,可能导致潜在的占用大量本机内存空间问题,应用服务器进程占用的内存会显著增大。您还有可能遇到抛出的内存溢出错误。案例:假笨说-从一起GC血案谈到反射原理
- jmap -clstats PID to dump class loader statistics;
- jcmd PID GC.class_stats to print the detailed information about memory usage of each loaded class. The latter requires -XX:+UnlockDiagnosticVMOptions.
sun.reflect.DelegatingClassLoader
元空间的一些工具
- jmap -permstat改成了jmap -clstats $PID。它用来打印Java堆的类加载器的统计数据。对每一个类加载器,会输出它的名字,是否存活,地址,父类加载器,以及它已经加载的类的数量及大小。除此之外,驻留的字符串(intern)的数量及大小也会打印出来。
- jstat -gc,这个命令输出的是元空间的信息而非持久代的
- jcmd $PID GC.class_stats提供类元数据大小的详细信息。使用这个功能启动程序时需要加上-XX:+UnlockDiagnosticVMOptions选项。