基本的垃圾回收算法
引用计数(Reference Counting)
增加一个引用,引用计数加1,去掉一个引用,引用计数减1,然后回收那些引用计数为0的对象
问题:无法处理循环引用问题(例如A、B两个对象互相引用,但没有其他对象引用它们,这时它们也无法被回收)
标记-清除(Mark-Sweep)
从引用根节点开始标记所有被引用的对象,然后遍历整个堆,清除未标记的对象
问题:产生碎片
复制(Copying)
首先将内存空间分为对等的两半,每次只使用其中一半
每次回收时,遍历当前使用区域,将正在使用的对象复制到另外一个区域
好处:一次遍历即可,且不会产生碎片
问题:需要两倍空间
标记-整理(Mark-Compact)
从引用根节点开始标记所有被引用的对象,然后遍历整个堆,清除未标记的对象,并把存活对象压缩到一块
好处:避免了空间的浪费,且不会产生碎片
比较
空间:复制>标记-清除=标记-整理(复制需要两倍空间)
时间:复制<标记-清除<标记-整理(复制最快,一次遍历即可;标记-整理比标记-清除要慢,因为除了清除之外,还要移动数据)
JVM分代结构
JVM内存采用分代结构,分别为Young、Tenured、Permanent,其中Young又细分为Eden和两个大小相同的Survivor区:From和To。
分代依据
- 绝大部分的对象都是临时对象
- 不同对象的生命周期不同,采用不同的算法,可以提高不同的效率
JVM GC过程
新建的对象都在Eden中创建
大的对象直接在Old中创建:1)超过-XX:PretenureSizeThreshold设置,2)大于整个Eden
如果Eden满了,则触发MinorGCMinorGC
暂停程序
将Eden和From中存活的对象复制到To,同时各个对象的年龄值加1(MinorGC后,Eden和From都是空的)
如果To满了,则将对象移到Old,如果此时Old满了,则发送Promotion Failed错误,触发FullGC
如果对象的年龄超过-XX:MaxTenuringThreshold,也移到Old(这里有一个动态对象年龄的概念:不是每次都要求对象的年龄一定要超过-XX:MaxTenuringThreshold才晋升到Old,如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入Old)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那样,进行数据移动,也就解决了碎片的问题
选择
- 如果heap少于100MB,选择Serial
- 对于TPS,如果CPU够用,则选择并发GC,如果CPU使用率较高,则选择Throughput
- 对于平均响应时间,通常Throughput比并发GC要好
- 对于90%或99%的响应时间,并发GC比Throughput要好
- 如果选用并发GC,heap少于4G选择CMS,大于4G选择G1(这个保留,对G1算法不了解,了解后再修正)
GC Root
垃圾回收从Root开始,栈是程序真正执行的地方,所以从栈开始找,而栈又属于线程独有,所以从所有的线程的栈开始找
- 线程的栈帧中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI引用的对象