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

年轻代:很快就要被回收的对象

老年代:长期存在的对象

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

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

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

什么是永久代?JVM里的永久代其实就是我们之前说的方法区

上面那个图里的方法区,其实就是所谓的永久代,可以认为永久代就是放一些类信息的。

方法区内会不会进行垃圾回收?在以下几种情况下,方法区里的类会被回收。

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

“大部分的正常对象,都是优先在新生代分配内存的。”

上面那段代码,虽然我们看代码知道,类静态变量“fetcher”引用的那个“ReplicaFetcher”对象,是会长期存活在内存里的。

但是哪怕是这种对象,其实刚开始你通过“new ReplicaFetcher()”代码来实例化一个对象时,他也是分配在新生代里的。

包括在“loadReplicasFromDisk()”方法中创建的“ReplicaManager”实例对象,也都是一样分配在新生代里的。

实际上垃圾回收他也得有点触发的条件。

其中一个比较常见的场景可能是这样的,假设我们写的代码中创建了N多对象,然后导致Java堆内存里囤积了大量的对象。

然后这些对象都是之前有人引用,比如各种各样的方法中的局部变量,但是现在也都没人引用了。

如果新生代我们预先分配的内存空间,几乎都被全部对象给占满,就会触发一次新生代内存空间的垃圾回收,也称之为“Minor GC”,有的时候我们也叫“Young GC”,他会尝试把新生代里那些没有人引用的垃圾对象,都给回收掉。

长期存活的对象会躲过多次垃圾回收

如果上图中的那个“ReplicaFetcher”对象在新生代中成功躲过10多次垃圾回收,成为一个“老年人”,那么就会被认为是会长期存活在内存里的对象。

然后他会被转移到Java堆内存的老年代中去,顾名思义,老年代就是放这些年龄很大的对象。

老年代会垃圾回收吗?

接着下一个问题就是,老年代里的那些对象会被垃圾回收吗?

答案是肯定的,因为老年代里的对象也有可能随着代码的运行,不再被任何人引用了,就需要被垃圾回收。

被哪些变量引用的对象是不能回收的?

JVM中使用了一种可达性分析算法来判定哪些对象是可以被回收的,哪些对象是不可以被回收的。这个算法的意思,就是说对每个对象,都分析一下有谁在引用他,然后一层一层往上去判断,看是否有一个GC Roots

假设现在上图中“ReplicaManager”对象被局部变量给引用了,那么此时一旦新生代快满了,发生垃圾回收,会去分析这个“ReplicaManager”对象的可达性

这时,发现他是不能被回收的,因为他被人引用了,而且是被局部变量“replicaManager”引用的。在JVM规范中,局部变量就是可以作为GC Roots的。

只要一个对象被局部变量引用了,那么就说明他有一个GC Roots,此时就不能被回收了。

此时在JVM的规范里,静态变量也可以看做是一种GC Roots,此时只要一个对象被GC Roots引用了,就不会去回收他。

一句话总结:只要你的对象被方法的局部变量、类的静态变量给引用了,就不会回收他们。

Java中对象不同的引用类型

关于引用和垃圾回收的关系,大家在这里务必有脑子里要引入一个新的概念,那就是Java里有不同的引用类型。

分别是强引用、软引用、弱引用和虚引用。

强引用,就是类似下面的代码:

blob.png

这个就是最普通的代码,一个变量引用一个对象,只要是强引用的类型,那么垃圾回收的时候绝对不会去回收这个对象的。

接着是软引用,类似下面的代码。

blob.png

就是把“ReplicaManager”实例对象用一个“SoftReference”软引用类型的对象给包裹起来了,此时这个“replicaManager”变量对“ReplicaManager”对象的引用就是软引用了。

正常情况下垃圾回收是不会回收软引用对象的,但是如果你进行垃圾回收之后,发现内存空间还是不够存放新的对象,内存都快溢出了

此时就会把这些软引用对象给回收掉,哪怕他被变量引用了,但是因为他是软引用,所以还是要回收。

接着是弱引用,类似下面的代码。

image-20220913161301092

这个其实非常好解释,你这个弱引用就跟没引用是类似的,如果发生垃圾回收,就会把这个对象回收掉。

虚引用,这个大家其实暂时忽略他也行,因为很少用。

其实这里比较常用的,就是强引用和软引用,强引用就是代表绝对不能回收的对象,软引用就是说有的对象可有可无,如果内存实在不够了,可以回收他。

复制算法
针对新生代的垃圾回收算法,他叫做复制算法

  • 复制算法的基本思想是将内存分为两个相等的部分,每次只使用其中的一半。在垃圾收集时,将活动的对象从当前使用的内存区复制到另一个空闲的内存区,然后清除掉原有的内存区中的所有对象,包括那些已经死亡的对象。这样,复制算法每次都是在两个区域之间进行对象的复制和清除。
  • 复制算法的优点是实现简单,垃圾收集时只需要复制存活的对象,不需要考虑内存中的碎片问题(直接对一块内存空间回收掉垃圾对象,保留存活对象的方法,绝对是不可取的;因为内存碎片太多,很多内存碎片压根儿是没法使用的,会造成大量的内存浪费;Solution:把那些存活的对象转移到另外一块空白的内存中)。此外,因为大多数对象在新生代中生命周期都很短,所以复制的数量相对较少,这使得复制算法在新生代中非常高效。但缺点是需要额外的内存空间来支持两块内存的切换,这在内存较紧张的情况下可能是一个限制。

复制算法的优化:Eden区和Survivor区

把新生代内存区域划分为三块:

1个Eden区,2个Survivor区,其中Eden区占80%内存空间,每一块Survivor区各占10%内存空间,比如说Eden区有800MB内存,每一块Survivor区就100MB内存。平时可以使用的,就是Eden区和其中一块Survivor区,那么相当于就是有900MB的内存是可以使用的。

但是刚开始对象都是分配在Eden区内的,如果Eden区快满了,此时就会触发垃圾回收

此时就会把Eden区中的存活对象都一次性转移到一块空着的Survivor区。接着Eden区就会被清空,然后再次分配新对象到Eden区里,然后就会如上图所示,Eden区和一块Survivor区里是有对象的,其中Survivor区里放的是上一次Minor GC后存活的对象。

如果下次再次Eden区满,那么再次触发Minor GC,就会把Eden区和放着上一次Minor GC后存活对象的Survivor区内的存活对象,转移到另外一块Survivor区去。

老年代垃圾回收算法:

常见的老年代垃圾回收算法

  • 标记-清除(Mark-Sweep):此算法首先标记所有从根集合可达的对象,然后清除所有未标记的对象。标记-清除算法的主要缺点是它会留下大量内存碎片。
  • 标记-整理(Mark-Compact):与标记-清除类似,此算法在标记完存活对象后,将所有存活的对象压缩到堆的一端,从而清除掉死亡对象并消除碎片问题。
  • 复制算法:虽然复制算法主要用于新生代,但在某些特定情况下,也可以在老年代中使用,特别是在内存较为充足时。

触发条件

老年代的垃圾回收通常在以下情况下触发:

  • 老年代空间不足时:当尝试为对象分配空间而失败,且老年代已满或接近满载时。
  • JVM执行了一定时间的运行或内存使用达到某个阈值时:根据JVM的垃圾回收策略和设置,可能定期触发Full GC。
  • 手动触发:开发人员可以通过调用System.gc()等方法来建议JVM执行垃圾回收,尽管具体的垃圾回收时间和行为取决于JVM的实现和当前的垃圾回收策略。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVM(Java虚拟机)的分代模型是一种内存管理策略,将堆内存划分为不同的(Generation),包括年轻(Young Generation)、年代(Old Generation)和永久(Permanent Generation,JDK8之后被元空间(Metaspace)取)。下面是JVM分代模型的执行流程: 1. 初始阶段:当Java应用程序启动时,JVM会为其分配一块初始的堆内存空间。此时,年轻年代都是空的。 2. 对象创建:当Java程序创建对象时,对象会被分配在年轻的Eden区域。如果Eden区域没有足够的空间来存放新创建的对象,就会触发一次垃圾回收(Minor GC)。 3. Minor GC:在Minor GC中,垃圾回收器会扫描年轻的Eden区域和Survivor区域,将不再被引用的对象进行回收。存活的对象会被移动到Survivor区域中的一个空闲区域。 4. 对象晋升:当一个对象经过多次Minor GC后仍然存活,它会被晋升到年代。晋升条件可以根据不同的垃圾回收器而有所不同。 5. Major GC:当年代空间不足时,会触发一次Major GC(也称为Full GC)。在Major GC中,垃圾回收器会扫描整个堆内存,对不再被引用的对象进行回收。 6. 永久/元空间:永久(JDK8之前)或元空间(JDK8及以后)用于存放类的元数据和常量池等信息。当类的元数据不再被使用时,会触发一次永久/元空间的垃圾回收。 7. 内存分配担保:在进行垃圾回收时,如果年代的空间不足以存放新创建的对象,JVM会进行一次内存分配担保。即使触发了垃圾回收,也能保证新创建的对象能够顺利分配到年代
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值