内存划分:
moniorGC(年轻代)
MajorGC(老年代)
元空间
FullGC(收集整个堆)
FullGC的排查步骤
*检查JVM参数设置,JVM监控系统的各项指标
*查看fullGC后 老年代的内存空间是否收回(内存泄露)
*查看堆内存的各区域的使用率及GC情况
--jstat -gcutil -h20 pid 1000
*查看堆内存中的存活对象,并按空间排序
--jmap -histo pid | head -n20
*dump堆内存文件,用可视化的堆内存工具
--jmap -dump:format=b,file=heap pid
FullGC的触发条件:
System.gc()方法的调用
当年轻代移动的次数大于设置的阈值
老年代的空间(连续空间)不足时
元空间内存到达阈值
堆中产生的大对象超过阈值直接放入老年区,导致老年代空间不足
总得来说就是:大多数情况下是老区老年代的空间不足或者连续空间不足导致的GC
JVM垃圾收集器
垃圾回收算法,ParNew CMS 与G1的区别
什么环境下可以使用G1替换ParNew+CMS(官方)
-实时数据占用了超过半数的堆空间
-对象分配率或“提升”的速度变化明显
-当GC的耗费的时间过长(0.5-1S)
CMS回收步骤:
*初始标记:stop the world
*并发标记
*重新标记: stw
*并发清理
G1回收步骤:
*初始标记:stw
*并发标记
*最终标记:stw
*筛选处理
G1的优缺点:
-模糊了分代的概率,用区域Region来替代,一般是1-32M,默认分为2048个空间
-高吞吐,低延迟
-可以根据用户设定的回收时间,产生对应的预测回收模型,在有限的时间内,尽量回收更多的内存空间
GC Roots过程存在的多标漏标的问题:
三色标记法:通过GCRoots使用三色标记算法去检查存活对象,对对象进行标记
三色标记法的标记过程:
初始的时候,所有对象都在【白色集合】中
将GC Roots直接引用的对象挪到【灰色集合】中
对【灰色集合】中对象操作
3.1将【灰色集合】中的本对象的引用也挪到【灰色集合】中
3.2 将【灰色集合】中的本对象挪到【黑色集合】中
重复步骤3,直到【灰色集合】中的对象为空
遍历结束后,扔在【白色集合】中的对象就是可以回收的
为什么使用三色标记法:
-避免STW现象,能并发的处理过程,减少中断时间或者没有中断来进行GC回收环节
三色分别为白色(未被检查的对象)、灰色(被初步检查,未完全检查完全,只扫描对象本身,未扫描引用的对象)、黑色(检查完全,已经扫描过它全部的引用对象)
发生问题场景:多标、漏标
-在GC回收过程中当应用线程与三色标记算法的GC一起运行的时候出现的多标、漏标问题
-多标-浮动垃圾:在遍历的过程中,原本的黑色对象A引用了对象B,此时在遍历灰色B的时候,A删除了对B的引用,然后此时B已经是灰色对象了,所以认为B是存活的对象,此轮检查就活了下来,需要等待下一轮的重新检查,在清理B。
-漏标(把原来存活的垃圾,标记成了死亡):有2个必要条件
至少有一个黑色对象在被扫描完之后对白色对象A产生了引用关系
灰色对象在被扫描的过程中,删除了对白色对象A的引用
解决漏标的方法:
CMS采用了增量更新的方式打破条件1:在黑色对象新增了白色对象A引用时,将黑色对象又重新置为了灰色对象,在下一轮的重新标记检查中重新扫描
G1采用原始快照的来打破条件2:在灰色对象断开白色对象引用的之前进行了快照,在删除引用后,对白色对象变为灰色对象,在下一轮的最终标记阶段对灰色对象重新进行扫描
增量更新跟原始快照的优缺点:
增量更新:不会产生浮动垃圾,效率低(因为扫描的时黑色对象的全部引用)
原始快照:会产生浮动垃圾(原本就应该被判定为垃圾的对象,会多存活一段时间),效率高(只对被删除引用的白色对象进行引用的扫描)
JVM常见问题:
-内存溢出(Out Of Memory):在申请内存的时候,JVM没有足够的内存分配
-内存泄露(Memory Leak):申请了内存的,但是没有释放回收,导致内存空间浪费
小知识点:内存泄露会导致出现内存泄露的问题,会报Out Of Memory Error异常,而内存泄露则不会直接报出异常,需要自己去定位排查问题。
内存溢出分为几种情况:
-堆溢出
排查方案--jmap查看内存占用情况,使用命令查看堆内对象的分配,对Dump出来的堆转储(JVM内存的某一个时刻的快照,生成一个.hprof的二进制文件,谨慎使用,因为在命令执行时间会暂停其它线程)内存快照进行分析
导致的原因--
创建了大量新建的对象或者大集合
使用了大量的循环或者死循环,或者无限递归
使用了过多的static修饰
存在很多对象的引用,导致对象不能被释放
-栈溢出
排查方案同上
-导致的原因
一般是死循环或者递归太深,请求的栈深度大于虚拟机所允许的最大深度
栈空间设置过小
-方法区溢出(<=JDK1.7)
排查方案同上
-导致的原因
出现大量的Class对象,类加载过多,常量池的对象太大
-元空间溢出(>=JDK1.8)
排查方案同上
-导致的原因
使用的是本地内存,大量的Class对象被出创建没有回收(方法中重复创建对象,又因为ClassLoder是主线程创建的,只要线程一直运行就不会被回收)
解决方案:重复使用一个对象,避免重复创建。给元空间设置合理的大小
小知识:堆外内存:也叫直接内存,不属于虚拟机运行时数据区,是一块由操作系统直接管理的内存,性能强于堆内存。使用场景:1.有很大的数据需要存储,生命周期很长2.频繁的IO操作
对象逃逸分析:分析对象动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用。
CPU突然飙高的解决方式:
通过top -p pid h 打印该进程下的所有线程,查看占用cpu资源高的线程ID
printf“%x”,将十进程的线程id转化为十六进制的线程id
jstack pid 找出占用cpu占用过高的线程id的线程信息
JVM调优:
主要是关注吞吐量跟响应时间
吞吐量 = 用户代码的执行时间/(用户代码的执行时间+GC执行时间)
响应时间 = 整个接口的响应时间(用户代码的执行时间+GC执行时间),stw越短(stop the world过程会暂停其它线程的运行),响应时间越短
调优内容:
jvm参数设置符合自身业务需求跟性能最优
选择合适的垃圾收集器
目标就是减少GC的次数及GC的时间