文章目录
- 垃圾标记算法
- 解决
- 对象被判定为垃圾的标准
- 判定对象是否为垃圾的算法
- 引用计数算法
- 优缺点
- 缺点代码演示
- 可达性分析算法
- 可以作为GC ROOT的对象
- 垃圾回收算法
- 标记-清除算法
- 碎片化
- 复杂算法
- 标记整理算法
- 分带收集算法 主流
- Minor GC年轻代垃圾复制算法
- 如何晋升至老年代
- Full GC老年代
- jdk7及以前 对比 jdk8
- 参数调优
- 新生代垃圾收集器
- 其他线程等待gc线程
- 安全点
- JVM运行模式
- Serial收集器
- 老年代收集器
- 及适用于年轻代又适老年代
- 垃圾收集器之间的联系
- 面试题
- Object的finalize()方法的作用是否与C++的析构函数作用相同
- Java的强引用、软引用、弱引用、虚引用,有什么用?
- 综上比较
- 类层次结构
- 引用队列
- 追源码
- 引用对象放在ReferenceQueue代码示例
垃圾标记算法
解决
对象内存分配问题、回收分配给对象内存的问题
对象被判定为垃圾的标准
没有被其他对象引用
判定对象是否为垃圾的算法
引用计数算法
优缺点
缺点代码演示
对象实例相互引用,循环引用
主流的gc没用这种方法
可达性分析算法
离散数学的图论理解
从gc Root 向下搜索走过的路径,成为引用链。
存活标记为可达。
垃圾对象则不可达
可以作为GC ROOT的对象
垃圾回收算法
标记-清除算法
碎片化
不利于后来程序运行时,无法找到较大的连续内存分配
复杂算法
对象存活率低的场景
不会有碎片,每次对每个半区进行回收
商用虚拟机,多采用这种方法回收年轻代基本只有10%存活
年轻代
标记整理算法
在标记清除做了改进
解决空间碎片化问题
分带收集算法 主流
生命周期划分不同区域。采用不同垃圾回收算法
Minor GC年轻代垃圾复制算法
所有java出生的地方。申请的内存和存放都是在此处
Java对象一般不需要长久存活,朝生夕灭
每次gc年龄递增+1
通过调节年龄到老年代的参数,默认15岁
如何晋升至老年代
Full GC老年代
一般老年代回收,会伴随年轻代。
Full 比 Minor 执行慢10倍左右,但执行频率低
触发回收Full
老年代空间不足。
jdk1.7及以前永久代空间不足
CMS GC 同时。年轻代放不下,老年代也放不下。
jdk7及以前 对比 jdk8
jdk8将永久代删掉了
年轻代:存活率低,采用复制算法
老年代:存活率高,采用标记整理、清除
参数调优
空间分配:老:新大概2:1
新生代垃圾收集器
其他线程等待gc线程
安全点
什么是安全点?
JVM的快照下分析,其他线程停止等待gc线程。
不能存在gc分析过程中对象引用状态还在不停变换,因此分析结果在某个点具有确定性,这个点叫做安全点。
程序必须到达安全点才会停顿下来。
安全点太多,GC 过于频繁,增大运行时负荷;安全点太少,GC 等待时间太长。
JVM运行模式
Server
启动慢,重型,运行稳定后变快
Client
启动快,轻型,运行后,没server快
Serial收集器
减少gc线程等待时间(系统停顿时间)适合交互
在程序启动时可以设置 指定 收集器
Java最基本历史最悠久的收集器
jdk1.3.1以前是Java虚拟机年轻代的唯一选择
Server模式下的年轻代首选
除了Serial 只有ParNew与CMS配合使用
多线程
关注系统吞吐量,适合后台计算。
如果对收集器调优不熟悉,可以在启动时加上自适应 调节策略,把内存管理和调优任务交给虚拟机执行。
老年代收集器
jdk6之后提供
比较尴尬的是
在此之前,如果年轻代选择了Parallel Scavenge,老年代必须选择Serial Old收集器
。由于老年代服务端单线程性能上的拖累。使年轻代Parallel Scavenge未必能达到吞吐量最大化的效果。单线程的老年代无法充分利用年轻代多线程cpu的处理能力。
因此在老年代很大,硬件高级的环境中。甚至没有ParNew和CMS的组合给力。
直到Parallel Old出现,可以与Parallel Scavenge组合吞吐量优先收集器有了名副其实的组合。
适用:吞吐量、cpu敏感的场合
CMS占据着垃圾回收老年代的半壁江山
垃圾回收线程几乎和用户线程同时工作
但是还不能做到完全不需要(系统停顿时间),只是尽可能的缩短了应用时间
适用场景:对停顿比较敏感,需要更大的内存和cpu ,更牛的硬件
JVM中有很多存活时间相对较长的对象,可以用CMS
只扫描根直接关联的节点
缺点:标记清除,碎片化严重
及适用于年轻代又适老年代
用于替换掉JDK5中的CMS收集器
垃圾收集器之间的联系
CMS 和 G1使用了新的独立框架,而其他的共用框架类似
面试题
Object的finalize()方法的作用是否与C++的析构函数作用相同
C++析构函数,调用时机确定,对象离开作用域就会被清除掉
Java的finalize(),具有不确定性,当垃圾回收器宣告一个对象死亡时至少经过两次标记过程。
可达性分析之后发现,当没有和GC ROOT相链接的引用链,会被第一次标记。并且判断是否执行finalize方法,如果对象覆盖finalize()方法且未被引用, 这个对象就会被放置在F-Queue队列中,并在稍后由虚拟机自动建立的低优先级finalize线程去执行触发finalize()方法 。(由于优先级比较低,不承诺等待其运行结束,方法执行了随时可能被终止)给对象创造了最后一次重生的机会
不建议使用:由于运行的不确定性较大,无法保证各对象的调用顺序,同时运行代价相当高昂。
代码证明
Java的强引用、软引用、弱引用、虚引用,有什么用?
强
宁可终止也不回收
软
内存不够用再回收
适用:内存缓存
包装使用:SoftReference< String>
配合引用队列使用:
弱
见到就回收
垃圾回收优先级低
适用:偶尔使用
WeakReference< String >
必须和队列联合使用。软引用、弱引用也可以用此种方式
哨兵作用:判断是虚引用是否加入队列中,知道gc是什么时候执行的
综上比较
类层次结构
引用队列
依赖节点之间类似链表
链表的容器,其自己仅存储当前的head节点。后面的节点由referent.next()链接
除强引用,其他引用在gc后,将引用对象放在ReferenceQueue
追源码
referent新创建对象的引用
queue类似链表结构
next指向下一个引用
引用对象放在ReferenceQueue代码示例
开始时queue为空
normal打印载入的名字
weak没有打印
gc垃圾回收
weak载入到了主方法内,此时name被垃圾回收所以输出为null
对ReferenceQueue进行监控,如果有对象被回收,那么响应的Reference对象就会被放到Queue里面,就可以拿Refernce做一些事情。
如果不带Queue,就要不断的轮询Reference,通过不断的判断里面的get方法是否返回null ,来判断是否被回收。
通过Get方法来判断返回对象是否为空并不适用虚引用,因为此get方法返回值一直为null,只能通过Queue来判断
弱引用哨兵功能
主方法
输出