JVM-GC基础

基本的垃圾回收算法

引用计数(Reference Counting)

增加一个引用,引用计数加1,去掉一个引用,引用计数减1,然后回收那些引用计数为0的对象
问题:无法处理循环引用问题(例如A、B两个对象互相引用,但没有其他对象引用它们,这时它们也无法被回收)

标记-清除(Mark-Sweep)

从引用根节点开始标记所有被引用的对象,然后遍历整个堆,清除未标记的对象
问题:产生碎片

标记-清除

复制(Copying)

首先将内存空间分为对等的两半,每次只使用其中一半
每次回收时,遍历当前使用区域,将正在使用的对象复制到另外一个区域
好处:一次遍历即可,且不会产生碎片
问题:需要两倍空间

复制

标记-整理(Mark-Compact)

从引用根节点开始标记所有被引用的对象,然后遍历整个堆,清除未标记的对象,并把存活对象压缩到一块
好处:避免了空间的浪费,且不会产生碎片

标记-整理

比较

空间:复制>标记-清除=标记-整理(复制需要两倍空间)
时间:复制<标记-清除<标记-整理(复制最快,一次遍历即可;标记-整理比标记-清除要慢,因为除了清除之外,还要移动数据)

JVM分代结构

JVM内存采用分代结构,分别为Young、Tenured、Permanent,其中Young又细分为Eden和两个大小相同的Survivor区:From和To。

分代结构

分代依据

  1. 绝大部分的对象都是临时对象
  2. 不同对象的生命周期不同,采用不同的算法,可以提高不同的效率

JVM GC过程

GC过程
  1. 新建的对象都在Eden中创建
    大的对象直接在Old中创建:1)超过-XX:PretenureSizeThreshold设置,2)大于整个Eden
    如果Eden满了,则触发MinorGC

  2. MinorGC
    暂停程序
    将Eden和From中存活的对象复制到To,同时各个对象的年龄值加1(MinorGC后,Eden和From都是空的)
    如果To满了,则将对象移到Old,如果此时Old满了,则发送Promotion Failed错误,触发FullGC
    如果对象的年龄超过-XX:MaxTenuringThreshold,也移到Old(这里有一个动态对象年龄的概念:不是每次都要求对象的年龄一定要超过-XX:MaxTenuringThreshold才晋升到Old,如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入Old)

  3. FullGC
    如果Old满了,触发FullGC
    如果Perm满了,触发FullGC
    暂停程序(CMS算法的整个过程可以并行执行,只需短暂暂停程序2次)
    回收Old,如果回收后还是满了,则抛出OutOfMemoryError: Java heap space
    默认情况下,JVM是不回收Perm区的,要回收需要使用CMS算法,并设置-XX:+CMSClassUnloadingEnabled, -XX:+CMSPermGenSweepingEnabled,如果回收后还是满了,则抛出OutOfMemoryError: PermGen space

JVM GC算法

串行

效率高,但无法利用多核,一般在小程序使用,使用-XX:+UseSerialGC打开

串行

并行

对Young并行收集,使用-XX:+UseParallelGC打开
JDK6.0后可对Old进行并行收集,使用-XX:+UseParallelOldGC打开

并行

并发

保证大部分回收工作并发执行(应用不暂停),适合响应要求高的应用,使用-XX:+UseConcMarkSweepGC打开

并发

G1

待补

比较

Serial Throughput CMS G1
参数 -XX:+UseSerialGC -XX:+UseParallelGC -XX:+UseConcMarkSweepGC, -XX:+UseParNewGC -XX:+UseG1GC
Young(都是暂停整个应用) 单线程 多线程 多线程 多线程
Old 单线程,暂停应用,压缩 多线程,暂停应用,压缩 单或多线程,部分暂停,不压缩 多线程,部分暂停,压缩
增加CPU使用率,产生碎片,如果没有足够的CPU或者碎片太多,则退化成serial gc 增加CPU使用率,适合Heap大于4G的情况,Old区也是从一个region拷贝到另外一个region

G1和CMS的机制是差不多的,只是G1把old分区了,这样更有利于多线程的扫描
CMS每次清除后,都不会压缩整理的,会产生碎片,而G1每次都像young那样,进行数据移动,也就解决了碎片的问题

选择

  1. 如果heap少于100MB,选择Serial
  2. 对于TPS,如果CPU够用,则选择并发GC,如果CPU使用率较高,则选择Throughput
  3. 对于平均响应时间,通常Throughput比并发GC要好
  4. 对于90%或99%的响应时间,并发GC比Throughput要好
  5. 如果选用并发GC,heap少于4G选择CMS,大于4G选择G1(这个保留,对G1算法不了解,了解后再修正)

GC Root

垃圾回收从Root开始,栈是程序真正执行的地方,所以从栈开始找,而栈又属于线程独有,所以从所有的线程的栈开始找

GC Root
  1. 线程的栈帧中引用的对象
  2. 方法区中的类静态属性引用的对象
  3. 方法区中的常量引用的对象
  4. 本地方法栈中JNI引用的对象
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 6,040评论 2 31
  • 作者:一字马胡 转载标志 【2017-11-12】 更新日志 日期更新内容备注 2017-11-12新建文章初版 ...
    beneke阅读 2,249评论 0 7
  • JVM架构 当一个程序启动之前,它的class会被类装载器装入方法区(Permanent区),执行引擎读取方法区的...
    cocohaifang阅读 1,718评论 0 7
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,782评论 3 83
  • 转载blog.csdn.net/ning109314/article/details/10411495/ JVM工...
    forever_smile阅读 5,418评论 1 56