目录
7-1 垃圾回收之判定对象是否为垃圾的方法
对象被判定为垃圾的标准
- 没有被任何对象引用的情况下
判定对象是否为垃圾
- 引用计数算法
- 通过判断对象的引用数量来决定对象是否被回收
- 每个对象实例都有一个引用计数器,被引用则+1,完成引用(完成了一个线程后,栈内的变量消失)则+1
- 任何引用计算为0的对象实例可以被当作为垃圾收集
- 优点:执行效率高,程序执行受影响较小
- 缺点:无法检测出循环引用的情况,导致内存泄露
- 可达性分析算法(JVM用的时这种算法)
- 通过判断对象的引用链是否可达来决定对象是否可以被回收
- 是从离散数学中的图论来引用的
- GC root对象的时候作为起始节点,从这些节点往下搜索,搜索所走过的路径,就被称为引用链,当一个对象从GCroot起始节点开始,没有任何引用链相连,从GC roots到这个对象是不可达的,证明这个对象是不可用的
- 可以作为GC root的对象
- 虚拟机栈中引用的对象(栈帧中的本地变量表)
- 方法区中的常量引用的对象
- 方法区中的类静态属性引用的对象
- 本地方法栈中JNI(Native方法)的引用对象
- 活跃线程中的引用对象
7-2 垃圾回收算法
标记-清除算法(Mark and Sweep)
- 标记:从根集合进行扫描,对存活的对象进行标记(用可达性分析算法进行标记)
- 清除:对堆内存从头到尾进行线性遍历,如果发现没有对象被标记为可达对象,那么就将此对象占用的内存给回收了,并且将原来的标记为可达对象的标识给清除掉,以便进行下一次垃圾回收
- 缺点
- 碎片化:标记清除不需要进行对象的移动,并且仅对不存活的对象进行处理,因此标记清除后会产生大量不连续的内存碎片,空间碎片太多,以后分配较大的对象的时候,无法找到足够的连续内存,不得不提前进行下一次的垃圾收集动作
复制算法:适用于对象存活率低的场景,例如年轻代
- 分为对象面和空闲面
- 对象在对象面上创建
- 存活的对象被从对象面复制到空闲面
- 将对象面所有对象内存清除
- 优点
- 解决碎片化问题
- 顺序分配内存,简单高效
- 适用于对象存活率低的场景,用于回收年轻代(每次回收,只有10%的对象存活)
在应对存活率较高的对象,复制算法就有点力不从心,必须要进行较多的复制操作,效率将会变低,更关键的是,如果不想浪费50%的空间,就需要有额外的空间担保,以应对所有对象都百分之百存活的极端情况,在老年代一般不能直接选用这种算法
标记-整理算法(Compacting)
- 标记:从根集合开始扫描,对存活的对象进行标记
- 清除: 移动所有存货的对象,且按照内存地址次序依次排序,然后将末端地址以后的内存全部回收。
- 解决了内存碎片的问题
- 优点
- 避免内存的不连续性
- 不用设置两块内存互换
- 适用于存活率极高的场景
分代收集算法(Generational Collector )
- 垃圾回收算法的组合拳
- 按照生命周期的不同划分区域以采用不同的垃圾回收算法
- 目的:提高JVM的回收效率
- GC分类
- Minor GC --- 年轻代----复制算法
- 年轻代:尽可能快速的收集掉那些生命周期短的对象
- Eden区 8:1:1
- 两个Survivor区:from 和 to 区
- 年轻代:尽可能快速的收集掉那些生命周期短的对象
- Magor GC
- 老年代---存放生命周期较长对象--标记清理算法或者标记整理算法
- Full FC:触发FullGC的条件
- 老年代空间不足
- 永久代空间不足(JDK7)
- CMS GC出现promation filed 和 concurrent mode failure
- Minor GC晋升到老年代的平均大小大于老年代的剩余空间。
- 调用System.gc()
- 使用RMI(远程方式)进行RPC,每小时执行一次Full GC
- Minor GC --- 年轻代----复制算法
对象如何晋升老年代
- 经历一定Minor次数仍然存活的对象,15岁
- Survivor区中存放不下的对象,由分配担保进入老年代中
- 新生成的大对象
常用的调优参数
- -XX:SurvivorRatio:Eden和Survivor的比值,默认8:1
- -XX:NewRatio:老年代和年轻代内存大小的比例
- -XX:--XX:MaxTenuringThreshold 设置新生代晋升老年代经过GC次数的最大阈值
7-3 新生代垃圾收集器
Stop-the-World
- JVM由于要执行GC而停止了应用程序的执行
- 任何一种GC算法中都会发生
- 多数GC优化通过减少Stop -the-World发生的时间来提高程序性能,从而使系统具有高吞吐低停顿的特点
Safepoint
- 分析过程中对象引用关系不会发生变化的点
- 产生安全点二点地方:方法调用;循环跳转;异常跳转
- 安全点的数量得适中,太少会增加GC等待的时间,太多会增加程序运行的负荷
常见的垃圾收集器
JVM运行模式
- Server:启动较慢,进入稳定期后,Server程序的运行速度要比Client要快,重量级的虚拟机
- Client:启动速度快,轻量级的虚拟机
垃圾收集器之间的联系
常见年轻代收集器
- serial收集器:单线程收集,垃圾收集时必须暂停其他线程,简单高效,client下默认的年轻代收集器(复制算法,-XX:+UseSerialGC)
- ParNew收集器:多线程,其余特点和serial一样,server模式下jvm首选年轻代收集器,多核有优势(复制算法,暂停其他线程,-XX:+UseParNewGC),能够与CMS收集器相配合
- ParallelScavenge收集器:比起用户停顿时间,更关注系统吞吐量;多核有优势,server模式下默认年轻代收集器(-XX:+UseParallelGC,复制算法,暂停其他线程)(-XX:+UseAdaptiveSizePolicy ,自适应调节策略)
- 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),用户停顿时提高用户体验两,吞吐量一般用在后端提高程序运行效率,快速完成工作。
7-4 老年代垃圾收集器
- SerialOld收集器:单线程收集,暂停其他线程,简单高效,Client下默认老年代收集器(-XX:UseSerialOldGC,标记-整理算法)
- ParallelOld收集器:多线程,吞吐量优先(-XX:UseParallelOldGC,标记-整理算法)
- CMS收集器:尽可能的减少了停顿时间,并发几乎能与用户线程同时工作,内存cpu硬性条件高有优势,下图加粗1,4步骤需要短暂stop-the-world。注意算法会生产碎片(-XXUseConcMarkSweepGC,标记-清除算法)
CMS收集器步骤(最适合老年代的收集器):并发收集器
- 初始标记:stop-the-word (从垃圾对象的根对象开始,进扫描和根对象相连接的对象)
- 并发标记:并发追溯标记,程序不会停顿。应用程序的线程和并发标记的线程比并发执行,所以用户不会感到停顿
- 并发预处理:查找执行并发比标记阶段从年轻代晋升到老年代的对象,通过重新扫描,减少下一阶段重新标记的工作
- 重新标记:暂停标记,扫描CMS堆中剩余对象
- 并发清理:清理垃圾对象,收集线程和应用线程并发执行的
- 并发清理:清理垃圾对象,程序不会停顿。
- 并发重置:重置CMS收集器的数据结构。
G1收集器(-XX:+UseG1GC,复制+标记-整理算法)
- GarbageFirst收集器特点:并行并发;分代收集;空间整合;可预测的停顿。
- 与其他区别:将整个java堆内存分成多个大小相等的Region;
- 年轻代老年代不再物理隔离:在分配内存的时候,不需要一个连续的内存空间,即不需要在JVM启动时哪些属于老年代,哪些属于年轻代。年轻代regin被回收后,变成可用状态,也可能变成老年代。
- 触发:Eden区满了分配失败,触发年轻代回收,所有Eden存活对象复制到survivor区
7-5 常见面试题
Object 的finalize()方法的作用是否与C++的析构函数的作用相同
- 与C++的析构函数不同,析构函数调用确定,而它是不确定的
- 将未被引用的对昂放置在F-Queue队列
- 方法执行随时可能会被终止
- 给予对象的最后一次重生的机会
当垃圾回收器宣布一个对象死亡,至少需要经历两个阶段
- 当对象进行可达性分析的时候发现没有和GC root相连接就会被第一次标记;
- 判断对象是否覆盖finalize(),如果覆盖,并且未被引用过这个方法的对象就会被放在F-Queue中,最后由JVM执行该方法
Java中的强引用,软引用,弱引用,虚引用有什么用
强引用
- 最普遍的引用 Object obj = new Object()
- 抛出OutOfMemoryError终止程序也不会回收具有强引用的对象
- 通过对象设置为null来弱化引用,使其被回收
软引用(Soft Reference)
- 对象处在有用但是非必须的状态
- 只有当内存空间不足的时候,GC会回收该对象的内存
- 可以用来实现高速缓存:避免OutOfMemoryError
String str = new String("abc");//强引用
SoftReference<String> softR = new SoftReference<String>(str) //弱引用
- 还可以用引用队列来实现
弱引用(Weak Reference)
- 非必须的对象,比软引用更弱一些
- GC时会被收回
- 被回收的概率也不大,因为GC线程优先级比较低
- 适用于引用偶尔被使用且不影响垃圾收集的对象
虚引用(PhantomReference)
- 不会决定对象的生命值周期
- 任何时候都可能被垃圾回收器回收
- 跟踪对象被垃圾回收器回收的活动,起哨兵作用
- 必须和引用队列ReferenceQueue联合使用
引用类型 | 被垃圾回收时间 | 用途 | 生存时间 |
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 在内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 在垃圾回收时 | 对象缓存 | GC运行后终止 |
虚引用 | Unknown | 标记、哨兵 | Unknown |
引用队列(ReferenceQueue)
- 无实际存储结构,存储逻辑依赖于内部节点之间的关系来表达
- 存储关联的且被GC的软引用,弱引用以及虚引用