JVM 内存模型一共包括三个部分:

  • 堆 ( Java代码可及的 Java堆 和 JVM自身使用的方法区)、
  • 栈 ( 服务Java方法的虚拟机栈 和 服务Native方法的本地方法栈 )
  • 保证程序在多线程环境下能够连续执行的程序计数器

Eden区,s0区,s1区,默认数据大小为8 : 1 : 1

新生代: 老年代 默认为 1:2

年轻代

年轻代用来存放新近创建的对象,尺寸随堆大小的增大和减小而相应的变化,默认值是保持为堆大小的1/15,可以通过 -Xmn 参数设置年轻代为固定大小,也可以通过 -XX:NewRatio 来设置年轻代与年老代的大小比例,年青代的特点是对象更新速度快,在短时间内产生大量的“死亡对象”

年轻代的特点是产生大量的死亡对象,并且要是产生连续可用的空间, 所以使用复制清除算法和并行收集器进行垃圾回收.对年轻代的垃圾回收称作初级回收 (minor gc)

新生代是 GC 收集垃圾的频繁区域。 当对象在 Eden 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳,则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域中,然后清理所使用过的 Eden 以及 Survivor 区域,并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代

对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代 - 分配担保机制

大对象意味着高额的内存复制的成本

老年代

Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法

老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。 另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作

永久代

永久代是Hotspot虚拟机特有的概念,是方法区的一种实现,别的JVM都没有这个东西。在Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间。

永久代或者“Perm Gen”包含了JVM需要的应用元数据,这些元数据描述了在应用里使用的类和方法。注意,永久代不是Java堆内存的一部分。永久代存放JVM运行时使用的类。永久代同样包含了Java SE库的类和方法。永久代的对象在full GC时进行垃圾收集

GC 流程

新创建一个对象,首先判断能否放到Eden区,如果Eden区满了,会触发mirrorGc「所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程,但这段时间可以忽略不计」。此时Eden区和s0区中存活的对象移至s1区,并标志对象的分代年龄,eden区和s0区清空,如果此时对象还无法放置eden区,则直接放置老年代。反之亦然

一般情况新创建的对象会放到新生代中,只有经过一定次数的GC后还没有被回收的对象,我们认为这部分对象在未来也会长时间存在,所以会把这部分的对象转移到老年代的区域中去

分代年龄存储到java对象头中。如果old区满了,会触发fullGc,我们尽量避免fullGc「fullGc暂停所有正在执行的线程(Stop The World),来回收内存空间,这个时间需要考虑地

垃圾回收的流程类似下面几个步骤

1、 第一次GC会把Eden区的不可回收对象拷贝到S1区,然后把Eden区的所有对象进行清理。

2、第二次GC会把Eden区和S1区不可回收的对象拷贝到S2区,然后把Eden区和S1区的所有对象进行清理。

3、第三次GC会把Eden区和S2区不可回收的对象拷贝到S1区,然后把Eden区和S2区的所有对象进行清理。

每次GC都都能保证S区有一块空白连续的内存可以提供我们使用

https://cdn.nlark.com/yuque/0/2023/png/22813151/1672971107747-74b2e761-616a-48aa-8437-9d2013a6188a.png

垃圾回收算法

常见的垃圾回收算法有标记-清除算法、复制算法、标记整理算法和分代收集算法。以下是它们的对比:

  1. 标记-清除算法:这是一种最古老的垃圾回收算法,它分为标记和清除两个阶段。在标记阶段,垃圾回收器标记所有还在使用的对象;在清除阶段,垃圾回收器清除所有未被标记的对象。由于需要扫描整个堆内存,因此标记-清除算法的效率较低,同时容易产生内存碎片。
  2. 复制算法:这是一种将堆内存分为两个区域的垃圾回收算法。在第一个区域中,存放正在使用的对象;在第二个区域中,存放新创建的对象。当第一个区域满了之后,将第一个区域中的存活对象复制到第二个区域中,然后清空第一个区域。由于只需要扫描存活对象,因此复制算法的效率较高,但需要额外的内存空间用于复制。
  3. 标记整理算法:这是一种改进版的标记-清除算法,它能够解决内存碎片的问题。在标记阶段,垃圾回收器标记所有还在使用的对象;在整理阶段,垃圾回收器将所有存活对象移动到堆内存的一端,然后清空另一端的内存空间。这样可以避免出现内存碎片,但需要额外的整理操作。
  4. 分代收集算法:这是一种将堆内存分为新生代和老年代两个区域的垃圾回收算法。新生代中存放新创建的对象,老年代中存放已经存活一段时间的对象。由于新生代中对象的生命周期较短,因此可以使用复制算法进行垃圾回收,而老年代中对象的生命周期较长,因此可以使用标记整理算法进行垃圾回收。

常见的Java垃圾回收器包括串行垃圾回收器(Serial GC)、并行垃圾回收器(Parallel GC)、CMS垃圾回收器、G1垃圾回收器等。这些垃圾回收器在实现上采用了不同的垃圾回收算法和优化策略,以适应不同的应用场景。比如:

  1. 串行垃圾回收器:使用标记-清除算法,适用于单线程环境下的小型应用程序。
  2. 并行垃圾回收器:使用复制算法或标记整理算法,适用于多核CPU环境下的中小型应用程序。
  3. CMS垃圾回收器:使用标记-清除算法和并发标记-清除算法,适用于对响应时间有较高要求的大型应用程序。
  4. G1垃圾回收器:使用分代收集算法和标记整理算法,适用于具有大量对象和大量并发线程的大型应用程序。