JVM GC简单总结

JVM GC漫谈

GCRoots

GC Roots是一些由堆外指向堆内的引用,可作为GC Roots的对象包含但不限于

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中引用的对象

哪些对象需要垃圾回收?

  • 对象到GC Roots没有引用链

jvm如何判断对象是否存活?

引用计数法

  • 每个对象自身持有一个计数器,每当对象被一个地方引用那么计数器+1;
    存在循环引用的问题(两个失效对象相互保存了对方的指针)

可达性分析法

  • 有一系列"GC Roots"起点,从这些点开始向下搜索,走过的路径称为“引用链”。若一个对象没有任何引用链可达GC Roots,那么该对象就是不可用,即使该对象还与其它对象相关联。
  • 经可达性分析算法所标记出的对象,会进行一次筛选(根据finalize方法)。若经过筛选,判定可回收,那么就会进行立即回收;若判定没有必要回收,那么就会将finalizable对象放入F-Queue队列中,进行二次筛查。二次筛查会执行对象的finalize()方法,若重写了该方法,与引用链上的任何一个对象建立关联,那么该对象就会从回收集合中移除,否则被回收。

垃圾回收算法

  • 引用计数法
    就是为对象设置计数器
  • 停止-复制法
    无内存碎片,浪费内存
  • 标记清除法
    标记、清除两个阶段,产生大量内存碎片。大对象无法分配到足够的连续内存,从而不得不提前触发GC
  • 标记整理法
    所有存活对象移动到一端,清理掉端边界外的内存
  • 分代法
    主要思想根据对象的生命周期长短特点将其进行分代,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

JVM内存区域划分

heap区域和非heap区域

  • heap区域:Eden Space(伊甸园)、Survivor Space(幸存者区)、Tenured Gen(老年代)
  • 非head区域:Perm Gen(永久代1.8以前,现在叫元空间)、Code Cache(代码缓存区)、JVM虚拟机栈、本地方法栈

Stop The World(stw)

虚拟机为一些特定指令位置设置“检查点”,程序运行到点时会暂停其他非垃圾回收线程的工作(Stop The World),暂停后再找到“GC Roots”进行关系的组建,进而执行标记和清除,直到完成垃圾回收。这也就造成了垃圾回收所谓的暂停时间(GC pause)。
Stop the world指令位置:
1、 循环的末尾
2、 方法临返回前/调用方法call后
3、 可能抛出异常的位置
CMS用两次短暂停来代替标记整理法中的长暂停

部分内存参考自:https://blog.csdn.net/zqz_zqz/article/details/70568819

垃圾收集器

串行回收:Serial收集器,单线程回收,全程stw
并行回收: Parallel xxx收集器,多线程回收,全程stw
并发回收: CMS和G1,多线程分阶段回收,只有某阶段stw

担保机制

串行老年代收集器将会以stw的方式进行一次Full GC,会对整个堆做标记和压缩,最后将包含纯粹的存活对象,从而造成较大的停顿时间。

CMS 标记清除 老年代 最小延迟

特点:只回收老年代和永久带(jdk1.8开始为元空间),不是说老年代满了再回收,而是有个触发阈值(92%),到阈值就开始回收

CMS的步骤:
1、初始标记(根可以直接关联的对象)stw
2、并发标记(和用户线程一起),标记对象(初始标记阶段标记的对象的引用的老年代的对象)
2.1 预清理(和用户线程一起) (老年代中有些没有被引用到对象)
2.2 可被终止的预清理(和用户线程一起)
3、重新标记 (多个标记线程,修正)stw
4、并发清除(和用户线程一起),清除对象
5、并发重置(和用户线程一起),等待下次CMS的触发

关键点:
第一次stw:初始标记阶段(可以是多线程)
标记老年代中所有存活的GC Roots对象
年轻代中活着的对象引用到的老年代的对象

第二次stw:重新标记阶段
标记整个老年代的所有的存活对象
标记的范畴是整个堆?(如果新生代引用老年代的对象,那么这个老年代对象视为存活)
这个阶段最为耗时

CMS的两次暂停为什么?
一种场景:当虚拟机完成两次标记后,便可以确认可回收的对象,而垃圾回收线程与程序并行,当GC线程标记好一个对象时,此时程序线程将对象重新加入“关系网”,当执行二次标记时,该对象没有重写finallze()方法,这个对象被回收

缺陷
1、CMS会产生内存碎片,老年代空间会随着应用时长被逐步耗尽,最后不得不通过担保机制对对内存进行压缩(串行老年代收集器将会以stw的方式进行一次GC,从而造成较大的停顿时间)CMS提供参数来指定多少次CMS收集后进行一次压缩的FULL GC
2、由于并发进行,CMS在收集与应用线程会同时增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制。

G1 分区 适合大尺寸堆内存 最小延迟

分区:Region

  • eden
  • survivor
  • old
  • humongous(巨型对象):一个大小达到甚至超过分区大小的一半的对象

堆内存空间的划分,可以在物理上是不连续的,只要逻辑上连续即可

G1的步骤:
1、初始标记
2、并发标记
3、重新标记
4、清除
5、转移回收

G1的设计原则:
1、启发式算法,收集尽可能多的垃圾,在老年代找出具有高收集收益的分区进行收集。同时G1根据用户设置的暂停时间目标自动调整年轻代和总堆的大小,暂停目标越短年轻带空间越小,总空间越大。
2、分区(Region),将内存划分为一个个相等大学校的内存分区,回收以分区为单位进行,存活的对象复制到另一个空闲分区中
3、分代,逻辑上分代,物理上无差别
4、G1的收集都是STW的

  • young gc 年轻代的gc,停止复制
  • mixed gc 执行ygc 和 回收一部分老年代(注意是部分)
  • full gc 单线程执行的serial old gc(触发担保机制)

Minor GC & Major GC & Full GC

Minor GC
年轻代的GC,停止复制法
内存池被填满的时候,其中的内容全部会被复制,指针从9开始跟踪空闲内存,始终停留在内存池顶部
大部分Eden区的对象都被认为是垃圾,直接就给清理掉了

Major GC
清理老年代的内存

Full GC
清理整个堆内存,包括年轻代和老年代,还有部分永久带

System.gc() 会马上执行吗?

直接调用system.gc()只会把这次的gc请求记录下来,并不会马上执行gc。
等到runFinalization=true的时候才会执行gc,runFinalization=true之后会允许一次system.gc(),之后在call system.gc()还会重复上面的行为。

system.gc()
runtime.runFinalzationSync();
System.gc()

finalize方法

finalize()方法是 Object 类的一个 protected 方法, 它是在对象被垃圾回收之前由 Java 虚拟机来调用的。

  • 对象再生问题:调用system.gc(),是否重写了finalize方法,在该方法中重写,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的。
  • finalize方法至多由GC执行一次(用户当然可以手动调用对象的finalize方法,但并不影响GC对finalize的行为)
  • 有一种 JNI(Java Native Interface)调用 non-Java程序(C 或 C++) , finalize()的工作就是回收这部分的内存

如何减少GC?

GC会stw。会暂停程序的执行,带来延迟的代价。所以在开发中,我们不希望GC的次数过多。

  • 对象不用时最好显式置为 Null
  • 尽量少用 System.gc()
  • 尽量少用静态变量
  • 尽量使用 StringBuffer,而不用 String 来累加字符串
  • 分散对象创建或删除的时间
  • 尽量少用 finalize 函数
  • 使用软引用类型 (只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等)

如何触发full gc?

  • system.gc()方法的调用
  • 老年代空间不足,调优时应该尽量做到让对象在Minor GC阶段就挥手,让对象在新生代多活一阵子,不要创建过大的数组和对象
  • 永久带空间不足(方法区在永久带中)
  • 堆中分配很大的内存
  • 如果yong gc的平均晋升大小比目前old gen剩余空间大,则不会触发young gc,而是直接触发full gc

如何排查JVM进行GC之后还有很多的内存没有释放

借大佬博客:https://blog.csdn.net/fishinhouse/article/details/80781673

JVM参数设置

转自:http://blog.csdn.net/kthq/article/details/8618052

  • -Xmx3550m:设置JVM最大堆内存为3550M。
  • -Xms3550m:设置JVM初始堆内存为3550M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
  • -Xss128k:设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M,之前每个线程栈大小为256K。应当根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。需要注意的是:当这个值被设置的较大(例如>2MB)时将会在很大程度上降低系统的性能。
  • -Xmn2g:设置年轻代大小为2G。在整个堆内存大小确定的情况下,增大年轻代将会减小年老代,反之亦然。此值关系到JVM垃圾回收,对系统性能影响较大,官方推荐配置为整个堆大小的3/8。
  • -XX:NewSize=1024m:设置年轻代初始值为1024M。
  • -XX:MaxNewSize=1024m:设置年轻代最大值为1024M。
  • -XX:PermSize=256m:设置持久代初始值为256M。
  • -XX:MaxPermSize=256m:设置持久代最大值为256M。
  • -XX:NewRatio=4:设置年轻代(包括1个Eden和2个Survivor区)与年老代的比值。表示年轻代比年老代为1:4。
  • -XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的比值。表示2个Survivor区(JVM堆内存年轻代中默认有2个大小相等的Survivor区)与1个Eden区的比值为2:4,即1个Survivor区占整个年轻代大小的1/6。
  • -XX:MaxTenuringThreshold=7:表示一个对象如果在Survivor区(救助空间)移动了7次还没有被垃圾回收就进入年老代。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代,对于需要大量常驻内存的应用,这样做可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代存活时间,增加对象在年轻代被垃圾回收的概率,减少Full GC的频率,这样做可以在某种程度上提高服务稳定性。

JVM性能调优工具

top 查看系统整体运行情况 cpu 、mem 、swap 、 进程等

jps 查看进程

jmap 生成 java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。

 jmap pid  查看进程的内存映像信息。
 jmap -heap pid  显示Java堆详细信息
 jmap -histo:live pid 显示堆中对象的统计信息
 jmap -clstats pid 打印类加载器信息
 jmap -dump:format=b,file=heapdump.phrof pid 生成堆转储快照dump文件。

jstat 查看堆内存各部分的使用量,以及加载类的数量

 jstat -class pid 类加载统计
 jstat -compiler pid 编译统计
 jstat -gc pid 垃圾回收统计
 jstat -gcutil pid times 总结垃圾回收统计(百分比,可以加时间)
 jstat -gccapacity pid 堆内存统计
 jstat -gcnew pid 新生代垃圾回收统计
 jstat -gcnewcapacity pid 新生代内存统计
 jstat -gcold pid 老年代垃圾回收统计
 jstat -gcoldcapacity pid 老年代内存统计
 jstat -gcmetacapacity pid 元数据空间统计
 jstat -printcompilation pid 编译方法统计

jstack pid 堆栈跟踪工具

jconsole 可视化工具

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值