垃圾回收

2021-01-12  本文已影响0人  每皮1024

一、垃圾的判断?

  1. 引用计数法(RC)
    缺点:不能解决循环引用问题
  2. 可达性(Root Searching)
    Root:线程栈变量、静态变量、常量池、JNI指针(java native interface)

二、垃圾回收算法

  1. 标记清除(Mark Sweep)
    方法:找到垃圾后直接清除,表示可以用了
    缺点:碎片
  2. 拷贝(Copy)
    方法:将内存一分为二,GC时将有用对象复制到另一边
    缺点:空间浪费
  3. 标记压缩(Mark Compact)
    方法:将有用对象转移,整理到一起
    缺点:需要挪对象,效率偏低

三、垃圾回收器

1. 垃圾回收器图示

image.png
  1. 前面6种是分代模型,G1逻辑分代物理不分代,ZGC逻辑物理均分代
  2. jdk1.8默认Parrallel Scanvenge+Parrallel Old组合,jdk1.9默认G1
    2.1. 可使用命令java -XX:+PrintCommandLineFlags -version来判断当前使用的
    回收器
  3. 垃圾回收器一般分为Serial,Parrallel,CMS(上面三种竖向组合)。上述有这么多的垃圾回收器,是因为随着内存变大,不断地需要满足垃圾回收的要求。
    3.1 Serial:a stop-the-world进入safe point(STW:此时工作线程不能干活),copying collector which uses a single GC Thread
    3.2 Serial Old:a stop-the-world, mark-sweep-compact collector that uses a single GC thread
    ** 上面这两种Serial垃圾回收器组合,在内存变大时,清理时间会变长,STW时间太长会导致工作线程长期不可工作,也就是卡顿
    3.3 Parallel Scaveng:a stop-the-world, copying collector which uses multiple GC threads
    3.4 Parallel Old: a compacting collector that uses multiple GC threads
    ** 与Serial的区别主要在于,当STW时,有多个线程来帮助进行清理,提高了效率
    ** 但是,GC线程数量不可无限增加,当GC线程数量太多时,CPU会耗费精力在线程切换(context switch),所以当内存越来越大时依然会出现卡顿现象
    3.5 ParNew:描述同Parallel Scavenge,区别在于it has enhancements that make it usable with CMS。For example, "ParNew" does the synchronization needed so that it can run during concurrent phases of CMS

    3.6 CMS image.png
    • concurrent mark sweep,并发,做到了垃圾回收线程和工作线程同时进行
    • a mostly concurrent, low-pause collector
    • 4 phases:
      (1)initial mark:stw,然后标记根对象(所以耗时很短)
      (2)concurrent mark:并发标记根对象树(此时GC线程过滤对象树,但工作线程同时又在改变对象树,所以可能会出现错标现象)
      (3)remark:再次stw,修正错标问题(注意:CMS/g1/ZGC的区别主要在这一步:CMS和g1使用的是三色标记,而ZGC使用的是颜色指针color pointer)TODO:学习算法
      (4)concurrent sweep:经过第三次的修正错标后,并发进行清理

1.1 错标问题的解决(CMS/g1/ZGC)

三色标记法:黑色表示对象和对象的所有成员变量都已经遍历过了,灰色表示仅遍历了对象但其成员变量还没有遍历完,白色表示还没有遍历到

  1. 情况一:如下图,当标记完A为黑后,B->D的引用消失,而A->D的引用增加,此时GC标记线程不会再去遍历A,则D会被认为是没有引用而将被回收
    • image.png
    • CMS解决方案:Incremental Update,通过将A修改为灰色,下次标记时则会再次对A进行遍历。将A变灰条件:跟踪引用的变化,并对A的颜色进行修改(写屏障) image.png

      ** 此方法存在缺陷1:GC标记可能是多线程同时标记一个对象,假设A有1和2两个成员变量,m1线程刚标记完1正要标记2(m1认为A是灰的),此时1指向了D对象触发了m2线程将A设置为灰(写屏障),然后m1继续标记完2后将A设置为黑色,这种情况下就会出现漏标。所以CMS remark阶段,必须从头到尾扫一遍。
      ** 此方法存在缺陷2:当CMS(mark sweep)由于碎片导致放不下,会变成Serial Old

2. 分代模型

也即堆内存逻辑分区(不适用不分代垃圾收集器),新生代:老年代默认内存比例1:2

2.1 从新生代到老年代

image.png
  1. 对象如果能在栈中放得下,则放入栈中,在栈退出pop时会直接释放,不需要垃圾回收的介入,此种方式最为高效
  2. 如果该对象栈内放不下,则根据该对象的大小(可用-XX:PretenureSizeThreshold)判断,大对象直接进入老年代,等待FullGC时回收
  3. 如果该对象大小介于上述两者中间,则进入TLAB
    • Thread Local Allocation Buffer:(可取消)代表的是线程在Eden区单独的空间,当该线程需要分配对象空间时,优先在此区域分配,避免多线程竞争问题
  4. 新生代又分为eden、s1、s2区,一个对象首先进入eden区,回收一次会被拷贝到s1或者s2区(并且年龄+1),当年龄超过特定值时进入老年代
    • 年龄值设定
      • 可通过-XX:MaxTenuringThreshold配置,表示超过多少年龄就进入老年代
      • 各个垃圾回收器有默认值:一般垃圾回收器是15,CMS的默认值是6

四、GC Tuning

  1. 根据需求进行JVM规划和预调优
  2. 优化运行JVM环境(慢、卡顿)
  3. 解决JVM运行过程中出现各种问题(OOM)

1. 常见

  1. 命令
top
jps # java process, 当前系统内跑着的java的进程号
jinfo <process id> # 打印该进程关于java的信息(java版本等)
jstat -gc <process id> # 打印gc的所有内容空间
jstack <process id> # 打印线程及状态
jmap -histo <process id> | head -20 # 列举出该进程下,类对应了多少的对象;后面的可选,列出前20行
arthas # 阿里开源神器

arthas

jad # 反编译
redefine
  1. 图形化界面(但上线后一般不允许远程连接)

2. 实战

  1. 如何定位频繁的FGC
    可能是有对象不断占用内存
    1.1 首先可以通过jmap -histo <process id>查看类对应的对象数量
    1.2 但是生产环境如果直接使用会有性能影响(会产生STW):(1)测试环境压测(2)如有高可用,做好隔离,在另一台机器测(3)tcp copy流量到测试机器(4)-XX:+HeapDumpOnOutOfMemoryError,然后使用MAT/jhat/jvisualvm进行dump文件分析 (5)jmap -dump:format=b,file=xxx pid: (6)arthas - heapdump --live /path/to/file
  2. 如何定位OOM
  3. 如何定位直接内存
    3.1 NMT打开 --XX:NativeMemoryTracking=detail
    3.2 perf工具
    3.3 gperftools
  4. 如何定位JVM进程静悄悄挂掉,即没有dump文件
    4.1 JVM 自身oom导致:heap dump on oom,这种最容易解决
    4.2 JVM自身故障:-XX:ErrorFile=/var/log/hs_err_pid<pid>.log,超级复杂文件,包括crash线程信息、safepoint信息、锁信息、native code、cache、编译时间、gc相关记录、jvm内存映射等等
    4.3 被Linux OOM killer杀死:(1)日志位于/var/log/messages (2)egrep -i 'killed process' /var/log/messages
    4.4 硬件或内核问题:dmesg | grep java
  5. 如果一个Java进程CPU突然暴增(50%~90%)
    首先使用arthas-dashboard找到哪个线程占用CPU很高;如果是业务线程,则看业务逻辑;如果是JVM线程,则可能是GC问题:例如OOM造成的不断地FGC,消耗CPU资源导致暴增,此时看GC日志。
    5.1 普通linux命令:top -Hp找出进程线程占比CPU最高,jstack
    5.2 arthas - dashboard thread/thread <thread id> 可以看到线程堆栈
  6. 死锁如何排查?
    6.1 jstack观察线程情况
    6.2 或者用arthas - thread -b(找到那个因为拿着锁block最多线程的线程)

[书签: p6 10:31]

参考

上一篇 下一篇

猜你喜欢

热点阅读