1.当前对象是否为垃圾(垃圾确认机制)
运行时区域中的程序计数器. 虚拟机栈, 本地方法栈都是随着线程而灭, 不需要过多考虑回收问题, 而且基本上内存大小的分配也都是编译器就已知的(暂不考虑JIT的优化)
堆和方法区是线程共享的区域, 这部分区域在内存的分配和回收都是动态的, 垃圾回收器回收的就是这部分的内存
那么在堆中如何确认一个对象是否已经是垃圾即需要被回收了呢?
有两种思路即算法
(1)引用计数
实现简单, 效率高, 在对象上添加一个引用计数器, 每当一个有效引用到此对象时候, 计数器+1, 反之-1
主流的Java虚拟机实现都基本没有选择此算法, 因为不好解决循环引用的问题
(2)可达性分析
通过一系列GC Roots的对象作为起始点, 从此对象向下搜索走过的路径称为引用链, 当一个对象到GC Roots没有任何引用链时, 即被判定为可回收对象
以上两种算法都是通过引用来实现, 为了使引用更加灵活, 而不是非生即死的状态, Java对引用的概念进行了扩充, 能够描述一些'食之无味, 弃之可惜'的对象, 即当内存空间还足够时, 则能够保留在内存中, 进行回收之后还是很紧张, 则可以抛弃这些对象, 很多系统的缓存都符合这样的应用场景
强引用, 软引用, 弱引用, 虚引用 四种引用类型强度逐渐减弱, JDK中都有对应的class来实现各种类型引用, ThreadLocalMap就是弱引用的实现
方法区的回收
1.此区域主要回收两部分内容, 废弃常量和无用的类
虚拟机规范说过不要求虚拟机实现方法区的垃圾收集, 而且此区域の垃圾收集一般性价比比较低(以下都是HotSpot的实现)
常量池中的字符串常量和类. 接口, 方法和字段的符号引用回收较简单, 主要取决于是否有引用指向
而无用的类就比较不好判断, 需满足三个条件才有回收的可能, 分别是:
(1).该;类的所有对象都已经被回收
(2).加载该类的ClassLoader已经被回收
(3).该类对应的java.lang.Class对象没有在任何地方被引用, 无法在任何地方通过反射访问此类的方法
在大量使用反射, 动态代理, CGLib等ByteCode框架, 动态生成JSP以及OSGI这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能, 以避免永久带溢出
2.回收垃圾的算法概述
标记-清除
最容易想到的方法, 后面各个算法都是在此基础上的改进, 两个阶段: 标记所有需要回收的对象, 回收
缺点: 效率不高, 标记和清除两个过程的效率都不高, 而且会造成内存碎片, 使内存不连续无法容纳需要连续空间的大对象, 从而造成FullGC
复制
效率高, 一定程度的内存浪费(HotSpot对新生代的收集实现, 这里的程度可以配置调整)
标记-压缩
解决了标记清除的内存碎片化问题
3.回收垃圾的各收集器
JVM发起内存回收前, 需要保证线程执行到Safe Region中, 线程离开Safe Region前, 需保证JVM已经完成了根节点枚举或者整个GC
上图为现有的垃圾收集器的分代组合使用情况
新生代:
Serail收集器: 单线程收集, 有STW, 无线程切换开销, 单CPU环境下比较适合, client模式下的默认选择
ParNew收集器: Serial的多线程版本, 通常使用CMS清理老年代时, 与ParNew这使用, 也是CMS模式下的默认选择
Parallel Scavenge收集器: 其他收集器主要关注垃圾收集时工作线程的停顿时间, Parallel Scavenge更关注CPU吞吐量(运行代码时间/虚拟机总运行时间), 主要适合在后台运算而不需要太多交互的任务, Parallel Scavenge还提供了自适应调节策略
老年代:
Serial Old收集器: 单线程, 标记-整理
Parallel Old收集器: '多线程, 标记-整理 吞吐量优先', 注重吞吐量的场合, 可以优先考虑Parallel Scavenge+Parallel Old
CMS: 标记-清除 四个阶段: 初始标记, 并发标记, 重新标记, 并发清除, 初始标记为了找到GC Roots,速度很快, 并发标记时间较长即标记线程与用户线程并行工作(吞吐量会降低), 重新标记也比较快, 把上一步骤过程中遗漏的(少)再次收集, 然后并发清除
JDK7下的常用参数
参数
|
描述
|
UseSerialGC
|
虚拟机运行在Client 模式下的默认值,打开此开关后,使用Serial +Serial Old 的收集器组合进行内存回收
|
UseParNewGC
|
打开此开关后,使用ParNew + Serial Old 的收集器组合进行内存回收
|
UseConcMarkSweepGC
|
打开此开关后,使用ParNew + CMS + Serial Old 的收集器组合进行内存
回收。Serial Old 收集器将作为CMS 收集器出现Concurrent Mode Failure失败后的后备收集器使用
|
UseParallelGC
|
虚拟机运行在Server 模式下的默认值,打开此开关后,使用Parallel
Scavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收
|
UseParallelOldGC
|
打开此开关后,使用Parallel Scavenge + Parallel Old 的收集器组合进行内存回收
|
SurvivorRatio
|
新生代中Eden 区域与Survivor 区域的容量比值, 默认为8, 代表
Eden :Survivor=8∶1
|
PretenureSizeThreshold
|
直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象
将直接在老年代分配
|
MaxTenuringThreshold
|
晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC 之后,年
龄就加1,当超过这个参数值时就进入老年代
|
UseAdaptiveSizePolicy
|
动态调整Java 堆中各个区域的大小以及进入老年代的年龄
|
HandlePromotionFailure
|
是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个
Eden 和Survivor 区的所有对象都存活的极端情况
|
ParallelGCThreads
|
设置并行GC 时进行内存回收的线程数
|
GCTimeRatio
|
GC 时间占总时间的比率,默认值为99,即允许1% 的GC 时间。仅在
使用Parallel Scavenge 收集器时生效
|
MaxGCPauseMillis
|
设置GC 的最大停顿时间。仅在使用Parallel Scavenge 收集器时生效
|
CMSInitiatingOccupancyFraction
|
设置CMS 收集器在老年代空间被使用多少后触发垃圾收集。默认值为
68%,仅在使用CMS 收集器时生效
|
UseCMSCompactAtFullCollection
|
设置CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅
在使用CMS 收集器时生效
|
CMSFullGCsBeforeCompaction
|
设置CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理。
仅在使用CMS 收集器时生效
|