06、JVM 调优实战 - JVM分代模型:年轻代、老年代、永久代

1. 背景引入

JVM内存的分代模型: 年轻代、老年代、永久代

我们在代码里创建的对象,都会进入到 Java 堆内存中,方法的栈帧都会压入到Java虚拟机栈里,而方法如果有局部变量,该局部变量就会在方法所对应栈帧里去引用Java对列出里的对象实例。

 

最终,就会执行该对象的方法。比如上图的 ReplicaManager对象的 load() 方法。

2. 大部分对象都是存活周期极短的

 

上述的代码中, ReplicaManager 对象,是属于短暂存活的一个对象。当最终执行到 replicaManager.load() 方法完毕, loadReplicasFromDisk()方法就会结束。

一旦方法结束,那么 loadReplicasFromDisk()方法的栈帧就会出栈。

此时一旦没人引用这个 ReplicaManager 对象,就会被 JVM 的垃圾回收线程给回收掉,释放内存空间。

然后在main() 方法的 while 循环里,下一次循环再次执行 loadReplicasFromDisk()方法的时候,又会走一遍上面的程序,重新将方法入栈,将实例对象放在Java堆里,将栈帧的局部变量指向Java堆内存的实例。

最后在load() 方法执行完毕后,loadReplicasFromDisk()方法结束,又再次被JVM垃圾回收线程回收掉。

从这段代码和分析,可以得知,大部分在我们代码里创建的对象,其实都是存活周期很短的。这种对象,在我们写的 Java 代码中,占到绝大部分的比例。

3. 少数对象是长期存活的

 

上述代码中,给 Kafka 类定义了一个静态变量,也就是 “replicaManager” ,这个 Kafka 类是在 JVM 的方法区里的。

然后让“replicaManager” 引用了一个在 Java 堆内存里创建的 ReplicaManager 实例对象,如下图

 

接着在main() 方法中,就会在 while 循环里不停的调用 ReplicaManager 对象的 load() 方法,做成一个周期性运行的模式。

这个时候,这个 ReplicaManager 实例对象,是会一直被 Kafka 的静态变量引用的,然后会一直驻留在 Java 堆内存里,是不会被垃圾回收掉的。

因为该实例对象需要长期被使用,周期性的被调用 load() 方法,所以它就成为了一个长时间存在的对象,轻易不会被垃圾回收

4. JVM 分代模型: 年轻代和老年代

已知,根据写代码方式的不同,采用不同的方式来创建和使用对象,其实对象的生存周期是不同的。

所以JVM 将 Java堆内存划分为了两个区域,一个是年轻代,一个是老年代

年轻代,就是把第一种代码示例中,创建和使用完之后立马就要回收的对象放在里面。

老年代,就是把第二种代码示例中,创建之后需要一直长期存在的对象放在里面。

 

如下示例代码:

 

在上述代码中, Kafka 的静态变量“fetcher” 引用了 ReplicaFetcher 对象,是长期需要驻留在内存里面使用的。

该对象会在年轻代停留一会儿,最终会进入老年代。

 

进入main() 方法之后,会先调用loadReplicasFromDisk() 方法,业务含义是系统启动就从磁盘加载一次副本数据,这个方法的栈帧会入栈。

然后在这个方法里面创建 ReplicaManager 对象,这个对象用完就会回收,所以是放在年轻代里的,由栈帧里的局部变量来引用。

如下图:

 

然后一旦 loadReplicasFromDisk() 方法执行完毕,方法的栈帧就会出栈,对应的年轻代里的 ReplicaManager对象也会被回收掉。如下图:

 

接着会执行一段 while循环代码,他会周期性的调用 ReplicaFetcher 的 fetch() 方法,去从远程加载副本数据。

所以ReplicaFetcher 这个对象以为被 Kafka 类的静态变量 fetcher 给引用了,所以它会长期存在于老年代里,持续被使用。

5. 为什么要分成年轻代和老年代?

主要和垃圾回收有关,对于年轻代里的对象,他们的特点是创建之后很快就会被回收,所以需要一种垃圾回收算法;对于老年代里的对象,他们的特点是需要长期存在,所以需要另外一种垃圾回收算法,所以需要分成两个区域来放不同的对象

6. 什么是永久代

JVM里的永久代就是 前面的方法区。永久代里就是放一些类信息的。

7. 思考题

方法区会不会进行垃圾回收?

答:以下几种情况,方法区会进行垃圾回收。

  • 首先该类的所有实例对象都已经从 Java 堆内存里被回收
  • 其次加载这个类的 ClassLoader 已经被回收
  • 最后,对该类的 Class 对象没有任何引用

满足上面三个条件就可以回收该类了。