文章 答疑

Java 高级篇:JVM 垃圾回收机制

整体了解 JDK & JVM

首先要对官方的 SDK 有点认识,同时要明白下面的概念:

  • Java SE(Java Platform, Standard Edition):它是 Java 的标准版,主要用于桌面应用开发,同时也是 Java 的基础,它包含 Java 语言基础、JDBC(Java 数据库连接性)操作、I/O(输出输出)操作、网络通信、多线程等技术。
  • Java EE(Java Platform, Enterprise Edition):它是 Java 的企业版本(javax..*),包含了 Servlet、JSP、JMS、JNDI 等的扩展。
  • Java ME(Java Platform, Micro Edition):一般是指 Java ME Embedded,Java 微型版本,一般做嵌入式开发用。
  • JRE(Java Runtime Environment),Java 运行环境。
  • JDK(Java Development Kit),Java 开发者工具集,是用来编译和执行 Java 程序必备的 Java 开发环境,现在我们一般说 JDK 就是指的 Oracle 的 Java SE。因为 Sun JDK 和 Open JDK、JRockit 都被 Orcale 收购了,一统了江湖。

我们看下 Oracle 官方的图,如下:

JDK全视角

这样我们对 JDK、JRE,有了概念上的认识之后,我们来看下我们的 JVM(Java Virtual Machine)——Java 虚拟机,也就是 HotSport 了。

Oracle 也出了 JVM 的规范。

Java 内存模型

Java 内存模型是理解 Java 多线程和 Java GC 必须要了解的抽象知识点,我们可以通过工具来更好的掌握 Java 内存模型。我给大家一个点:“我们通过不同的视角来理解内存模型”。我们可以通过不同的视角来理解内存模型。因为 JVM 类里面实际的内存操作远比我们想象得要复杂,因为这部分代码是 Oracle 官方的核心机密,没有对外公开,我们也只能通过官方文档及其 Jdk/bin 目录下面的工具来做到整体认识。

站在理解线程的视角看内存模型

enter image description here

我们可以把 JVM 内存结构直接分成线程私有内存和共享主内存。这样我们就可以很好地理解多线程的很多问题如同步锁、lock、validate 关键字,及其 ThreadLocal。这部分内容如果还疑惑的可以看作者的另外一篇 Chat:Java 多线程与并发编程 · Java 工程师必知必会

我们从内存设置的角度出发

enter image description here

我们可以将内存直接分位堆内存、非堆内存(JDK8 以后叫 Metaspace,元空间)和其它,三个大的类别。 我们来看一下 JConsole 和 JVisualVM。

java/bin/jconsole 打开以后界面如下:

enter image description here

java/bin/jvisualvm 打开以后界面如下:

enter image description here

利用 tools,也可以看到 Java 工具也是简单的将其分成堆和非堆(Metaspace)。

而其它是什么呢?

Other 指的是“直接内存”,如一些(IO/NIO),这些 JVM 控制不了(如果线程变多线程栈吃的内存也会变的非常大,不可设置)。

对应的 JVM 设置的参数是:
  • Xmx4g:JVM 最大允许分配的堆内存,按需分配;
  • Xms4g:JVM 初始分配的堆内存,一般和 Xmx 配置成一样以避免每次 gc 后 JVM 重新分配内存;
  • XX:MetaspaceSize=64m 初始化元空间大小;
  • XX:MaxMetaspaceSize=128m 最大化元空间大小。

Metaspace 建议大家不要设置,一般让 JVM 自己启动的时候动态扩容就好了,没必要自己去设置。如果不动态加载 class ,当启动起来的时候,一般是很少有变化的。

从这个角度我们可以认为我们的 JVM 内存的大小是堆+metaspace+io(运行时产生的大小)。

我们从 JVM 的运行期的视角来看

enter image description here

可以分为五大部分:方法区、堆、本地方法栈区、PC 计数器、线程栈。我们也可以看下面的图,PC 计数器和栈、本地方法栈,是随着当前的线程开始而开始,销毁而销毁的。

我们再通过下面这个图理解一下这五个区和线程的关系:

enter image description here

对应的 JVM 的参数为 Xss512k,用来设置每个线程的堆栈大小。

从垃圾回收机制的视角来看

enter image description here

全局分代收集器,我们通过 java/bin/jvisualvm 来观察一下:

enter image description here

通过 JVisualVM 我们可以看得出来:

  • 内存直接被垃圾收集器切分了5个部分:metaspace

2018年4月4日,周三晚上8点30分,Java架构专家,拥有10几年Java开发经验,《Java并发编程从入门到精通》、《Spring Data Jpa从入门到精通》的作者张振华带来了主题为《Java 高级篇之 JVM 垃圾回收机制》的交流。以下是主持人丽辉整理的问答实录,记录了作者和读者间问答的精彩时刻。


内容提要:

  • 递归和 for 循环分别影响的是哪块内存?
  • 垃圾回收的触发时机是什么?
  • 如何判断一个对象该不该回收?
  • 对象四种引用方式是什么,在什么场景用到?
  • -D -XX -X 有什么区别?
  • 问题默认开启的是哪个 GC ?
  • 我们一般需要关注的参数有哪些?
  • 实际生产环境你们设置了哪些参数?
  • 监控的工具有哪些?
  • Java 8 引入了元空间,元空间和永久代有什么不一样?元空间和永久代可以理解为方法区的不同实现吗?
  • 国外有哪些一手JVM资料的获取途径?尤其是在 JVM 规范、 HotSpot 实现方面?
  • 既然实际中很少去配 JVM 参数,为什么面试的时候还总问 JVM 优化的问题?如果缺乏 JVM 调优经验,面试中该如何应对?
  • 能否把对 JVM 的监控集成到系统里面?

问:递归和 for 循环分别影响的是哪块内存?

答:回答这个问题,大家要先搞明白,什么是递归,什么是循环调用?

递归就是方法自己调用自己无限调用下去;循环就是一个方法体内部的while或者for无限循环下去。

如果认真看老师的内存模型其实就不难回答这个问题,递归使方法调用链变长,所以线程栈就会可能不够。而for循环呢?里面可能会有很多对象,所以对堆的影响比较大。


问:垃圾回收的触发时机是什么?

答:我们分成两种情况:

  1. 我们先看 Minor GC 又称年轻代 GC。触发时机是在 Enden 满了之后将会被触发。

  2. 我们再来看一下 Minor GC ,又称Full GC 触发条件,这个比较复杂一点:

(1)调用System.GC时,系统建议执行Full GC,但是不必然执行。(可通过通过-XX:+ DisableExplicitGC来禁止RMI调用System.GC)

(2)老年代空间不足。

(3)方法去空间不足,如果没有动态加载,一般是发生在启动的时候的。

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存。

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。

总之一句话,不管什么场景都是放不下的时候触发,而需要注意的是,“当发生 full GC 之后还不够就会抛出 OutOfMemoryError,一般 fullGC 会伴随这个一次 minor GC”。


问:如何判断一个对象该不该回收?

答:这个问题其实是 GC 的核心。

GC 判断回收的对象是否“存活”或“死去”,主要看这个对象是不是

收藏 收藏
分享
购买文章 ¥9.99