引用计数算法
- 判断对象的引用数量来决定对象是否可以被回收
- 每个对象实例都有一个引用计数器,被引用则+1,完成引用-1
- 任何引用计数为0的对象实例被当作垃圾
优点:执行效率高,程序执行受影响小
缺点:无法检测出循环引用的情况,导致内存泄漏
可达性分析算法
判断对象的引用链是否可达来决定对象是否可以被回收(理论基础:离散数学图论)
可以作为ROOT的对象
- 虚拟机栈中引用的对象
- 方法区中常量引用的对象
- …
标记-清除算法
- 标记:从根集合进行扫描,对存活的对象进行标记 (可达算法实现)
- 清除:对堆内存从头到尾进行线性遍历,回收不可达对象内存
缺点:造成碎片化
复制算法
- 分为对象面和空闲面
- 对象面在对象面上创建
- 存活的对象从对象面复制到空闲面
- 将对象所有对象内存清除
适用于对象存活率低的场景
解决了碎片化问题
顺序分配内存,简单高效
标记-整理算法
- 标记:从根集合进行扫描,对存活的对象进行标记
- 清除:移动所有存活的对象,且按照内存地址次序依次排序,然后将末端内存地址以后的内存全部回收
避免了内存的不连续性
不要设置两块内存互换
适用于对象成活率极高的场景
分代收集算法(组合)
按照对象生命周期的不同划分区域以采用不同的垃圾回收算法
目的:提高JVM的回收效率
jvm的新生代、老年代、永久代,元空间(jdk1.8以后元空间取代永久代)
新生代分为三个区域,一个Eden区和两个Survivor区,它们之间的比例为(8:1:1),这个比例也是可以修改的
新生代中对象的特点是:很快就会被GC回收掉的或者不是特别大的对象
1.Eden区
Eden区位于Java堆的年轻代,是新对象分配内存的地方,由于堆是所有线程共享的,因此在堆上分配内存需要加锁。而Sun JDK为提升效率,会为每个新建的线程在Eden上分配一块独立的空间由该线程独享,这块空间称为TLAB(Thread Local Allocation Buffer)。在TLAB上分配内存不需要加锁,因此JVM在给线程中的对象分配内存时会尽量在TLAB上分配。如果对象过大或TLAB用完,则仍然在堆上进行分配。如果Eden区内存也用完了,则会进行一次Minor GC(young GC)。
2.Survival from to
Survival区与Eden区相同都在Java堆的年轻代。Survival区有两块,一块称为from区,另一块为to区,这两个区是相对的,在发生一次Minor GC后,from区就会和to区互换。在发生Minor GC时,Eden区和Survivalfrom区会把一些仍然存活的对象复制进Survival to区,并清除内存。Survival to区会把一些存活得足够旧的对象移至年老代。
3.年老代
年老代里存放的都是存活时间较久的,大小较大的对象,因此年老代使用标记整理算法。当年老代容量满的时候,会触发一次Major GC(full GC),回收年老代和年轻代中不再被使用的对象资源。
在新生代每进行一次垃圾收集后,就会给存活的对象“加1岁”,当年龄达到一定数量的时候就会进入老年代(默认是15,可以通过-XX:MaxTenuringThreshold来设置)
4. 永久代(Permanent Generation ):即JVM的方法区。在这里存放着一些被虚拟机加载的类信息(别忘了还有动态生成的类)的静态文件,这就导致了这个区中的东西比老年代和新生代更不容易回收。
5. 元空间(Metaspace):(降低FULL GC的频率)
从JDK 8开始,Java开始使用元空间取代永久代,元空间并不在虚拟机中,而是直接使用本地内存。
那么,默认情况下,元空间的大小仅受本地内存限制。当然,也可以对元空间的大小手动的配置。
1. 什么时候会触发Minor GC(从年轻代空间(包括 Eden 和 Survivor 区域)回收内存)?
-
Eden区域满了,或者新创建的对象大小 > Eden所剩空间
-
CMS设置了CMSScavengeBeforeRemark参数,这样在CMS的Remark之前会先做一次Minor
GC来清理新生代,加速之后的Remark的速度。这样整体的stop-the world时间反而短 -
Full GC的时候会先触发Minor GC
2. 什么时候会触发Full GC(Full GC是清理整个堆空间—包括年轻代和永久代)?
- Minor GC后存活的对象晋升到老年代时由于悲观策略的原因,有两种情况会触发Full GC, 1种是之前每次晋升的对象的平均大小 >老年代剩余空间
- 1种是Minor GC后存活的对象超过了老年代剩余空间。这两种情况都是因为老年代会为新生代对象的晋升提供担保,而每次晋升的对象的大小是无法预测的,所以只能基于统计,1个是基于历史平均水平,一个是基于下一次可能要晋升的最大水平。这两种情况都是属于promotion failure
- CMS失败,发生concurrent mode failure会引起Full GC,这种情况下会使用Serial Old收集器,是单线程的,对GC的影响很大。concurrent mode failure产生的原因是老年代剩余的空间不够,导致了和gc线程并发执行的用户线程创建的大对象(由PretenureSizeThreshold控制新生代直接晋升老年代的对象size阀值)不能进入到老年代,只要stop the world来暂停用户线程,执行GC清理。可以通过设置CMSInitiatingOccupancyFraction预留合适的CMS执行时剩余的空间
- 新生代直接晋升到老年代的大对象超过了老年代的剩余空间,引发Full GC。注意于promotion failure的区别,promotion failure指的是Minor GC后发生的担保失败
- Perm永久代空间不足会触发Full GC,可以让CMS清理永久代的空间。设置CMSClassUnloadingEnabled即可
- System.gc()引起的Full GC,可以设置DisableExplicitGC来禁止调用System.gc引发Full GC
- 使用RMI进行RPC或管理JDK应用(每一小时执行一次Full Gc)
Stop-the-World
- JVM由于要执行GC而停止了应用程序的执行
- 任何一种GC算法都会发生
- 多数GC优化通过减少Stop-The-World发生的时间来提高程序性能
Safepoint(安全点)
- 分析过程中对象引用关系不会发生变化的点
- 产生安全点的地方:方法调用;循环跳转;异常跳转
- 安全点数量要适中(太少GC会等待很长时间,太多会增加程序运行负荷)