文档章节

【求解惑】由一个Bug来看Java内存模型和垃圾回收

J
 Java领航员
发布于 05/27 16:42
字数 1653
阅读 5
收藏 0

 

前两天,项目中发现一个Bug。我们使用的 RocketMQ ,在服务启动后会创建MQ的消费者实例,来订阅topic。测试过程中,发现服务启动一段时间后,与 RocketMQ 的连接就会断掉,从而找不到订阅关系,监听不到数据。

一、Bug的产生

经过回溯代码,发现订阅的逻辑是这样的。将 ConsumerStarter 类注册到Spring,并通过 PostConstruct 注解触发初始化方法,完成MQ消费者的创建和订阅。

上面代码中的 Subscriber 类是同事写的一个工具类,订阅的时候都调用这里。这里面也不复杂,就是调用 RocketMQ ,完成创建和订阅。

1、finalize

上面的代码看起来平平无奇,但实际上他重写了 finalize 方法。并且在里面执行了 consumer.shutdown() ,将 RocketMQ 断开了,这里是诱因。

finalize 是 Object 中的方法。在GC(垃圾回收器)决定回收一个不被其他对象引用的对象时调用。子类覆写 finalize 方法来处置系统资源或是负责清除操作。

回到项目中,他这样的写法就是在 Subscriber 类被回收的时候,断开 RokcketMQ 的连接,因而产生了Bug。最简单的方式就是把 shutdown 这句代码删掉,但这似乎不是好的解决方案。

2、为何被回收

在Java的内存模型中,有一个 虚拟机栈 ,它是线程私有的。

虚拟机栈是线程私有的,每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈,虚拟机栈表示Java方法执行的内存模型,每调用一个方法就会为每个方法生成一个栈帧(Stack Frame),用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用和完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。虚拟机栈的生命周期和线程是相同的

在上面的 ConsumerStarter.init() 方法中, Subscriber subscriber = new Subscriber() 被定义成了局部变量,在方法执行完毕后,变量就没有了引用,会被销毁。

很快,我就有了新的想法,将 Subscriber 定义成 ConsumerStarter 类中的成员变量也是可以的,因为 ConsumerStarter 是注册到了 Spring 中。在Bean的生命周期内,不会被回收。

如上代码,把 subscriber 作用域提到类级别,事实证明这样也是没问题的。

还有个更优的方案是,将 Subscriber 直接注册到 Spring 中,由 PostConstruct 注解触发初始化完成对MQ的创建和订阅;由 PreDestroy 注解完成资源的释放。这样,资源的创建和销毁跟Bean的生命周期绑定,也是没问题的。

到目前为止,这个Bug的原因和解决方案都有了。但还有个问题,笔者一时没想明白。

二、疑问点

为了确定哪些对象是垃圾,在Java中使用了可达性分析的方法。

它通过通过一系列的 GC roots 对象作为起点搜索。从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

在Java语言中,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。

  • 方法区中类静态属性引用的对象。

  • 方法区中常量引用的对象。

  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

结合代码来看,虚拟机栈中引用的对象是 subscriber ,而 subscriber 对象中又包含了 Consumer 对象。 Consumer 对象是在 RocketMQ 中创建的,并且调用了它的 consumer.start 方法。

我大概看了下 RocketMQ ,作为一个 Consumer 实例,它肯定会定期从 Name Server 拉取消息;并且定时向服务器发生心跳。而且在 RocketMQ 代码中,我也看到了 ScheduledExecutorService 这种定时器的启动。

那么,这一切说明, subscriber 类的 consumer 的实例是活跃的呀,它们之间是可达的,不应该被回收吧?

这个问题也可以被描述成:如果A对象没有了引用,是确定可以被回收的 比如局部变量subscriber,方法执行完应该就被销毁 ;但是如果A对象中还有线程在活跃, 比如在活跃的线程是consumer实例 ,此时A对象还会被回收吗?

此处可能逻辑是错误的,也是笔者没能理解的地方。望大佬指正、解惑。

然后,基于上面的问题,笔者又做了两个测试。

回到上面项目中的代码,此时我还是将 Subscriber 定义成局部变量,这样在GC的时候,它还是要被回收的。在这里,可以通过 System.gc(); 来手动触发GC。

1、在Subscriber类中新建线程

在 Subscriber 类中,通过 new Thread().start(); 的方式来创建一个线程并调用它的启动方法,整体代码如下:

如果是这种情况,当触发GC的时候, Subscriber 类不会被回收, finalize 方法也没有被调用,线程还会持续输出。

2、在Subscriber类中调用其他线程类

首先定义一个线程类 MyThread1 ,它的run方法也是死循环。

然后在 Subscriber 类中通过 MyThread1 thread1 = new MyThread1(); 实例化。

然后通过 new Thread(thread1).start(); 来启动它。

此时,如果触发GC, Subscriber 类照样会被回收, finalize 方法也会被调用,但 thread1线程仍然还会持续输出。

通过这两个测试,我更不太明白了。都是在 Subscriber 类中启动新的线程,为什么结果却不同呢?

是因为在测试1中,本类的线程还未执行结束,方法未结束吗?

请大佬们带着批判的目光审视第二部分,其中逻辑可能有误,请大佬们不吝赐教。如果一两句话扯不清楚,也希望有大佬可以专门写篇文章讲讲这里面的逻辑误区~

© 著作权归作者所有

J
粉丝 1
博文 45
码字总数 110128
作品 0
朝阳
私信 提问
Java虚拟机必学之四大知识要点你掌握了吗?

作为一位 Java 程序员,在尽情享受 Java 虚拟机带来好处的同时,我们还应该去了解和思考“这些技术特性是如何实现的”,去了解最底层的原理。只有熟悉 JVM,你才能在遇到 OutOfMemory 等异常...

Java干货分享
2018/10/17
52
0
2019年面试必备:最新Java核心知识点(3)—JAVA多线程并发(上)

核心知识——JVM jvm基本概念: JVM 是可运行 Java 代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、 一个垃圾回收,堆 和 一个存储方法域JVM 是运行在操作系统之上的,它与硬...

我最喜欢三大框架
06/05
12
0
JVM系列第8讲:JVM 垃圾回收机制

在第 6 讲中我们说到 Java 虚拟机的内存结构,提到了这部分的规范其实是由《Java 虚拟机规范》指定的,每个 Java 虚拟机可能都有不同的实现。其实涉及到 Java 虚拟机的内存,就不得不谈到 Ja...

陈树义
2018/11/21
0
0
深入理解Java的分级引用模型

本文通过探析Java中的引用模型,分析比较强引用、软引用、弱引用、虚引用的概念及使用场景,知其然且知其所以然,希望给大家在实际开发实践、学习开源项目提供参考。 1 Java的引用 对于Java中...

Java干货分享
2018/10/12
1K
2
《成神之路-基础篇》JVM——垃圾回收(已完结)

Java内存模型,Java内存管理,Java堆和栈,垃圾回收 本文是[《成神之路系列文章》][1]的第一篇,主要是关于JVM的一些介绍。 持续更新中 Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收 ...

2018/05/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

64.监控平台介绍 安装zabbix 忘记admin密码

19.1 Linux监控平台介绍 19.2 zabbix监控介绍 19.3/19.4/19.6 安装zabbix 19.5 忘记Admin密码如何做 19.1 Linux监控平台介绍: 常见开源监控软件 ~1.cacti、nagios、zabbix、smokeping、ope...

oschina130111
今天
13
0
当餐饮遇上大数据,嗯真香!

之前去开了一场会,主题是「餐饮领袖新零售峰会」。认真听完了餐饮前辈和新秀们的分享,觉得获益匪浅,把脑子里的核心纪要整理了一下,今天和大家做一个简单的分享,欢迎感兴趣的小伙伴一起交...

数澜科技
今天
7
0
DNS-over-HTTPS 的下一代是 DNS ON BLOCKCHAIN

本文作者:PETER LAI ,是 Diode 的区块链工程师。在进入软件开发领域之前,他主要是在做工商管理相关工作。Peter Lai 也是一位活跃的开源贡献者。目前,他正在与 Diode 团队一起开发基于区块...

红薯
今天
12
0
CC攻击带来的危害我们该如何防御?

随着网络的发展带给我们很多的便利,但是同时也带给我们一些网站安全问题,网络攻击就是常见的网站安全问题。其中作为站长最常见的就是CC攻击,CC攻击是网络攻击方式的一种,是一种比较常见的...

云漫网络Ruan
今天
12
0
实验分析性专业硕士提纲撰写要点

为什么您需要研究论文的提纲? 首先当您进行研究时,您需要聚集许多信息和想法,研究论文提纲可以较好地组织你的想法, 了解您研究资料的流畅度和程度。确保你写作时不会错过任何重要资料以此...

论文辅导员
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部