专业的JAVA编程教程与资源

网站首页 > java教程 正文

关于Java虚拟机,了解这几点,效率提升80%

temp10 2024-09-05 23:30:45 java教程 10 ℃ 0 评论

你好,我是你的 JVM 讲师李国,本文来自拉勾教育专栏《深入浅出 Java 虚拟机》。

欢迎来到 05 课时,在前面几个课时中,我们不止一次提到了堆(heap),堆是一个巨大的对象池。在这个对象池中管理着数量巨大的对象实例。

关于Java虚拟机,了解这几点,效率提升80%

而池中对象的引用层次,有的是很深的。一个被频繁调用的接口,每秒生成对象的速度,也是非常可观的。对象之间的关系,形成了一张巨大的网。虽然 Java 一直在营造一种无限内存的氛围,但对象不能只增不减,所以需要垃圾回收。

那 JVM 是如何判断哪些对象应该被回收?哪些应该被保持呢?

在古代,刑罚中有诛九族一说。指的是有些人犯大事时,皇上杀一人不足以平复内心的愤怒时,会对亲朋好友产生连带责任。诛九族时首先需要追溯到一个共同的祖先,再往下细数连坐。堆上的垃圾回收也有同样的思路。我们接下来就具体分析 JVM 中是如何进行垃圾回收的。

JVM 的 GC 动作,是不受程序控制的,它会在满足条件的时候,自动触发。

在发生 GC 的时候,一个对象,JVM 总能够找到引用它的祖先。找到最后,如果发现这个祖先已经名存实亡了,它们都会被清理掉。而能够躲过垃圾回收的那些祖先,比较特殊,它们的名字就叫作 GC Roots。

从 GC Roots 向下追溯、搜索,会产生一个叫作 Reference Chain 的链条。当一个对象不能和任何一个 GC Root 产生关系时,就会被无情的诛杀掉。

如图所示,Obj5、Obj6、Obj7,由于不能和 GC Root 产生关联,发生 GC 时,就会被摧毁。


本文来自拉勾教育专栏《深入浅出 Java 虚拟机》

垃圾回收就是围绕着 GC Roots 去做的。同时,它也是很多内存泄露的根源,因为其他引用根本没有这样的权利。

那么,什么样的对象,才会是 GC Root 呢?这不在于它是什么样的对象,而在于它所处的位置。

GC Roots 有哪些

GC Roots 是一组必须活跃的引用。用通俗的话来说,就是程序接下来通过直接引用或者间接引用,能够访问到的潜在被使用的对象。

GC Roots 包括:

  • Java 线程中,当前所有正在被调用的方法的引用类型参数、局部变量、临时值等。也就是与我们栈帧相关的各种引用。
  • 所有当前被加载的 Java 类。
  • Java 类的引用类型静态变量。
  • 运行时常量池里的引用类型常量(String 或 Class 类型)。
  • JVM 内部数据结构的一些引用,比如 sun.jvm.hotspot.memory.Universe 类。
  • 用于同步的监控对象,比如调用了对象的 wait() 方法。
  • JNI handles,包括 global handles 和 local handles。

这些 GC Roots 大体可以分为三大类,下面这种说法更加好记一些:

  • 活动线程相关的各种引用。
  • 类的静态变量的引用。
  • JNI 引用。

有两个注意点:

  • 我们这里说的是活跃的引用,而不是对象,对象是不能作为 GC Roots 的。
  • GC 过程是找出所有活对象,并把其余空间认定为“无用”;而不是找出所有死掉的对象,并回收它们占用的空间。所以,哪怕 JVM 的堆非常的大,基于 tracing 的 GC 方式,回收速度也会非常快。

引用级别

接下来的一道面试题就有意思多了:能够找到 Reference Chain 的对象,就一定会存活么?

我在面试的时候,经常会问这些问题,比如“弱引用有什么用处”?令我感到奇怪的是,即使是一些工作多年的 Java 工程师,对待这个问题也是一知半解,错失了很多机会。

对象对于另外一个对象的引用,要看关系牢靠不牢靠,可能在链条的其中一环,就断掉了。

根据发生 GC 时,这条链条的表现,可以对这个引用关系进行更加细致的划分。

它们的关系,可以分为强引用、软引用、弱引用、虚引用等。


典型 OOM 场景

OOM 的全称是 Out Of Memory,那我们的内存区域有哪些会发生 OOM 呢?我们可以从内存区域划分图上,看一下彩色部分。

可以看到除了程序计数器,其他区域都有OOM溢出的可能。但是最常见的还是发生在堆上。

所以 OOM 到底是什么引起的呢?有几个原因:

  • 内存的容量太小了,需要扩容,或者需要调整堆的空间。
  • 错误的引用方式,发生了内存泄漏。没有及时的切断与 GC Roots 的关系。比如线程池里的线程,在复用的情况下忘记清理 ThreadLocal 的内容。
  • 接口没有进行范围校验,外部传参超出范围。比如数据库查询时的每页条数等。
  • 对堆外内存无限制的使用。这种情况一旦发生更加严重,会造成操作系统内存耗尽。

典型的内存泄漏场景,原因在于对象没有及时的释放自己的引用。比如一个局部变量,被外部的静态集合引用。

你在平常写代码时,一定要注意这种情况,千万不要为了方便把对象到处引用。即使引用了,也要在合适时机进行手动清理。关于这部分的问题根源排查,我们将在实践课程中详细介绍。

小结

你可以注意到 GC Roots 的专业叫法可达性分析法。另外,还有一种叫作引用计数法的方式,在判断对象的存活问题上,经常被提及。

因为有循环依赖的硬伤,现在主流的 JVM,没有一个是采用引用计数法来实现 GC 的,所以我们大体了解一下就可以。引用计数法是在对象头里维护一个 counter 计数器,被引用一次数量 +1,引用失效记数 -1。计数器为 0 时,就被认为无效。你现在可以忘掉引用计数的方式了。

本课时,我们详细介绍了 GC Roots 都包含哪些内容。HostSpot 采用 tracing 的方式进行 GC,内存回收的速度与处于 living 状态的对象数量有关。

?

讨论了几种典型的 OOM 场景,你可能现在对其概念比较模糊。接下来的课时,我们将详细介绍几个常见的垃圾回收算法,然后对这些 OOM 的场景逐个击破。

下一课时我们将深入剖析垃圾回收 ,记得按时来听课啊,下一课时见。


本文来自拉勾教育专栏《深入浅出 Java 虚拟机》。


版权声明:本文版权归属拉勾教育及该专栏作者,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者必究。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表