Code前端首页关于Code前端联系我们

JVM回顾:Java虚拟机运行时数据区域划分、垃圾回收算法

terry 2年前 (2023-09-25) 阅读数 51 #后端开发

2.运行时数据区域

Java虚拟机在程序执行过程中定义了多个数据区域,其中一些数据区域在Java虚拟机中machine 是被创建的,一旦创建就不会被销毁,直到虚拟机终止。根据Java虚拟机的定义,我们可以在数据区进行如下区分,分为:堆、Java虚拟机栈、程序计数器、方法区(元数据区、运行时常量池、本地方法栈)。下面我们将详细介绍每个区域的作用

复习JVM:Java虚拟机运行时数据区域划分,垃圾回收算法

2.1 程序计数器

程序计数器是线程私有区域,它是一小块内存,用于存储当前线程执行的字节码的指令地址如果执行的是本地方法(Native),计数器将为空(Undefined)。

2.2 Java虚拟机栈

Java虚拟机栈是线程的私有区域,生命周期是和线程一样,它存储的是栈帧(Stack Frame),栈帧会存储局部变量表、操作数栈、动态链接、方法出口、返回地址等信息。每个方法从调用到执行的过程对应进入虚拟机堆栈的堆栈帧。从入栈到出栈的过程。如果线程请求的堆栈深度大于虚拟机允许的最大深度,则会抛出StackOverflowError异常;如果应用程序堆栈内存不够,也会抛出OutOfMemoryError异常。

Jvm参数 
-Xss:栈空间大小;栈的空间大小决定了栈能创建的深度

栈结构如下:

复习JVM:Java虚拟机运行时数据区域划分,垃圾回收算法

2.3 本地方法栈

本地方法栈与Java方法栈非常相似。它们之间的主要区别在于,Java方法栈是为字节码服务提供的,而本地方法栈是为本地方法(用C语言实现)调用服务的。 Java虚拟机对本地方法栈所使用的语言、数据结构等没有强制的规则,因此虚拟机可以自行实现。 Sun HotSpot虚拟机将虚拟机堆栈和Java方法堆栈合二为一。本地方法堆栈也会像虚拟机堆栈一样抛出StackOverFlowError和OutOfMemoryError异常。

2.4 堆

Java 堆是所有线程共享的区域。堆用于存储几乎所有的对象实例和数组。人群按照一代的思想,可以分为新生代(YoungGeneration)和老一代(Old/Tenured Generation)。新生代又可以分为誓言、生存空间0(s0或来自空间)和生存空间1(s1或去空间)。我们用一个图来表示栈的划分:

复习JVM:Java虚拟机运行时数据区域划分,垃圾回收算法

eden区:新对象一般都放置在这个区域。除非是新创建一个大对象,如果区域装不下,就会直接存储在老年代(Tenured)。 S0和S1区域:位于该区域的对象至少经历过一次垃圾回收(Minor GC)。如果经历了多次聚集,达到规定次数后仍然存活,就会被转移到老年代。

Java虚拟机规范规定堆可以是物理上不连续的空间,只需要逻辑上连续即可。我们可以通过命令(-Xmx 和 -Xms)调整堆空间。如果使用的堆内存超过堆最大内存将会抛出 OutOfMemoryError 异常。

Jvm参数
 -Xmx:最大堆空间大小
 -Xms:最小堆空间大小
 -Xmn:新生代空间大小

2.5 方法区(元数据区)

方法区是线程共享的区域。用于存储虚拟机已经加载的类信息、常量池、静态变量、即时编译器编译的代码等数据。 。类信息包括类的全名、超类的全名、类型修饰符(public/protected/private)、类型的直接接口类表;常量池是指运行时常量池(后面会介绍);方法区也称为非堆。

在Host Spot虚拟机实现中,方法区也称为永久区,是独立于Java堆的内存空间。虽然叫永久区,但是永久区中的对象也是可以被GC复用的(注:方法区是JVM的一种规范,永久区是具体的实现。在Java8中,永久区已经被相应地,JVM 参数 PermSize 和 MaxPermSize 替换为 MetaSpaceSize 和 MaxMetaSpaceSize )。永久区GC的复用通常从两个方面来分析:一是GC永久区常量池的复用;二是GC的永久区常量池的复用。另一个是永久区中类元数据的复用。

当方法作用域无法满足内存分配要求时,会抛出OutOfMemoryError异常。

2.5.1 运行时常量池

运行时常量池是方法区的一部分。主要用于存储编译过程中生成的各种文字和符号引用。既然已经运行了。常量池当然可以存储运行时生成的常量。例如,调用 String.intern() 方法生成的字符串常量将被放入运行时池常量中。

3。垃圾判断算法

3.1 引用计数算法

引用计数方法的思想比较简单。每个对象都有一个引用计数器。只要该对象被引用,计数器就会+1。当不再引用该对象时,计数器减一。这个算法效率很高,但是有一个致命的缺陷,就是循环引用的问题。两个无用对象的相互引用会导致两个对象的计数器不为零,因此不能判断为无用对象,内存也无法复用。

3.2可达性分析算法

由于引用计数方法存在相互引用的缺陷,因此Java虚拟机采用可达性分析算法来确定垃圾对象。该算法的思想是使用一系列称为“GC Roots”的对象作为起点,并从这些起点向下搜索。搜索所覆盖的路径称为参考链。当一个对象到达 GC Roots 时没有任何引用链(从 GC Roots 到这个对象不可用)时,就意味着这个对象不可用并且不可重用。

可用作GC根的对象包括:

  • 虚拟机堆栈中引用的对象(局部变量)。
  • 方法范围内的类静态属性引用的对象(静态变量)。
  • 方法范围内常量引用的对象(常量)。
  • 本地方法栈中JNI(即本地方法)引用的对象。

那为什么上面四个对象可以作为GC root呢?

1。虚拟机栈中当前引用的对象,因为虚拟机栈中的对象随着线程的生命周期而存活,那么在垃圾求值期间当前线程仍然存活,也就是说栈中的对象仍然存活。有些对象肯定是活着的,所以它们可以用作 GC Roots。这同样适用于本地方法堆栈。

2。关于方法作用域中的静态变量引用和常量,我的理解是,使用方法作用域中的对象作为GC Roots并不一定意味着你将其中的所有对象都用作GC Roots,尽管Java虚拟机没有规定方法区必须被重用,但是这个区域在当前的JVM实现中是被重用的。由于方法范围也会重用“过时的常量”和“无用的类”,因此选择GC Roots只会选择方法范围内的有效对象。 。 “废弃常量”的判断比较简单。对于“无用类”的评估,Java虚拟机只会评估动态加载的类。对于最初加载的类,虚拟机永远不会自动删除它们。因此,判断一个动态加载的类是否无用,可以采用以下原则:

  • 该类的所有实例都已被重用,并且堆上没有该类的实例。
  • 该类的类加载器已被复用
  • 该类对应的Java.lang.Class对象没有在任何地方被引用,并且无法通过反射访问该类的方法。

复习JVM:Java虚拟机运行时数据区域划分,垃圾回收算法

4。垃圾收集算法

4.1 标记清除算法

标记清除算法分为“标记”和“清除”两个步骤。首先标记要回收的物品,标记完成后继续。废物收集总量。该算法有两个缺点: 1. 效率不高; 2、清除后会产生大量不连续的内存碎片。内存碎片会导致分配大对象时发现内存不足,从而抢先触发GC。

复习JVM:Java虚拟机运行时数据区域划分,垃圾回收算法

4.2 复制算法

上述清除标记的算法效率不高。为了解决这个问题,有一种复制算法。复制算法将内存容量分成两个相等的块,并且一次仅使用其中一个作为一块内存。使用完毕后,将幸存的对象复制到另一个内存块,然后清理原来的内存块。该算法的优点是内存分配不需要考虑碎片问题。只需要将内存指针移到堆顶,按顺序分配内存即可。但该算法的缺点是空间利用率不高。内存减少到原来的一半,一半的内存实际上没有被利用。

虽然内存利用率不高,但当前虚拟机中的新生代堆采用的是这种垃圾回收算法。上面我们提到新生代分为三个部分:伊甸空间、形态空间和二次空间。 From和to空间可以认为是两块大小相同、状态相同的空间进行复制,它们的角色可以互换。 from和to房间也称为survivor room,即幸存者房间,用于存放未回收的物品。

在垃圾回收期间,Eden 空间中的幸存项将被复制到未使用的 Survivor 空间(假设为 on),已使用的 Survivor 空间(假设为 off)中的年轻项也会被复制到 to。 space(大的item或者较老的item直接进入老年代,如果空间满了,item也会进入老年代)。此时,伊甸园中和空间中剩余的对象都是废弃对象,直接清空。他们在房间里存放了这次回收后幸存下来的物品。

复习JVM:Java虚拟机运行时数据区域划分,垃圾回收算法

4.3 标记压缩算法

复制算法只适合新生代,存活率较低。如果存活率高,则需要进行太多的复制操作,效率就会降低。老年代的存活率比较高,因此复制算法不太适合老年代的场景。之前提到的“标记-清除”算法,如果不产生内存碎片,还是可以满足老年代的。那么有没有什么方法可以不产生内存碎片呢?类似的片段算法怎么样?答案是肯定的,“标记排序”算法很实用。其核心思想是:首先标记可重用的对象,然后将所有幸存的对象移至一端,然后直接清理边界意外的内存区域。因为清理后,幸存的对象在一端紧密地堆积在一起,因此不会出现内存碎片。

复习JVM:Java虚拟机运行时数据区域划分,垃圾回收算法

8。总结

这篇文章我整理了Java虚拟机运行区域的划分,各个区域的作用,并分享了垃圾评估算法和垃圾回收算法。运行时数据区分为:程序计数器、Java虚拟机栈、本地方法栈、堆、方法区、运行时常量池。有文章提到Jdk1.7及更高版本从方法作用域中删除了运行时常量。这里我想说明一下,Java虚拟机规范仍然要求在方法区进行分配。这只是单个虚拟机的实现,例如Say Hot Spot虚拟机。

垃圾判断算法 目前,虚拟机主要采用可用性分析算法。垃圾收集算法包括“标记-清除”算法、“复制”算法和“标记-组织”算法。 “复制”算法更适合存活对象较少的新生代,“标记排序”算法更适合老年代。排序的目的是拥有连续的内存空间,防止过多的内存碎片存放大对象。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门