问:你知道java的内存模型吗?
答:你指的是哪个版本的JDK?
本文参考自:马士兵教育 mashibing.com,在原文基础上丰富了技术细节和配图。
学东西先要学脉络! ——马士兵
GC的基础知识
1.什么是垃圾
C语言申请内存:malloc free
C++: new delete
c/C++ 手动回收内存
Java: new ?
自动内存回收,编程上简单,系统不容易出错,手动释放内存,容易出两种类型的问题:
忘记回收
多次回收
没有任何引用指向的一个对象或者多个对象(循环引用)
2.如何定位垃圾
-
引用计数(ReferenceCount),java没采用
每引用一次就加一,减少引用就减一,到0时判断为垃圾。
-
根可达算法(RootSearching),目前采用
比如root开始,将引用连成一条线,在线上的就不是垃圾,不在线上的就是垃圾。
什么可以作为root:
-
被启动类(bootstrap 加载器)加载的类和创建的对象;
-
JavaStack 中的引用的对象 (栈内存中引用的对象);
-
方法区中静态引用指向的对象;
-
方法区中常量引用指向的对象;
-
Native 方法中 JNI 引用的对象。
-
3.常见的垃圾回收算法
-
标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描),老年代回收
清理效果如图所示:
缺点:需要扫描整个堆区,时间开销较大。感兴趣的可以查看我上传的ppt中,标记-清除法的优化版。
-
拷贝算法 (copying) - 没有碎片,浪费空间,新生代回收
在堆区中,对象分为新生代(年轻代)、老年代和永生代,而复制算法发生是发生在新生代的。新建的对象一般分配在新生代的Eden区,当Eden快满时进行一次小型的垃圾回收。存活的对象会移动到 Survivor1区(以下简称S1)。
当再次发生 GC 时,S1区的存活对象将复制到先前闲置的S2区,同时存活对象寿命+1;以后每次发生GC,S1和S2区将交替的作为存活对象的存放区和闲置区。并且如果存活对象的寿命达到某个阈值,它将被分配到老年代中。
注意在JDK8中已经没有老年代的概念,使用的是metaspace的概念,感兴趣请参考 jdk8 Metaspace 调优
-
标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整),老年代回收
总体思想和标记-清除法类似
①标记阶段,通过根节点标记所有可达(直接或间接可访问)对象,和标记-清除法类似;
②清除阶段,将上一轮存活对象压缩到内存的一端,之后清理边界。(如此一来可以减少内存碎片,避免分配大对象时空间不够)
清理效果如图所示!
几种算法对比
- 复制算法与标记-压缩法对比:
复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以复制算法仅仅应用在新生代,而老年代一般不能直接选用这种算法,使用标记-压缩法。
- 标记-压缩法与标记-清除法对比:
根据老年代的特点,有人提出了“标记-压缩”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。也可以减少内存碎片。
4.常见的垃圾回收器
-
垃圾回收器的指标参数
(A)、吞吐量(Throughput)
CPU用于运行用户代码的时间与CPU总消耗时间的比值;
即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间);
高吞吐量即减少垃圾收集时间,让用户代码获得更长的运行时间;
(B)、垃圾收集器期望的目标(关注点)
停顿时间
停顿时间越短就适合需要与用户交互的程序;
良好的响应速度能提升用户体验;
吞吐量
高吞吐量则可以高效率地利用CPU时间,尽快完成运算的任务;
主要适合在后台计算而不需要太多交互的任务;
覆盖区(Footprint)
在达到前面两个目标的情况下,尽量减少堆的内存空间;
可以获得更好的空间局部性;
-
STW
stop-the-word:指的是停止业务进程,启动垃圾回收器进行垃圾回收那个中断的时间
-
PS 和 PN区别的延伸阅读: ▪docs.oracle.com/en/java/jav…
-
垃圾收集器跟内存大小的关系
Serial 几十兆
PS 上百兆 - 几个G(JDK8)
CMS - 20G
G1 - 上百G
ZGC - 4T - 16T(JDK13)
-
并发垃圾收集和并行垃圾收集的区别
(A)、并行(Parallel)
指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;
如ParNew、Parallel Scavenge、Parallel Old;
(B)、并发(Concurrent)
指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行);
用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上,不再区分新生代和老年代。
如CMS、G1(也有并行);
-
Minor GC和Full GC的区别
(A)、Minor GC
又称新生代GC,指发生在新生代的垃圾收集动作;
因为Java对象大多是朝生夕灭,所以Minor GC非常频繁,一般回收速度也比较快;
(B)、Full GC
又称Major GC或老年代GC,指发生在老年代的GC;
出现Full GC经常会伴随至少一次的Minor GC(不是绝对,Parallel Sacvenge收集器就可以选择设置Major GC策略);
Major GC速度一般比Minor GC慢10倍以上;
垃圾回收器的发展路线
随着内存越来越大的过程而演进 从分代算法演化到不分代算法 Serial算法 几十兆 Parallel算法 几个G CMS 几十个G - 承上启下,开始并发回收 - .- 三色标记 -
JDK诞生后默认是Serial,java1.3.1前是Serial,为了提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前任何一个JDK版本默认是CMS 并发垃圾回收是因为无法忍受STW
Serial 年轻代 串行回收
针对新生代;
采用复制算法;
单线程收集;
进行垃圾收集时,必须暂停所有工作线程,直到完成;
即会"Stop The World";
SerialOld
Serial对应的老年代回收器
采用"标记-整理"算法(还有压缩,Mark-Sweep-Compact);
单线程收集;
ParNew 年轻代 一般是配合CMS 并行回收
ParNew垃圾收集器是Serial收集器的多线程版本。
ParNew/Serial Old组合收集器运行示意图如下:
-
特点
除了多线程外,其余的行为、特点和Serial收集器一样; 如Serial收集器可用控制参数、收集算法、Stop The World、内存分配规则、回收策略等; 两个收集器共用了不少代码; 在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作; 但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。
-
设置参数: "-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器; "-XX:+UseParNewGC":强制指定使用ParNew; "-XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
-
为什么只有ParNew能与CMS收集器配合: CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器,第一次实现了让垃圾收 集线程与用户线程(基本上)同时工作; CMS作为老年代收集器,但却无法与JDK1.4已经存在的新生代收集器Parallel Scavenge配合工作; 因为Parallel Scavenge(以及G1)都没有使用传统的GC收集器代码框架,而另外独立实现;而其余几种 收集器则共用了部分的框架代码;
Parallel Scavenge(PS) 年轻代 并行回收
Parallel Scavenge垃圾收集器因为与吞吐量关系密切,也称为吞吐量收集器(Throughput Collector)。
(A)、有一些特点与ParNew收集器相似
新生代收集器;
采用复制算法;
多线程收集;
(B)、主要特点是:它的关注点与其他收集器不同
CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间;
而Parallel Scavenge收集器的目标则是达一个可控制的吞吐量(Throughput)。
ParallelOld
parallel是并行的意思,java8的默认老年代回收器
针对老年代;
采用"标记-整理"算法;
多线程收集;
Parallel Scavenge/Parallel Old收集器运行示意图如下:
设置参数
"-XX:+UseParallelOldGC":指定使用Parallel Old收集器;
ConcurrentMarkSweep 老年代 并发回收
垃圾回收和应用程序同时运行,降低STW的时间(200ms) CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定 CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时 候,使用SerialOld 进行老年代回收
想象一下: PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + SerialOld(几个小时 - 几天的STW)
几十个G的内存,单线程回收 -> G1 + FGC
几十个G -> 上T内存的服务器 ZGC 算法:三色标记 + Incremental Update
CMS
并发标记清理(Concurrent Mark Sweep,CMS)收集器也称为并发低停顿收集器(Concurrent Low Pause Collector)或低延迟(low-latency)垃圾收集器;
-
特点
针对老年代;
基于"标记-清除"算法(不进行压缩操作,产生内存碎片);
以获取最短回收停顿时间为目标;
并发收集、低停顿;
需要更多的内存(看后面的缺点);
是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器;
第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;
无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败
-
步骤
(A)、初始标记(CMS initial mark)
仅标记一下GC Roots能直接关联到的对象;
速度很快;
但需要"Stop The World";
(B)、并发标记(CMS concurrent mark)
进行GC Roots Tracing的过程;
刚才产生的集合中标记出存活对象;
应用程序也在运行;
并不能保证可以标记出所有的存活对象;
(C)、重新标记(CMS remark)
为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录;
需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
采用多线程并行执行来提升效率;
(D)、并发清除(CMS concurrent sweep)
回收所有的垃圾对象;
G1(200ms - 10ms)
算法:三色标记 + SATB
G1(Garbage-First)是JDK7-u4才推出商用的收集器;
-
特点
(A)、并行与并发
能充分利用多CPU、多核环境下的硬件优势;
可以并行来缩短"Stop The World"停顿时间;
也可以并发让垃圾收集与用户程序同时进行;
(B)、分代收集,收集范围包括新生代和老年代
能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;
能够采用不同方式处理不同时期的对象;
虽然保留分代概念,但Java堆的内存布局有很大差别;
将整个堆划分为多个大小相等的独立区域(Region);
新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合;
(C)、结合多种垃圾收集算法,空间整合,不产生碎片
从整体看,是基于标记-整理算法;
从局部(两个Region间)看,是基于复制算法;
这是一种类似火车算法的实现;
都不会产生内存碎片,有利于长时间运行;
(D)、可预测的停顿:低停顿的同时实现高吞吐量
G1除了追求低停顿处,还能建立可预测的停顿时间模型;
可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒;
-
应用场景
面向服务端应用,针对具有大内存、多处理器的机器;
最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案;
如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒;
用来替换掉JDK1.5中的CMS收集器;
在下面的情况时,使用G1可能比CMS好:
(1)、超过50%的Java堆被活动数据占用;
(2)、对象分配频率或年代提升频率变化很大;
(3)、GC停顿时间过长(长于0.5至1秒)。
如果现在采用的收集器没有出现问题,不用急着去选择G1;
如果应用程序追求低停顿,可以尝试选择G1;
是否代替CMS需要实际场景测试才知道。
-
设置参数
"-XX:+UseG1GC":指定使用G1收集器;
"-XX:InitiatingHeapOccupancyPercent":当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45;
"-XX:MaxGCPauseMillis":为G1设置暂停时间目标,默认值为200毫秒;
"-XX:G1HeapRegionSize":设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region;
-
运行步骤
(A)、初始标记(Initial Marking)
仅标记一下GC Roots能直接关联到的对象;
且修改TAMS(Next Top at Mark Start),让下一阶段并发运行时,用户程序能在正确可用的Region中创建新对象;
需要"Stop The World",但速度很快;
(B)、并发标记(Concurrent Marking)
进行GC Roots Tracing的过程;
刚才产生的集合中标记出存活对象;
耗时较长,但应用程序也在运行;
并不能保证可以标记出所有的存活对象;
(C)、最终标记(Final Marking)
为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录;
上一阶段对象的变化记录在线程的Remembered Set Log;
这里把Remembered Set Log合并到Remembered Set中;
需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
采用多线程并行执行来提升效率;
(D)、筛选回收(Live Data Counting and Evacuation)
首先排序各个Region的回收价值和成本;
然后根据用户期望的GC停顿时间来制定回收计划;
最后按计划回收一些价值高的Region中垃圾对象;
回收时采用"复制"算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存;
可以并发进行,降低停顿时间,并增加吞吐量;
ZGC (10ms - 1ms) PK C++
算法:ColoredPointers + LoadBarrier
Shenandoah
算法:ColoredPointers + WriteBarrier
Eplison
5.JVM内存分代模型(用于分代垃圾回收算法)
如果有人问你:JVM内存管理模型?你就说:您指的是哪种垃圾回收器?
-
部分垃圾回收器使用的模型
除Epsilon ZGC Shenandoah之外的GC都是使用逻辑分代模型
G1是逻辑分代,物理不分代
除此之外不仅逻辑分代,而且物理分代
-
新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace
-
永久代 元数据 - Class
-
永久代必须指定大小限制 ,元数据可以设置,也可以不设置,无上限(受限于物理内存)
-
字符串常量 1.7 - 永久代,1.8 - 堆
-
MethodArea逻辑概念 - 永久代、元数据
-
-
新生代 = Eden + 2个suvivor区
-
YGC回收之后,大多数的对象会被回收,活着的进入s0
-
再次YGC,活着的对象eden + s0 -> s1
-
再次YGC,eden + s1 -> s0
-
年龄足够 -> 老年代 (15 CMS 6)
-
s区装不下 -> 老年代
-
-
老年代
-
顽固分子
-
老年代满了FGC Full GC
-
-
GC Tuning (Generation)
-
尽量减少FGC
-
MinorGC = YGC
-
MajorGC = FGC
-
-
对象分配过程图
new对象的时候分析是否存在逃逸分析,不存在且可以标量替换,放栈中。
-
动态年龄:(不重要) www.jianshu.com/p/989d3b06a…
-
分配担保:(不重要) YGC期间 survivor区空间不够了 空间担保直接进入老年代 参考:cloud.tencent.com/developer/a…
常见垃圾回收器组合参数设定:(1.8)
-
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
- 小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
-
-XX:+UseParNewGC = ParNew + SerialOld
-
这个组合已经很少用(在某些版本中已经废弃)
-
-
-XX:+UseConc
(urrent)
MarkSweepGC = ParNew + CMS + Serial Old
-
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
-
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-
-XX:+UseG1GC = G1
-
Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC
-
java +XX:+PrintCommandLineFlags -version
-
通过GC的日志来分辨
-
-
Linux下1.8版本默认的垃圾回收器到底是什么?
-
1.8.0_181 默认(看不出来)Copy MarkCompact
-
1.8.0_222 默认 PS + PO
-
JVM调优第一步,了解JVM常用命令行参数
-
JVM的命令行参数参考:docs.oracle.com/javase/8/do…
-
HotSpot参数分类
标准: - 开头,所有的HotSpot都支持
非标准:-X 开头,特定版本HotSpot支持特定命令
不稳定:-XX 开头,下个版本可能取消
java -version
java -X
java -XX:+PrintFlagsWithComments //只有debug版本能用
试验用程序:
import java.util.List;import java.util.LinkedList;public class HelloGC { public static void main(String[] args) { System.out.println("HelloGC!"); List list = new LinkedList(); for(;;) { byte[] b = new byte[1024*1024]; list.add(b); } }}-
区分概念:内存泄漏memory leak,内存溢出out of memory
-
java -XX:+PrintCommandLineFlags HelloGC
-
java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC PrintGCDetails PrintGCTimeStamps PrintGCCauses
-
java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC
-
java -XX:+PrintFlagsInitial 默认参数值
-
java -XX:+PrintFlagsFinal 最终参数值
-
java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数
-
java -XX:+PrintFlagsFinal -version |grep GC
-
java -XX:+PrintFlagsFinal -version | wc -l 共728个参数
-
PS GC日志详解
每种垃圾回收器的日志格式是不同的!
PS日志格式
heap dump部分:
eden space 5632K, 94% used [0x00000000ff980000,0x00000000ffeb3e28,0x00000000fff00000) 后面的内存地址指的是,起始地址,使用空间结束地址,整体空间结束地址
total = eden + 1个survivor
调优前的基础概念:
-
吞吐量:用户代码时间 /(用户代码执行时间 + 垃圾回收时间)
-
响应时间:STW越短,响应时间越好
所谓调优,首先确定,追求啥?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量...
问题:
科学计算,吞吐量。数据挖掘,thrput。吞吐量优先的一般:(PS + PO)
响应时间:网站 GUI API (1.8 G1)
什么是调优?
-
根据需求进行JVM规划和预调优
-
优化运行JVM运行环境(慢,卡顿)
-
解决JVM运行过程中出现的各种问题(OOM)
调优,从规划开始
-
调优,从业务场景开始,没有业务场景的调优都是耍流氓
-
无监控(压力测试,能看到结果),不调优
-
步骤:
-
熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)
-
响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)
-
吞吐量 = 用户时间 /( 用户时间 + GC时间) [PS]
-
-
选择回收器组合
-
计算内存需求(经验值 1.5G 16G)
-
选定CPU(越高越好)
-
设定年代大小、升级年龄
-
设定日志参数
-
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
-
或者每天产生一个日志文件
-
-
观察日志情况
-
-
案例1:垂直电商,最高每日百万订单,处理订单系统需要什么样的服务器配置?
这个问题比较业余,因为很多不同的服务器配置都能支撑(1.5G 16G)
1小时360000集中时间段, 100个订单/秒,(找一小时内的高峰期,1000订单/秒)
经验值,
非要计算:一个订单产生需要多少内存?512K * 1000 500M内存
专业一点儿问法:要求响应时间100ms
压测!
-
案例2:12306遭遇春节大规模抢票应该如何支撑?
12306应该是中国并发量最大的秒杀网站:
号称并发量100W最高
CDN -> LVS -> NGINX -> 业务系统 -> 每台机器1W并发(10K问题) 100台机器
普通电商订单 -> 下单 ->订单系统(IO)减库存 ->等待用户付款
12306的一种可能的模型: 下单 -> 减库存 和 订单(redis kafka) 同时异步进行 ->等付款
减库存最后还会把压力压到一台服务器
可以做分布式本地库存 + 单独服务器做库存均衡
大流量的处理方法:分而治之
-
怎么得到一个事务会消耗多少内存?
-
弄台机器,看能承受多少TPS?是不是达到目标?扩容或调优,让它达到
-
用压测来确定
-
优化环境
-
有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G 的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G 的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了
-
为什么原网站慢? 很多用户浏览数据,很多数据load到内存,内存不足,频繁GC,STW长,响应时间变慢
-
为什么会更卡顿? 内存越大,FGC时间越长
-
咋办? PS -> PN + CMS 或者 G1
-
-
系统CPU经常100%,如何调优?(面试高频) CPU100%那么一定有线程在占用系统资源,
-
找出哪个进程cpu高(top)
-
该进程中的哪个线程cpu高(top -Hp)
-
导出该线程的堆栈 (jstack)
-
查找哪个方法(栈帧)消耗时间 (jstack)
-
工作线程占比高 | 垃圾回收线程占比高
-
-
系统内存飙高,如何查找问题?(面试高频)
-
导出堆内存 (jmap)
-
分析 (jhat jvisualvm mat jprofiler ... )
-
-
如何监控JVM
- jstat jvisualvm jprofiler arthas top...
解决JVM运行中的问题
一个案例理解常用工具
-
测试代码:
package com.mashibing.jvm.gc;import java.math.BigDecimal;import java.util.ArrayList;import java.util.Date;import java.util.List;import java.util.concurrent.ScheduledThreadPoolExecutor;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;/** * 从数据库中读取信用数据,套用模型,并把结果进行记录和传输 */public class T15_FullGC_Problem01 { private static class CardInfo { BigDecimal price = new BigDecimal(0.0); String name = "张三"; int age = 5; Date birthdate = new Date(); public void m() {} } private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50, new ThreadPoolExecutor.DiscardOldestPolicy()); public static void main(String[] args) throws Exception { executor.setMaximumPoolSize(50); for (;;){ modelFit(); Thread.sleep(100); } } private static void modelFit(){ List<CardInfo> taskList = getAllCardInfo(); taskList.forEach(info -> { // do something executor.scheduleWithFixedDelay(() -> { //do sth with info info.m(); }, 2, 3, TimeUnit.SECONDS); }); } private static List<CardInfo> getAllCardInfo(){ List<CardInfo> taskList = new ArrayList<>(); for (int i = 0; i < 100; i++) { CardInfo ci = new CardInfo(); taskList.add(ci); } return taskList; }} -
java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01
-
一般是运维团队首先受到报警信息(CPU Memory)
-
top命令观察到问题:内存不断增长 CPU占用率居高不下
-
top -Hp 观察进程中的线程,哪个线程CPU和内存占比高
-
jps定位具体java进程 jstack 定位线程状况,重点关注:WAITING BLOCKED eg. waiting on <0x0000000088ca3310> (a java.lang.Object) 假如有一个进程中100个线程,很多线程都在waiting on ,一定要找到是哪个线程持有这把锁 怎么找?搜索jstack dump的信息,找 ,看哪个线程持有这把锁RUNNABLE 作业:1:写一个死锁程序,用jstack观察 2 :写一个程序,一个线程持有锁不释放,其他线程等待
-
为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称 怎么样自定义线程池里的线程名称?(自定义ThreadFactory)
-
jinfo pid 查看进程信息,pid通过jps找到
-
jstat -gc 动态观察gc情况 / 阅读GC日志发现频繁GC / arthas观察 / jconsole/jvisualVM/ Jprofiler(最好用) jstat -gc 4655 500 : 每个500个毫秒打印GC的情况 如果面试官问你是怎么定位OOM问题的?如果你回答用图形界面(错误) 1:已经上线的系统不用图形界面用什么?(cmdline arthas) 2:图形界面到底用在什么地方?测试!测试的时候进行监控!(压测观察)
-
jmap - histo 4655 | head -20,查找有多少对象产生
-
jmap -dump:format=b,file=xxx pid :
线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合) 1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件(不是很专业,因为多有监控,内存增长就会报警) 2:
很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
3:在线定位(一般小点儿公司用不到)
4:在测试环境中压测(产生类似内存增长问题,在堆还不是很大的时候进行转储)
12. java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mashibing.jvm.gc.T15_FullGC_Problem01
* -XX:+HeapDumpOnOutOfMemoryError 在OOM发生时导出堆,可以用于分析原因
13. 使用MAT、jhat或jvisualvm 进行dump文件分析www.cnblogs.com/baihuitests… jhat -J-mx512M xxx.dump http://192.168.17.11:7000 拉到最后:找到对应链接 可以使用OQL查找特定问题对象
- 找到代码的问题
jconsole远程连接
-
程序启动加入参数:
java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false XXX
-
如果遭遇 Local host name unknown:XXX的错误,修改/etc/hosts文件,把XXX加入进去
192.168.17.11 basic localhost localhost.localdomain localhost4 localhost4.localdomain4::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
-
关闭linux防火墙(实战中应该打开对应端口)
service iptables stopchkconfig iptables off #永久关闭
-
windows上打开 jconsole远程连接 192.168.17.11:11111
jvisualvm远程连接
www.cnblogs.com/liugh/p/762… (简单做法)
jprofiler (收费)
arthas在线排查工具
-
为什么需要在线排查? 在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因。为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出入参,然后重新打包发布,如果打了日志还是没找到问题,继续加日志,重新打包发布。对于上线流程复杂而且审核比较严的公司,从改代码到上线需要层层的流转,会大大影响问题排查的进度。
-
jvm观察jvm信息
-
thread定位线程问题
-
dashboard 观察系统情况
-
heapdump + jhat分析
-
jad反编译 动态代理生成类的问题定位 第三方的类(观察代码) 版本问题(确定自己最新提交的版本是不是被使用)
-
redefine 热替换 目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性 m() -> mm()
-
sc - search class
-
watch - watch method
-
没有包含的功能:jmap,jmap是linux自带,类似于arthas进行threaddump分析的情况
GC算法的基础概念
- Card Table 由于做YGC时,需要扫描整个OLD区,效率非常低,所以JVM设计了CardTable, 如果一个OLD区CardTable中有对象指向Y区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card 在结构上,Card Table用BitMap来实现
CMS
CMS的问题
-
Memory Fragmentation
-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩
-
Floating Garbage
Concurrent Mode Failure 产生:if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped
解决方案:降低触发CMS的阈值
PromotionFailed
解决方案类似,保持老年代有足够的空间
–XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让CMS保持老年代足够的空间
CMS日志分析
执行命令:java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC com.mashibing.jvm.gc.T15_FullGC_Problem01
[GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K), 0.0268035 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
ParNew:年轻代收集器
6144->640:收集前后的对比
(6144):整个年轻代容量
6585 -> 2770:整个堆的情况
(19840):整个堆大小
[GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] //8511 (13696) : 老年代使用(最大) //9866 (19840) : 整个堆使用(最大)[CMS-concurrent-mark-start][CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] //这里的时间意义不大,因为是并发执行[CMS-concurrent-preclean-start][CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] //标记Card为Dirty,也称为Card Marking[GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] //STW阶段,YG occupancy:年轻代占用及容量 //[Rescan (parallel):STW下的存活对象标记 //weak refs processing: 弱引用处理 //class unloading: 卸载用不到的class //scrub symbol(string) table: //cleaning up symbol and string tables which hold class-level metadata and //internalized string respectively //CMS-remark: 8511K(13696K): 阶段过后的老年代占用及容量 //10108K(19840K): 阶段过后的堆占用及容量[CMS-concurrent-sweep-start][CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] //标记已经完成,进行并发清理[CMS-concurrent-reset-start][CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] //重置内部结构,为下次GC做准备
G1
G1日志详解
[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]//young -> 年轻代 Evacuation-> 复制存活对象 //initial-mark 混合回收的阶段,这里是YGC混合老年代回收 [Parallel Time: 1.5 ms, GC Workers: 1] //一个GC线程 [GC Worker Start (ms): 92635.7] [Ext Root Scanning (ms): 1.1] [Update RS (ms): 0.0] [Processed Buffers: 1] [Scan RS (ms): 0.0] [Code Root Scanning (ms): 0.0] [Object Copy (ms): 0.1] [Termination (ms): 0.0] [Termination Attempts: 1] [GC Worker Other (ms): 0.0] [GC Worker Total (ms): 1.2] [GC Worker End (ms): 92636.9] [Code Root Fixup: 0.0 ms] [Code Root Purge: 0.0 ms] [Clear CT: 0.0 ms] [Other: 0.1 ms] [Choose CSet: 0.0 ms] [Ref Proc: 0.0 ms] [Ref Enq: 0.0 ms] [Redirty Cards: 0.0 ms] [Humongous Register: 0.0 ms] [Humongous Reclaim: 0.0 ms] [Free CSet: 0.0 ms] [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)] [Times: user=0.00 sys=0.00, real=0.00 secs] //以下是混合回收其他阶段[GC concurrent-root-region-scan-start][GC concurrent-root-region-scan-end, 0.0000078 secs][GC concurrent-mark-start]//无法evacuation,进行FGC[Full GC (Allocation Failure) 18M->18M(20M), 0.0719656 secs] [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 3876K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]
案例汇总
OOM产生的原因多种多样,有些程序未必产生OOM,不断FGC(CPU飙高,但内存回收特别少) (上面案例)
-
硬件升级系统反而卡顿的问题(见上)
-
线程池不当运用产生OOM问题(见上) 不断的往List里加对象(实在太LOW)
-
smile jira问题 实际系统不断重启 解决问题 加内存 + 更换垃圾回收器 G1 真正问题在哪儿?不知道
-
tomcat http-header-size过大问题(Hector)
-
lambda表达式导致方法区溢出问题(MethodArea / Perm Metaspace) LambdaGC.java -XX:MaxMetaspaceSize=9M -XX:+PrintGCDetails
"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -XX:MaxMetaspaceSize=9M -XX:+PrintGCDetails "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.1\lib\idea_rt.jar=49316:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\work\ijprojects\JVM\out\production\JVM;C:\work\ijprojects\ObjectSize\out\artifacts\ObjectSize_jar\ObjectSize.jar" com.mashibing.jvm.gc.LambdaGC[GC (Metadata GC Threshold) [PSYoungGen: 11341K->1880K(38400K)] 11341K->1888K(125952K), 0.0022190 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Metadata GC Threshold) [PSYoungGen: 1880K->0K(38400K)] [ParOldGen: 8K->1777K(35328K)] 1888K->1777K(73728K), [Metaspace: 8164K->8164K(1056768K)], 0.0100681 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] [GC (Last ditch collection) [PSYoungGen: 0K->0K(38400K)] 1777K->1777K(73728K), 0.0005698 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Last ditch collection) [PSYoungGen: 0K->0K(38400K)] [ParOldGen: 1777K->1629K(67584K)] 1777K->1629K(105984K), [Metaspace: 8164K->8156K(1056768K)], 0.0124299 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:388) at sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(InstrumentationImpl.java:411)Caused by: java.lang.OutOfMemoryError: Compressed class space at sun.misc.Unsafe.defineClass(Native Method) at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:63) at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:399) at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:394) at java.security.AccessController.doPrivileged(Native Method) at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:393) at sun.reflect.MethodAccessorGenerator.generateSerializationConstructor(MethodAccessorGenerator.java:112) at sun.reflect.ReflectionFactory.generateConstructor(ReflectionFactory.java:398) at sun.reflect.ReflectionFactory.newConstructorForSerialization(ReflectionFactory.java:360) at java.io.ObjectStreamClass.getSerializableConstructor(ObjectStreamClass.java:1574) at java.io.ObjectStreamClass.access$1500(ObjectStreamClass.java:79) at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:519) at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:494) at java.security.AccessController.doPrivileged(Native Method) at java.io.ObjectStreamClass.<init>(ObjectStreamClass.java:494) at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:391) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1134) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at javax.management.remote.rmi.RMIConnectorServer.encodeJRMPStub(RMIConnectorServer.java:727) at javax.management.remote.rmi.RMIConnectorServer.encodeStub(RMIConnectorServer.java:719) at javax.management.remote.rmi.RMIConnectorServer.encodeStubInAddress(RMIConnectorServer.java:690) at javax.management.remote.rmi.RMIConnectorServer.start(RMIConnectorServer.java:439) at sun.management.jmxremote.ConnectorBootstrap.startLocalConnectorServer(ConnectorBootstrap.java:550) at sun.management.Agent.startLocalManagementAgent(Agent.java:137) -
直接内存溢出问题(少见) 《深入理解Java虚拟机》P59,使用Unsafe分配直接内存,或者使用NIO的问题
-
栈溢出问题 -Xss设定太小
-
比较一下这两段程序的异同,分析哪一个是更优的写法:
Object o = null;for(int i=0; i<100; i++) { o = new Object(); //业务处理} for(int i=0; i<100; i++) { Object o = new Object();} -
重写finalize引发频繁GC 小米云,HBase同步系统,系统通过nginx访问超时报警,最后排查,C++程序员重写finalize引发频繁GC问题 为什么C++程序员会重写finalize?(new delete) finalize耗时比较长(200ms)
-
如果有一个系统,内存一直消耗不超过10%,但是观察GC日志,发现FGC总是频繁产生,会是什么引起的? System.gc() (这个比较Low)
-
Distuptor有个可以设置链的长度,如果过大,然后对象大,消费完不主动释放,会溢出 (来自 死物风情)
-
用jvm都会溢出,mycat用崩过,1.6.5某个临时版本解析sql子查询算法有问题,9个exists的联合sql就导致生成几百万的对象(来自 死物风情)
-
new 大量线程,会产生 native thread OOM,(low)应该用线程池, 解决方案:减少堆空间(太TMlow了),预留更多内存产生native thread JVM内存占物理内存比例 50% - 80%
-
近期学生案例SQLLite的类库,批处理的时候会把所有的结果加载内存,有的人一下子更新几十万条数据,结果就产生了内存溢出,定位上用的是排除法,去掉这个模块就没问题,加上该模块就会出问题
-
java在线解压以及压缩文件造成的内存溢出
-
java使用opencv造成的卡顿与缓慢
-
最容易引起崩溃的报表系统
-
分库分表所引起的系统崩溃
GC常用参数
-
-Xmn -Xms -Xmx -Xss 年轻代 最小堆 最大堆 栈空间
最小堆和最大堆在生产环境一般配置为一样
-
-XX:+UseTLAB 使用TLAB,默认打开
-
-XX:+PrintTLAB 打印TLAB的使用情况
-
-XX:TLABSize 设置TLAB大小
-
-XX:+DisableExplictGC System.gc()不管用 ,FGC
-
-XX:+PrintGC
-
-XX:+PrintGCDetails
-
-XX:+PrintHeapAtGC
-
-XX:+PrintGCTimeStamps
-
-XX:+PrintGCApplicationConcurrentTime (低) 打印应用程序时间
-
-XX:+PrintGCApplicationStoppedTime (低) 打印暂停时长
-
-XX:+PrintReferenceGC (重要性低) 记录回收了多少种不同引用类型的引用
-
-verbose:class 类加载详细过程
-
-XX:+PrintVMOptions
-
-XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 必须会用
-
-Xloggc:opt/log/gc.log
-
-XX:MaxTenuringThreshold 升代年龄,最大值15
-
锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 ... 这些不建议设置
Parallel常用参数
-
-XX:SurvivorRatio
-
-XX:PreTenureSizeThreshold 大对象到底多大
-
-XX:MaxTenuringThreshold
-
-XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
-
-XX:+UseAdaptiveSizePolicy 自动选择各区大小比例
CMS常用参数
-
-XX:+UseConcMarkSweepGC
-
-XX:ParallelCMSThreads CMS线程数量
-
-XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)
-
-XX:+UseCMSCompactAtFullCollection 在FGC时进行压缩
-
-XX:CMSFullGCsBeforeCompaction 多少次FGC之后进行压缩
-
-XX:+CMSClassUnloadingEnabled
-
-XX:CMSInitiatingPermOccupancyFraction 达到什么比例时进行Perm回收
-
GCTimeRatio 设置GC时间占用程序运行时间的百分比
-
-XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代
G1常用参数
-
-XX:+UseG1GC
-
-XX:MaxGCPauseMillis 建议值,G1会尝试调整Young区的块数来达到这个值
-
-XX:GCPauseIntervalMillis ?GC的间隔时间
-
-XX:+G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32。 随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长 ZGC做了改进(动态区块大小)
-
G1NewSizePercent 新生代最小比例,默认为5%
-
G1MaxNewSizePercent 新生代最大比例,默认为60%
-
GCTimeRatio GC时间建议比例,G1会根据这个值调整堆空间
-
ConcGCThreads 线程数量
-
InitiatingHeapOccupancyPercent 启动G1的堆空间占用比例
常用工具实战
生产环境jmap和heapdump不能使用,因为生产环境内存可能有几G,会导致生产环境卡顿。只能测试环境或者高可用的环境使用。-XX:+HeapDumpOnOutOfMemoryError 在OOM发生时导出堆,可以用于分析原因。可能会分析不出问题,最好的方式就是写日志。
linux自带命令
查看进程信息 jinfo
用于查看进程各个信息,常用于查询JVM 参数
[root@instance-m33tfvmh ~]# jps10632 nacos-server.jar27101 Jps[root@instance-m33tfvmh ~]# jinfo 10632Attaching to process ID 10632, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.162-b12Java System Properties:java.runtime.name = Java(TM) SE Runtime Environmentjava.vm.version = 25.162-b12sun.boot.library.path = /home/jdk1.8.0_162/jre/lib/amd64java.protocol.handler.pkgs = org.springframework.boot.loaderjava.vendor.url = http://java.oracle.com/...VM Flags:Non-default VM flags: -XX:CICompilerCount=2 -XX:GCLogFileSize=104857600 -XX:InitialHeapSize=536870912 -XX:MaxHeapSize=536870912 -XX:MaxNewSize=268435456 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=268435456 -XX:NumberOfGCLogFiles=10 -XX:OldSize=268435456 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseGCLogFileRotation Command line: -Xms512m -Xmx512m -Xmn256m -Dnacos.standalone=true -Djava.ext.dirs=/home/jdk1.8.0_162/jre/lib/ext:/home/jdk1.8.0_162/lib/ext -Xloggc:/soft/nacos/logs/nacos_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dloader.path=/soft/nacos/plugins/health,/soft/nacos/plugins/cmdb,/soft/nacos/plugins/mysql -Dnacos.home=/soft/nacos
列出堆栈信息 jstack:
jstack可以列出线程信息,用于查死锁和查那个线程cpu占用较高。
cpu占用高排查就用该命令。
当前进程里面的线程信息:
- 线程
线程名:Attach Listener、http-nio-8848-exec-28
编号:#252、#220
优先级:prio=9, prio=5
状态:RUNNABLE、TIMED_WAITING (parking)
[root@instance-m33tfvmh ~]# jps10632 nacos-server.jarf569 Jps[root@instance-m33tfvmh ~]# jstack 10632 | more2020-08-17 20:43:37Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.162-b12 mixed mode):"Attach Listener" #252 daemon prio=9 os_prio=0 tid=0x00007f2fc808a800 nid=0x599 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE"http-nio-8848-exec-28" #220 daemon prio=5 os_prio=0 tid=0x00007f2fc4997800 nid=0x3b06 waiting on condition [0x00007f2faa63d000] java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000f27a7d38> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078) at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467) at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:85) at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:31) at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1073) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748)
java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000f27a7d38>
上面代码的意思是阻塞了,在等待一个锁
0x00000000f27a7d38。
上面的线程名称都不规范,按照阿里巴巴的开发规范,必须指定线程池或者线程的名称,方便问题回溯。
jmap
效果和arthas 下的threaddump命令类似,可以列出占用内存最多的对象。如下命令是列出占用内存最多的前20行的对象。
[root@instance-m33tfvmh tmp]# jmap -histo 10632 | head -20 num #instances #bytes class name---------------------------------------------- 1: 168374 106102736 [B 2: 592794 100008712 [C 3: 319497 12779880 org.apache.derby.impl.sql.conn.GenericStatementContext$CancelQueryTask 4: 456634 10959216 java.lang.String 5: 75096 7996136 [I 6: 480161 7682576 java.lang.Object 7: 125709 5383840 [Ljava.util.HashMap$Node; 8: 105478 4613816 [Ljava.lang.Object; 9: 135823 4346336 java.util.HashMap$Node 10: 106824 4272960 java.util.LinkedHashMap$Entry 11: 56638 3171728 java.util.LinkedHashMap 12: 82954 2654528 java.util.concurrent.ConcurrentHashMap$Node 13: 29979 2638152 java.lang.reflect.Method 14: 16799 2419056 org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper 15: 17954 2010848 sun.nio.ch.SocketChannelImpl 16: 41720 2002560 java.util.HashMap 17: 103232 1835216 [Ljava.lang.Class;
arthas
概述
诊断分析工具有两类:
-
图形界面工具:
jvisulvm(java/bin目录下自带)
jprofiler(收费)
-
命令行界面工具(重点):
arthas
可用图形界面进行分析的场景如下:
-
集群环境可以熔断某一台机器后进行分析
-
用tcpdump命令将流量复制到备用机器进行分析。
-
压测的时候。
其他情况都用命令行界面工具。
文档地址:
下载和运行:
下载arthas-boot.jar,然后用java -jar的方式启动:
curl -O https://arthas.aliyun.com/arthas-boot.jarjava -jar arthas-boot.jar
打印帮助信息:
java -jar arthas-boot.jar -h
-
如果下载速度比较慢,可以使用aliyun的镜像:
java -jar arthas-boot.jar --repo-mirror aliyun --use-http[root@ ~]# java -jar arthas-boot.jar[INFO] arthas-boot version: 3.3.9[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.* [1]: 10632 /soft/nacos/target/nacos-server.jar
启动arthas后发现有个nacos的进程10632,接下来需要将arthas挂载(attach)到nacos程序上去进行观察,在上面运行界面上,列出了服务器启动的程序,敲对应的数字可以进行挂载。
完整的命令和log如下:
[root@instance-m33tfvmh ~]# java -jar arthas-boot.jar[INFO] arthas-boot version: 3.3.9[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1\. Then hit ENTER.* [1]: 10632 /soft/nacos/target/nacos-server.jar1[INFO] Start download arthas from remote server: https://arthas.aliyun.com/download/3.3.9?mirror=aliyun[INFO] File size: 11.44 MB, downloaded size: 2.05 MB, downloading ...[INFO] File size: 11.44 MB, downloaded size: 4.26 MB, downloading ...[INFO] File size: 11.44 MB, downloaded size: 6.05 MB, downloading ...[INFO] File size: 11.44 MB, downloaded size: 7.44 MB, downloading ...[INFO] File size: 11.44 MB, downloaded size: 9.32 MB, downloading ...[INFO] File size: 11.44 MB, downloaded size: 11.20 MB, downloading ...[INFO] Download arthas success.[INFO] arthas home: /root/.arthas/lib/3.3.9/arthas[INFO] Try to attach process 10632[INFO] Attach process 10632 success.[INFO] arthas-client connect 127.0.0.1 3658 ,---. ,------. ,--------.,--. ,--. ,---. ,---. / O \ | .--. ''--. .--'| '--' | / O \ ' .-' | .-. || '--'.' | | | .--. || .-. |`. `-. | | | || |\ \ | | | | | || | | |.-' | `--' `--'`--' '--' `--' `--' `--'`--' `--'`-----' wiki https://arthas.aliyun.com/doc tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html version 3.3.9 pid 10632 time 2020-08-17 21:34:19 [arthas@10632]$
可以系统中的名称修改成了[arthas@10632]。
arthas的命令:
help
通过help查看arthas支持的命令
[arthas@10632]$ help NAME DESCRIPTION help Display Arthas Help keymap Display all the available keymap for the specified connection. sc Search all the classes loaded by JVM sm Search the method of classes loaded by JVM ...
dashboard
通过仪表盘工具查看线程的信息,可以看到线程的id、名称、组、优先级、cpu、运行时间等
group可以看到是系统线程还是业务线程。观察如果内存或cpu在持续上升,说明程序存在问题。
jvm
可以看到垃圾回收器名称叫做 COLLECTORS,新生代算法是copy,老年代算法是MarkSweepCompact
...GARBAGE-COLLECTORS ---------------------------------------------------------------------------------------------------------------------------------------- Copy name : Copy [count/time (ms)] collectionCount : 35340 collectionTime : 452285 MarkSweepCompact name : MarkSweepCompact [count/time (ms)] collectionCount : 3 collectionTime : 374...
thread
查看线程的列表信息
添加线程号,查看到具体线程的堆栈信息
通过thread查看死锁的线程
heapdump
将堆导出到文件用于分析full gc的原因
[arthas@10632]$ heapdumpDumping heap to /tmp/heapdump2020-08-17-22-06145210892456962923.hprof ...Heap dump file created[arthas@10632]$
下载到本地,然后通过工具打开进行分析,找到占用内存的对象去分析代码。
jad
在线反编译class分析运行的程序内容
redefine
通过jad分析出版本不一致后通过redefine进行class替换,可以实现不停机生效。
作业
-
-XX:MaxTenuringThreshold控制的是什么? A: 对象升入老年代的年龄 B: 老年代触发FGC时的内存垃圾比例
-
生产环境中,倾向于将最大堆内存和最小堆内存设置为:(为什么?) A: 相同 B:不同
-
JDK1.8默认的垃圾回收器是: A: ParNew + CMS B: G1 C: PS + ParallelOld D: 以上都不是
-
什么是响应时间优先?
-
什么是吞吐量优先?
-
ParNew和PS的区别是什么?
-
ParNew和ParallelOld的区别是什么?(年代不同,算法不同)
-
长时间计算的场景应该选择:A:停顿时间 B: 吞吐量
-
大规模电商网站应该选择:A:停顿时间 B: 吞吐量
-
HotSpot的垃圾收集器最常用有哪些?
-
常见的HotSpot垃圾收集器组合有哪些?
-
JDK1.7 1.8 1.9的默认垃圾回收器是什么?如何查看?
-
所谓调优,到底是在调什么?
-
如果采用PS + ParrallelOld组合,怎么做才能让系统基本不产生FGC
-
如果采用ParNew + CMS组合,怎样做才能够让系统基本不产生FGC
1.加大JVM内存
2.加大Young的比例
3.提高Y-O的年龄
4.提高S区比例
5.避免代码内存泄漏
16. G1是否分代?G1垃圾回收器会产生FGC吗?
- 如果G1产生FGC,你应该做什么?
1. 扩内存
2. 提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)
3. 降低MixedGC触发的阈值,让MixedGC提早发生(默认是45%)
18. 问:生产环境中能够随随便便的dump吗? 小堆影响不大,大堆会有服务暂停或卡顿(加live可以缓解),dump前会有FGC
-
问:常见的OOM问题有哪些? 栈 堆 MethodArea 直接内存
-
如果JVM进程静悄悄退出怎么办?
1. JVM自身OOM导致
1. heap dump on oom,这种最容易解决
2. JVM自身故障
1. -XX:ErrorFile=/var/log/hs_err_pid<pid>.log 超级复杂的文件 包括:crash线程信息 safepoint信息 锁信息 native code cache , 编译事件, gc相关记录 jvm内存映射 等等
3. 被Linux OOM killer杀死
1. 日志位于/var/log/messages
2. egrep -i 'killed process' /var/log/messages
4. 硬件或内核问题
1. dmesg | grep java
5. **找马士兵老师^^!**
21. 如何排查直接内存?
1. NMT打开 -- -XX:NativeMemoryTracking=detail
2. perf工具
3. gperftools
22. 有哪些常用的日志分析工具?
1. gceasy
23. CPU暴增如何排查?
1. top -Hp jstack
2. arthas - dashboard thread thread XXXX
3. 两种情况:1:业务线程 2:GC线程 - GC日志
24. 死锁如何排查?
1. jstack 观察线程情况
2. arthas - thread -b
参考资料
-
JVM调优参考文档:docs.oracle.com/en/java/jav…
-
www.cnblogs.com/nxlhero/p/1… 在线排查工具
-
www.jianshu.com/p/507f7e0cc… arthas常用命令
-
Arthas手册:
-
启动arthas java -jar arthas-boot.jar
-
绑定java进程
-
dashboard命令观察系统整体情况
-
help 查看帮助
-
help xx 查看具体命令帮助
-
-
jmap命令参考: www.jianshu.com/p/507f7e0cc…
-
jmap -heap pid
-
jmap -histo pid
-
jmap -clstats pid
-
-
blog.csdn.net/chenssy/art… 分析hotspot error file