当前位置: 首页 > 科技观察

JVM垃圾收集是如何工作的

时间:2023-03-12 05:33:40 科技观察

掌握Java的内存管理机制对程序员来说不是必须的,但它可以帮助你更好地理解JVM如何处理程序中的变量和类实例。Java之所以如此受欢迎,是因为自动垃圾回收GarbageCollection(GC),这也是Java最重要的特性之一。在本文中,我将解释为什么垃圾回收如此重要。这篇文章的主要内容是:自动分代垃圾回收,JVM划分内存的基础,以及JVM垃圾回收的工作原理。Java内存分配Java程序的内存空间分为以下四个区域:堆:对象实例分配在这个区域。然而,当我们声明一个对象时,并没有在堆上分配内存,只是在栈上创建了对该对象的引用。堆栈区Stack:方法、局部变量、类实例变量都分配在这个区域。代码区代码:该区存放程序的字节码。静态区Static:该区存放程序的静态数据和静态方法。什么是自动垃圾收集?自动垃圾回收是这样一个过程:首先,堆中的所有对象都会被分类为“被引用”和“未被引用”;然后,“未引用的对象”将被标记为等待之后将其删除。其中,“被引用的对象”是指某个程序的某个部分还在使用的对象,“未被引用的对象”是指当前没有被使用的对象。许多编程语言,例如C和C++,都要求程序员手动管理内存的分配和释放。在Java中,这个过程是通过垃圾回收机制自动完成的(尽管你也可以在代码中调用system.gc();来手动触发垃圾回收)。垃圾回收的基本步骤如下:1.标记已使用和未使用的对象在这一步中,已使用和未使用的对象被分别标记。这是一个极其耗时的过程,因为需要扫描内存中的所有对象以确定它们是否正在被使用。Markusedandunusedobjects2.Scan/deleteobjects有两种不同的扫描和删除算法:简单删除(markclean):它的过程很简单,我们只需要删除未引用的对象。然而,后续为新对象分配内存变得困难,因为可用空间是碎片化的。markingcleanupdeletescompaction(标记)的过程:除了删除未引用的对象,我们还压缩引用的对象(未被删除的对象)。这样,新对象的内存分配就相对容易了,内存分配的效率也得到了提升。标记压缩的过程什么是分代垃圾收集,为什么需要它?正如我们在“扫描和删除”模型中看到的,一旦对象不断增长,我们就很难扫描所有未使用的对象来回收内存。然而,一项实验研究表明,大多数在程序执行期间创建的对象的生命周期都很短。由于大多数对象的生存时间都很短,我们可以利用这一事实来提高垃圾收集的效率。我该怎么做?首先,JVM将内存划分为不同的“世代”。接下来,它将所有对象排序到这些内存“世代”中,然后对这些“世代”中的每一个进行垃圾回收。这就是“世代垃圾收集”。堆内存的“分代”和分代垃圾回收过程为了提高垃圾回收中“标记清除”的效率,JVM会将内存分为以下三个“分代”:YoungGeneration、OldGeneration、OldGeneration,PermanentGeneration,PermanentGeneration,HotspotHeap内存结构下面我描述一下每个“代”及其主要特点。新生代中所有新创建的对象都存储在这里。新生代又分为以下两个区域:EdenareaEden:所有新创建的对象都在这里分配内存。幸存者区Survivor分为S0和S1:经过一次垃圾回收后,幸存者对象会被移动到两个幸存者区之一。对象分配发生在新生代的分代垃圾回收称为“minorGC”(LCTT译注:也称为“youngGC”)。MinorGC过程中的每个阶段都是“StopTheWorld”(STW),这会导致其他应用程序暂停,直到垃圾收集完成。这也是二次回收更快的原因。一句话概括:Eden区存放所有新创建的对象,当其可用空间耗尽时,会触发第一次垃圾回收。FillEdenMinorCollection:在这个垃圾收集过程中,所有活着的和死去的对象都被标记了。其中,幸存对象会被移动到S0幸存者区域。当所有幸存的对象都被移动到S0时,未引用的对象将被删除。副本引用的对象S0中的对象的年龄为1,因为它们在次要收集中幸存下来。此时,Eden和S1都是空的。每当清理完成后,伊甸园区域将再次接受新的幸存对象。随着时间的推移,eden和S0中的一些对象被宣告死亡(不再被引用),eden中的空闲空间再次被耗尽(填满),然后minorcollection将再次被触发。ObjectAge这一次标记了Eden和S0中的dead和alive对象。其中,Eden区存活的对象会被移动到S1,age会增加到1。S0中的存活对象也会移动到S1,它们的age会增加到2(因为它们存活了两次minorcollections).此时,Eden和S0又是空的。在每次小收集之后,伊甸园区域和两个幸存者区域之一将是空的。新对象总是在伊甸园区被创建,循环又开始。当下一次垃圾回收发生时,Eden区和S1都会被清理干净,其中存活的对象会被移动到S0区。在每次次要收集之后,交换两个幸存者区域(S0和S1)。增加附加年龄的过程会一直持续到某个存活对象的年龄达到一定的阈值,然后将其移动到一个叫做“老年代”的地方,这是通过一个叫做“提升”的过程来实现的。完全的。使用-Xmn选项设置新生代的大小。老年代是一个区域,里面存放着经过多次次要收集而幸存下来并达到一定年龄阈值的对象。提升在上面的示例图中,提升的年龄阈值为8。发生在老年代的垃圾收集称为“主要GC”。(LCTT译注:也称为“FullGC”)使用-Xms和-Xmx选项分别设置初始和最大堆内存大小。(LCTT译注:结合上面的-Xmn选项,可以间接设置老年代的大小。)永久代Permanentgeneration存储一些元数据,这些元数据与应用程序、Java标准环境、库类相关和JVM本身使用的方法。JVM在运行时会填入相应的数据,使用了哪些类和方法。当JVM发现未使用的类时,它会卸载或回收它们以为正在使用的类腾出空间。使用-XX:PermGen和-XX:MaxPerGen选项分别设置初始和最大永久代大小。MetaspaceJava8引入了Metaspace并用它取代了永久代。这样做的好处是会自动调整大小以避免OutOfMemory(OOM)错误。总结本文讨论了JVM内存的各种“代”,以及它们如何在分代垃圾收集算法中发挥作用。对于程序员来说,掌握Java的内存管理机制不是必须的,但可以帮助你更好地理解JVM在程序中处理变量和类实例的方式。这种理解使您能够规划和排除代码故障,并了解特定平台固有的潜在限制。