🦊 Java
1、Java基础
1.1 StringBuffer和StringBuilder的区别
1.2 ConcurrentHashMap和TreeMap实现原理
1.3 ArrayList和LinkedList实现原理
1.4 HashSet和TreeSet实现原理
1.5 深拷贝与浅拷贝
1.6 抽象类与接口
2、Java多线程
2.1 并发编程的三大特性
2.2 指令重排
2.3 Volatile原理
2.4 CAS原理
2.5 Java的4种引用级别
2.6 Java中的锁
2.7 Synchronized实现原理
2.8 线程池实现原理
2.9 AQS
2.10 创建线程的方式
2.11 ThreadLocal原理
3、JVM
3.1 判断对象是否存活的方法
3.2 JVM内存结构
3.3 常见的垃圾收集算法有哪些
3.4 指针碰撞和空闲列表
3.5 常见的垃圾收集器有哪些
3.6 内存溢出与内存泄漏的区别
3.7 常用的JVM启动参数有哪些
3.8 反射机制
4、NIO
4.1 概述
5、Spring
5.1 Spring IOC
5.2 Spring AOP
6、SpringBoot
6.1 SpringBoot、SpringCloud的联系与区别
-
+
游客
注册
登录
判断对象是否存活的方法
判断对象是否存活的方法有两种,分别是 `引用计数法`、`可达性分析算法`。 ## 1. 引用计数法 1. 引用计数法是指**给对象添加一个引用计数器**,**每当有一个地方[引用](https://notebook.ricear.com/project-34/doc-530)它时**,**计数器值就加 1**,**当引用失效时**,**计数器值就减 1**,**任何时刻计数器为 0 的对象就是不可能再被使用的**。 2. 缺点是它**很难解决对象之间相互循环引用的问题**。举个简单的例子,对象 `objA` 和 `obB` 都有字段 `Instance`,赋值令 `obja instance=objB` 及 `obiB. Instance=ojA`,除此之外,这两个对象再无任何引用,实际上这两个对象已经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为 0,于是引用计数算法无法通知 GC 收集器回收它们,具体代码如下: ```java /** * @author peng.wei * @version 1.0 * @date 2021/9/3 16:17 * @Description 引用计数法 GC 测试 */ public class ReferenceCountingGC { public Object instance = null; private static final int _1MB = 1024 * 1024; /** * 这个成员属性的唯一意义就是占点内存,以便能在 GC 日志中看清楚是否有回收过 */ private byte[] bigSize = new byte[2 * _1MB]; public static void main(String[] args) { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; // 假设在这行发生 GC,看一下 objA 和 objB 是否能被回收 System.gc(); } } ``` 运行结果: ```txt [GC (System.gc()) [PSYoungGen: 7449K->679K(38400K)] 7449K->687K(125952K), 0.0019256 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 679K->0K(38400K)] [ParOldGen: 8K->577K(87552K)] 687K->577K(125952K), [Metaspace: 3178K->3178K(1056768K)], 0.0082725 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] Heap PSYoungGen total 38400K, used 998K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000) eden space 33280K, 3% used [0x0000000795580000,0x0000000795679b20,0x0000000797600000) from space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000) to space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000) ParOldGen total 87552K, used 577K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000) object space 87552K, 0% used [0x0000000740000000,0x0000000740090418,0x0000000745580000) Metaspace used 3185K, capacity 4556K, committed 4864K, reserved 1056768K class space used 339K, capacity 392K, committed 512K, reserved 1048576K ``` 从运行结果中可以清除看到内存回收日志中包含「7449K -> 687K」,意味着**虚拟机并没有因为这两个对象相互引用就放弃回收他们**,这也从侧面说明了**Java 虚拟机并不是通过引用计数算法来判断对象是否存活的**。 ## 2. 可达性分析算法 1. 主要**通过一系列的称为“GC Roots”的对象作为起点**,**从这些节点开始向下搜索**,**搜索所走过的路径称为引用链**,**当一个对象到 GC Roots 没有任何引用链相连**(即从 GC Roots 到这个对象不可达)时,则**证明对象是不可用的**。 2. 如下图所示,对象 `object5`、`object6`、`object7` 虽然互有关联,但是他们到 GC Roots 是不可达的,因此他们会被判定为可回收的对象。 ![](/media/202109/2021-09-03_143542_966399.png) 3. 在 Java 技术体系里面,固定可作为 GC Roots 的对下那个包括以下几种: 1. **在虚拟机栈**(栈帧中的本地变量表)**中引用的对象**,譬如**各个线程中被调用的方法堆栈中使用到的参数**、**局部变量**、**临时变量**等。 2. **在方法区中类静态属性引用的对象**,譬如**Java 类的引用类型静态变量**。 3. **在方法区中类静态属性引用的对象**,譬如**Java 类的引用类型静态变量**。 4. **在方法区中常量引用的对象**,譬如**字符串常量池**(String Table)**里的引用**。 5. **在本地方法栈中 JNI**(通常所说的 Native 方法)**引用的对象**。 6. **Java 虚拟机内部的引用**,如**基本数据类型对应的 Class 对象**,**一些常驻的异常对象**(比如 NullPointerException、OutOfMemoryError)等,**还有系统类加载器**。 7. **所有被同步锁**(`synchronized` 关键字)**持有的对象**。 8. **反映 Java 虚拟机内部情况的 JMXBean**、**JVMT1 中注册的回调**、**本地代码缓存等**。 9. 除了这些固定的 GC Roots 集合以外,**根据用户所选用的垃圾收集器以及当前回收的内存区域不同**,还**可以有其他对象临时性地加入**,**共同构成完整 GC Roots 集合**,比如分代收集和局部回收(Partial GC),**如果只针对 Java 堆中某一块区域发起垃圾收集时**(如最典型的只针对新生代的垃圾收集),**必须考虑到内存区域是虚拟机自己的实现细节**(在用户视角里任何内存区域都是不可见的),更不是孤立封闭的,所以**某个区域里的对象完全有可能被位于堆中其他区域的对象所引用**,**这时候就需要将这些关联区域的对象也一并加入 GC Roots 集合中去**,**才能保证可达性分析的正确性**。 4. 即使在可达性分析算法中判定为不可达对象,也不是非死不可的,这时候他们还暂时还处于缓刑阶段,**要真正宣告一个对象死亡**,**至少要经历两次标记过程**: 1. **如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链**,**那他将会被第一次标记**。 2. **随后进行一次筛选**,**筛选的条件是此对象是否有必要执行 `finalize()` 方法**,**假如对象没有覆盖 `finalize()` 方法**,**或者 `finalize()` 已经被虚拟机调用过**,**那么虚拟机将这两种情况都视为「没有必要执行」**。 3. **如果这个对象被判定为「有必要执行」`finalize()` 方法**,**那么该对象会被放置在一个名为 `F-Queue` 的队列之中**,**并在稍后由一条由虚拟机自动建立的**、**低调度优先级的 `Finalizer` 线程去执行他们的 `finalize()` 方法**,这里所说的执行是指**虚拟机会触发这个方法开始运行**,但并**不承诺一定会等待他运行结束**,这样做的原因是,**如果某个对象的 `finalize()` 方法执行缓慢**,**或者更极端地发生了死循环**,**将很可能导致 `F-Queue` 队列中的其他对象永久处于等待**,**甚至导致整个内存回收子系统的崩溃**。 4. `finalize()`**方法是对象逃脱死亡命运的最后一次机会**,**稍后收集器将对 `F-Queue` 中的对象进行第二次小规模的标记**,**如果对象要在 `finalize()` 中成功拯救自己**(只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(`this` 关键字)赋值给某个类变量或者对象的成员变量),**那么第二次标记时他将被移出「即将回收」的集合**,**如果对象这时候还没有逃脱**,**那基本上他就真的要被回收了**,具体的示例代码如下所示: ```java /** * @author peng.wei * @version 1.0 * @date 2021/9/3 15:56 * @Description GC 回收对象在 finalize() 方法中逃脱测试类 */ public class FinalizeEscapeGC { public static FinalizeEscapeGC SAVE_HOOK = null; public void isAlive() { System.out.println("Yes, i am still alive."); } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("finalize method executed."); FinalizeEscapeGC.SAVE_HOOK = this; } public static void main(String[] args) throws InterruptedException { SAVE_HOOK = new FinalizeEscapeGC(); // 对象第一次成功拯救自己 SAVE_HOOK = null; System.gc(); // 因为 Finalizer 线程的优先级很低,所以暂停 0.5 秒,等待他一下 Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else { System.out.println("No, i am dead."); } // 第二次拯救自己,却失败了 SAVE_HOOK = null; System.gc(); // 因为 Finalizer 线程的优先级很低,所以暂停 0.5 秒,等待他一下 Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else { System.out.println("No, i am dead."); } } } ``` 运行结果: ```txt finalize method executed. Yes, i am still alive. No, i am dead. ``` 从运行结果可以看出: 1. `SAVE_HOOK`**对象的 `finaliz()` 方法确实被垃圾收集器触发过**,**并且在被收集前成功逃脱了**。 2. 另外一个值得注意的地方就是,**代码中有两段完全一样的代码片段**,**执行结果却是一次逃脱成功**,**一次失败了**,这是因为**任何一个对象的 `finalize()` 方法都只会被系统自动调用一次**,**如果对象面临下一次回收**,**他的 `finalize()` 方法不会再被执行**,因此**第二段代码的自救行动失败了**。 ## 参考文献 1. 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第 3 版) - 周志明》 ![](/media/202105//1621914618.1032557.png)
ricear
2022年7月11日 22:02
©
BY-NC-ND(4.0)
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码