面试必问的JVM运行时数据区你了解吗?

 2024-02-09 01:03:17  阅读 0

前言

Java虚拟机的运行时数据区是面试中经常被问到的。 很多概念市面上有各种各样的解释,很多同学可能会感到困惑。

当我们陷入不知道哪种说法正确的境地时,最好的参考就是源代码和规范。

面试的时候,当面试官问你:某某为什么会这样? 当被问到时,如果你回答:因为规范是这样写的,因为源代码是这样写的。

这个答案很有说服力。

因此,在本文描述一些有争议的问题时,优先考虑Java虚拟机规范。

文本 1. 运行时数据区域

永久代是方法区吗_永久数据集_static数据在永久代

Java虚拟机定义了程序执行期间使用的几个运行时数据区域。

其中一些数据区域在Java虚拟机启动时创建,并在虚拟机退出时销毁。 即线程之间共享的区域:堆、方法区、运行时常量池。

其他数据区域由线程划分。 这些数据区域在线程创建时创建,在线程退出时销毁。 即线程之间隔离的区域:程序计数器、Java虚拟机栈、本地方法栈。

1) 程序计数器 ( )

Java虚拟机可以支持多个线程同时执行,每个线程都有自己的程序计数器。 任何时候,每个线程只会执行一个方法的代码,该方法称为该线程的当前方法( )。

如果线程正在执行Java方法(实际上不是),则程序计数器记录正在执行的Java虚拟机字节码指令的地址。 如果正在执行local()方法,则计数器值为empty()。

2)Java虚拟机栈(Java)

每个Java虚拟机线程都有自己私有的Java虚拟机栈,它与线程同时创建,用于存储栈帧。

Java虚拟机栈描述了Java方法执行的内存模型:每个方法执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

每个方法从调用到执行完成的过程对应于将一个栈帧压入虚拟机栈到弹出的过程。

3)本地方法栈( )

本地方法栈和Java虚拟机栈扮演的角色非常相似。 它们之间唯一的区别是Java虚拟机栈是为虚拟机服务来执行Java方法(即字节码),而本地方法栈是为虚拟机服务。 机器使用的local()方法服务。

4)堆

堆是各个线程共享的运行时内存区域,也是所有类实例和数组对象分配内存的区域。

堆是在虚拟机启动时创建的,堆中存储的对象不会被显式释放,而是由垃圾收集器统一管理和回收。

5)方法区(Area)

方法区是各个线程共享的运行时内存区域。 方法区类似于传统语言中编译代码的存储区域。 它存储了每个类的结构信息,如:运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容,还包括一些用于类、实例和接口初始化的特殊方法。

6)运行时常量池(Run-Time Pool)

运行时常量池是类文件中每个类或接口的常量池表(table)的运行时表示。

它包含多种常量,从编译时已知的数字文字到必须在运行时解析的方法和字段引用。 运行时常量池的功能类似于传统编程语言的符号表,但它包含的数据范围比通常的符号表更广泛。

2. Java中有哪几种常量池?

现在我们经常提到的常量池主要有三种:类文件常量池、运行时常量池、字符串常量池。

3、类文件常量池

类文件常量池(class pool)是类文件之一。 类文件包含:幻数、类版本、常量池、访问标志、字段表集合、方发布等信息。

常量池用于存储编译过程中生成的各种文字()和符号引用()。

字面量更接近Java语言层面的常量概念,例如文本字符串、声明为final的常量值等。

符号引用属于编译原理的概念。 符号引用是描述引用目标的一组符号。 符号可以是任何形式的文字,只要使用时能够明确定位目标即可(它与直接引用不同,直接引用通常指向方法区中的本地位置)。 指针、相对偏移量或可以间接定位目标的句柄)。 符号引用主要包括以下几类常量:

常量池中的每个常量都是一个表。 从 JDK 13 开始,常量表中有 17 种不同类型的常量。 17个常量类型所代表的具体含义如图所示。

static数据在永久代_永久代是方法区吗_永久数据集

关于类文件常量池的更多信息,可以阅读周志明的《深入理解Java虚拟机》第6.3.2章。

4.运行时常量池

类文件常量池是在类编译成类文件时生成的。 当类被加载到内存中时,JVM会将类文件常量池中的内容存储到运行时常量池中。

Java虚拟机规范中对运行时常量池的定义如下:

运行时池是类文件中每个类或每个运行时的表。

运行时常量池是类文件中每个类或接口的常量池表(table)的运行时表示。

因此,根据规范定义,可以说运行时常量池是类文件常量池的运行时表示。 每个类在运行时都有自己独立的运行时常量池。

5.字符串常量池

简单来说,VM中的字符串常量池()就是一个哈希表。 全局只有一份,所有类共享。

具体存储的是对象的引用,而不是对象实例本身。 JDK 6及之前的对象实例属于永久代,从JDK 7开始放置在堆中。

根据Java虚拟机规范的定义,堆是Java对象存储的地方。 其他地方不会有Java对象实体。 如果有的话,根据规范的定义,这些地方也是堆的一部分。

6、字符串常量池属于方法区吗?

我认为它不属于。

在阅读本文之前,相信很多同学都会有以下观点:因为运行时常量池属于方法区,所以很多同学认为字符串常量池也应该属于方法区。

但相信看完上面的内容,你就会开始意识到,运行时常量池和字符串常量池其实是两个不同的东西。 当然,在字符串解析的过程中它们会产生关联。

方法区在Java虚拟机规范中定义如下:

Java 拥有一个属于所有 Java 的领域。 该区域是a 的代码区域或a 中的“文本”区域。 它针对每个类,例如运行时池、字段和数据,以及 and 的代码,类 and 中使用的 (§2.9)

在Java虚拟机中,方法区是各个线程共享的运行时内存区域。 方法区类似于传统语言中编译后代码的存储区域,或者类似于操作系统进程中的文本段。 它存储了每个类的结构信息,如:运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容,还包括一些用于初始化类、实例和接口的特殊方法。

这里的关键在于“它存储的是各个类的结构信息”,而字符串常量池并不属于某个类。 字符串常量是全局共享的。 因此,根据规范定义,我们可以说字符串常量池不属于方法区。

那么字符串常量池()存在于哪里呢?

本体存储在(本地内存)中,而不是在永久代中,也不是在方法区中,当然也不是在堆中。

7、运行时常量池和字符串常量池有什么关系?

上面提到了,运行时常量池和字符串常量池在字符串解析过程中是有关系的,如下。

类的运行时常量池中有类型的常量(参见问题3中的表)。 类型常量的解析()过程如下:

首先去字符串常量池()查找是否已经存在对该字符串的引用。 如果是,则直接返回字符串常量池的引用; 如果没有,则在堆中创建该对象并将其存储在字符串常量池中。 保留对其的引用,然后返回该引用。

也就是说,解析()之后,运行时常量池中的类型常量也存储了字符串引用,并且与常驻引用保持一致。

8.#方法

在JDK 7及以后的版本中,该方法的作用如下:如果字符串常量池中已存在该字符串,则直接返回常量池中的引用; 如果没有,则在字符串常量池中保存对该字符串的引用,然后返回该引用。

下面的例子可以很容易验证:

public static void main(String args[]) {
    // 创建2个对象,str持有的是new创建的对象引用
    // 1)驻留(intern)在字符串常量池中的对象
    // 2)new创建的对象
    String str = new String("joonwhee");
    // 字符串常量池中已经有了,返回字符串常量池中的引用
    String str2 = "joonwhee";
    // false,str为new创建的对象引用,str2为字符创常量池中的引用
    System.out.println(str == str2);
    // str修改为字符串常量池的引用,所以下面为true
    str = str.intern();
    // true
    System.out.println(str == str2);
}

9.永久代()

Java 8中删除了永久代。根据官方提案的描述,删除的主要动机是合并 和 ,并且没有永久代。

据我们所知,还有一个重要原因是永久代本身存在很多问题,经常出现OOM,出现很多Bug。

根据官方提案的描述,永久代主要存储三类数据:

1)Class(类元数据),是方法区包含的数据,只不过编译后的字节码放在(本地内存)。

2)、即引用所在字符串常量池中的字符串对象。 字符串常量池仅驻留在引用上,实际对象位于永久代中。

3)类,类静态变量。

永久代被移除后,类被移动到堆中,类被移动到后面的元空间中。

10、永久代和方法区有什么关系?

方法区是Java虚拟机规范中定义的一个逻辑概念,永久代就是方法区的实现。 但永久代和方法区不一样,方法区和永久代也不一样。

永久代不属于方法区。 根据规范:堆是Java对象存储的地方。 这部分应该属于堆,所以永久代不仅仅用来实现方法区。

方法区中JIT编译生成的代码并不是存储在永久代中,而是存储在。因此,可以说方法区不仅仅是由永久代来实现的。

11. 元空间()

元空间是在Java 8移除永久代后引入的,用于替代永久代。 它的本质和永久代类似,都是实现方法区。 不过,元空间和永久代最大的区别在于,元空间不在虚拟机中,而是使用本地内存()。

元空间主要用来存储Class(类元数据),从它的命名就可以看出。

元空间的大小可以通过-XX:参数来限制。 如果不设置该参数,元空间默认限制为机器内存。

12.为什么要引入元空间?

在Java 8之前,Java虚拟机使用永久代来存储类元信息,并使用-XX:、-XX:来控制这块内存的大小。 随着加载的动态类越来越多,这个内存变得不太可控。 设置多大是每个开发者都应该考虑的问题。

如果设置太小,容易发生内存溢出; 如果设置大了,就有点浪费了,虽然这么大的物理内存不会被实际分配。

元空间可以更好地解决内存设置的问题:当我们不指定-XX:时,元空间可以动态调整所使用的内存大小,以容纳不断增加的类数量。

13、元空间能否彻底解决内存溢出(Out Of)问题?

不幸的是,答案是否定的。

并不能完全解决内存溢出的问题,只能说是有所缓解。 当内存用完时,元空间也会发生内存溢出。 最典型的场景是发生内存泄漏时。

终于

我是颜辉,一名坚持分享原创技术信息的程序员。 我的目标是帮助每个人从主要制造商那里获得他们最喜欢的报价。

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码