判断是不是垃圾
首先我们需要判断这些对象是不是垃圾
引用计数算法
有地方引用他,计数器加一
引用失效,计数器减一
计数器为零时,不可再被使用,等待被回收
优点:原理简单,判定效率高,
缺点:两个对象互相引用,永远无法回收
可达性分析算法
Roots根对象,根据引用关系向下搜索
谁能成为Roots:
1:在栈帧中的本地变量表中引用的对象,比如:方法中的参数、局部变量、临时变量
2:类静态属性引用的对象
3:常量引用的对象,比如字符串常量池里的引用:new String("abc").intern()
4:本地方法栈中JNI引用的对象
5:被同步锁持有的对象
6:虚拟机内部的引用,基本数据类型对应的Classs对象
与引用的类型有关
强引用:赋值,new关键字创建对象,不会被回收
软引用:发生内存溢出前,二次回收,释放内存,适合实现缓存
弱引用:必被回收
虚引用:无法创建对象实例,只是为了在某个对象被回收时得到一个系统通知
死前还有一次机会
finalize()方法
只执行一次
把自己(使用this关键字)赋值给某个类变量或者对象的成员变量,与引用链上的对象重新建立关联,被引用链上的对象引用
但不建议用这个方法,运行代价高,不确定性大,无法保证各个对象的调用顺序
方法区
让我们看看方法区里有哪些会被回收
废弃的常量
没有任何字符串对象引用常量池中的常量
没有任何地方与常量池中的类、方法、字段有符号引用
不再使用的类型
三个条件:
1:该类在java堆中的所有实例都已经被回收
2:加载该类的类加载器都已经被回收
3:该类对应的java.lang.Class对象没有在任何地方被引用,没有在任何地方通过反射访问该类的方法
满足条件也不一定就回收,可以用参数控制
收集的方式(追踪式垃圾收集)
垃圾收集有哪些收集方式呢
垃圾收集的算法
标记-清除算法
标记死的活的都可以,干掉死的
缺点:
执行效率不稳定、内存空间碎片化
收集器:关注延迟的CMS收集器
标记-复制算法
可用内存按容量划分大小相等的两块
一半的内存用完了,将存活着的对象复制到另一块上
缺点:浪费内存空间
实际应用:新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次只用Eden和一块Survivor空间,新生代大部分是死的快的对象,所以基本够用
如果不够用,进行分配担保,把多的存活对象放在老年代里
标记-整理算法
让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存
这种对象移动操作要全程暂停用户应用程序,Stop The World
收集器:关注吞吐量的Paralled Old收集器
基于分代收集理论
新生代的对象:朝生夕灭,每次回收都有大批对象死去
老年代的对象:难以消亡,回收的频率可以低一些,减少垃圾收集整体时间
跨代引用问题
老年代会引用新生代的对象
假说:跨代引用相对于同代引用来说仅占极少数
推论:存在互相引用关系的两个对象,是应该倾向于同时生存或者同时消亡的
解决方式:新生代上建立记忆集,记忆集把老年代分割成一小块一小块,标识出哪一块存在跨代引用,存在跨代引用的那一块内存里的对象才被加入GC Roots进行扫描
PS:目前只有CMS收集器会有单独收集老年代的行为
经典垃圾收集器
衡量垃圾收集器的三项指标:
内存占用、吞吐量、延迟
书中关于新生代和老年大垃圾收集器的搭配图:
Serial收集器
最基础,单线程工作,HotSpot虚拟机运行在客户端模式下的默认新生代收集器,简单高效,额外内存消耗小
新生代采取标记-复制,老年代采用标记-整理
适用于单CPU、新生代空间较小和对暂停时间要求不是很高的应用上
ParNew收集器
Serial收集器的多线程版本
除了Serial,只有它能和CMS收集器(收集老年代)配合工作,基本是并入CMS,成为它处理新生代的组成部分
PS收集器没有CMS并发进行时发生MinorGC需要的相应处理,所以不行
Paralled Scavenge收集器
新生代收集器
基于标记-复制
关注吞吐量,提供两个参数精确控制吞吐量:最大垃圾收集停顿时间、设置吞吐量大小
不根据-XX:PretenureSizeThreshold决定对象是否在旧生代上直接分配,eden区不够时,如果大于等于eden区一半的大小,就直接在旧生代上分配
PS:垃圾收集停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的:缩小新生代的内存空间,收集的停顿时间少了,但是频率就要高,在一段时间内用于收集的总时间就会变长
Serial Old收集器
Serial收集器的老年代版本,使用标记-整理算法,供客户端下的HotSpot虚拟机使用
应用:JDK5以及之前的版本与Paralled Scavenge收集器搭配使用
作为CMS收集器失败时的备胎
Paralled Old收集器
基于标记-整理
Parallel Scavenge加Parallel Old收集器组合,注重吞吐量
CMS收集器
以获取最短回收停顿时间为目标的收集器,关注服务的响应速度
基于标记-清除算法,分为四个步骤:
1:初始标记:需要STW,标记直接被Roots引用的对象
2:并发标记:遍历整个对象图,耗时长,但是不需要停顿
3:重新标记:并发标记期间,可能标记会改变
4:并发清除
缺点:
占用资源,降低总吞吐量
无法及时处理并发时产生的浮动垃圾
空间碎片(设置参数,若干次不整理空间的Full GC后 下一次Full GC前整理碎片)
特点:
如果内存不够分配新对象,就启动Serial Old收集器收集老年代
问题:如何保证收集线程和用户线程互不干扰地运行
增量更新算法:JVM---理解G1的SATB和CMS的增量更新_紫气东来_life的博客-CSDN博客_cms增量更新
G1-SATB、CMS-增量更新(文章记录)_OkidoGreen的博客-CSDN博客_cms 增量更新
G1收集器
特点:面向局部收集、基于Region的内存分布形式
衡量标准是哪块内存中垃圾数量最多,回收收益最大
有一类特殊的Humongous区域,专门用来存储大对象,基本看作老年代
从整体看是基于“标记-整理”,从局部看是基于“标记-复制”,不会产生空间碎片
默认采用的仍然是分代的方式,仍然坚信大多数新对象不需要长的生命周期
更细致分为fully young或partially young,fully young方式暂停的适合仅处理young regions,partially不仅处理,还会根据允许的GC的暂停时间来决定是否加入其他的非young regions
外部无法决定什么方式,启动时采用fully-young方式,一次Concurrent Marking后,切换为partially young方式
运作过程:
初始标记:标记Roots直接关联的对象,将region中top的值放入TAMS指针,每个region都保存了两个标识用的bitmap,包含一个bit的地址信息来指向对象的起始点
并发标记:可达性分析,扫描结束后重新处理SATB记录下的并发时有引用变动的对象
最终标记:短暂暂停,处理并发阶段结束后遗留下的SATB记录
筛选回收:自由选择任意多个Region构成回收集,把决定回收的那一部分Region的存活对象复制到空的Region中
跨Region引用的问题
每个Region维护自己的记忆集,记录下别的Region指向自己的指针,标记这些指针在哪些卡页的范围之内
G1的记忆集在存储结构的本质上是一种哈希表
问题:如何保证收集线程和用户线程互不干扰地运行
原始快照(SATB)算法
为每一个Region设计了两个名为TAMS的指针,把Region的一部分空间划分出来用于并发回收过程中的新对象分配,隐式标记,默认存活
建立起可靠停顿预测模型
记录回收耗时、脏卡数量,决定由哪些Region组成回收集
收集器的权衡
遗留系统,4GB到6GB以下的堆内存,使用CMS
选择收集器,三个问题:
1:应用程序的主要关注点
数据分析、科学计算:关注吞吐量
直接面向用户提供服务的B/S系统:降低停顿时间,低延迟
客户端或者嵌入式应用:内存占用
2:运行应用的基础设施
处理器数量、分配内存大小、系统架构、选择的操作系统
3:使用JDK的发行商和版本号