万万没想到JVM内存结构的面试题可以问的这么难

32次阅读

共计 2650 个字符,预计需要花费 7 分钟才能阅读完成。

在我的博客中,之前有很多文章介绍过 JVM 内存结构,相信很多看多我文章的朋友对这部分知识都有一定的了解了。

那么,请大家尝试着回答一下以下问题:

1、JVM 管理的内存结构是怎样的?
2、不同的虚拟机在实现运行时内存的时候有什么区别?
3、运行时数据区中哪些区域是线程共享的?哪些是独享的?
4、除了 JVM 运行时内存以外,还有什么区域可以用吗?
5、堆和栈的区别是什么?
6、Java 中的数组是存储在堆上还是栈上的?
7、Java 中的对象创建有多少种方式?
8、Java 中对象创建的过程是怎么样的?
9、Java 中的对象一定在堆上分配内存吗?
10、如何获取堆和栈的 dump 文件?

以上 10 道题,如果您可以全部准确无误的回答的话,那说明你真的很了解 JVM 的内存结构以及内存分配相关的知识了,如果有哪些知识点是不了解的,那么本文正好可以帮你答疑解惑。

1.JVM 管理的内存结构是怎样的?

Java 虚拟机在执行 Java 程序的过程中会把他所管理的内存划分为若干个不同的数据区域。《Java 虚拟机规范》中规定了 JVM 所管理的内存需要包括一下几个运行时区域:

主要包含了 PC 寄存器(程序计数器)、Java 虚拟机栈、本地方法栈、Java 堆、方法区以及运行时常量池。

各个区域有各自不同的作用,关于各个区域的作用就不在本文中相信介绍了。

但是,需要注意的是,上面的区域划分只是逻辑区域,对于有些区域的限制是比较松的,所以不同的虚拟机厂商在实现上,甚至是同一款虚拟机的不同版本也是不尽相同的。

2. 不同的虚拟机在实现运行时内存的时候有什么区别?

前面提到过《Java 虚拟机规范》定义的 JVM 运行时所需的内存区域,不同的虚拟机实现上有所不同,而在这么多区域中,规范对于方法区的管理是最宽松的,规范中关于这部分的描述如下:

方法区在虚拟机启动的时候创建,虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现可以选择在这个区域不实现垃圾收集与压缩。本版本的规范也不限定实现方法区的内存位置和代码编译的管理策略。方法区的容量可以是固定的,也可以随着程序执行的需求动态扩展,并在不需要过多的空间时自行收缩。方法区在实际内存空间站可以是不连续的。

这一规定,可以说是给了虚拟机厂商很大的自由。

虚拟机规范对方法区实现的位置并没有明确要求,在最著名的 HotSopt 虚拟机实现中(在 Java 8 之前),方法区仅是逻辑上的独立区域,在物理上并没有独立于堆而存在,而是位于永久代中。所以,这时候方法区也是可以被垃圾回收的。

实践证明,JVM 中存在着大量的声明短暂的对象,还有一些生命周期比较长的对象。为了对他们采用不同的收集策略,采用了分代收集算法,所以 HotSpot 虚拟机把的根据对象的年龄不同,把堆分位新生代、老年代和永久代。

在 Java 8 中,HotSpot 虚拟机移除了永久代,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace)

3. 运行时数据区中哪些区域是线程共享的?哪些是独享的?

在 JVM 运行时内存区域中,PC 寄存器、虚拟机栈和本地方法栈是线程独享的。

而 Java 堆、方法区是线程共享的。但是值得注意的是,Java 堆其实还未每一个线程单独分配了一块 TLAB 空间,这部分空间在分配时是线程独享的,在使用时是线程共享的。

4. 除了 JVM 运行时内存以外,还有什么区域可以用吗?

除了我们前面介绍的虚拟机运行时数据区以外,还有一部分内存也被频繁使用,他不是运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域,他就是——直接内存。

直接内存的分配不受 Java 堆大小的限制,但是他还是会收到服务器总内存的影响。

在 JDK 1.4 中引入的 NIO 中,引入了一种基于 Channel 和 Buffer 的 I / O 方式,他可以使用 Native 函数直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的应用进行操作。

5. 堆和栈的区别是什么?

堆和栈(虚拟机栈)是完全不同的两块内存区域,一个是线程独享的,一个是线程共享的,二者之间最大的区别就是存储的内容不同:

堆中主要存放对象实例。
栈(局部变量表)中主要存放各种基本数据类型、对象的引用。

6.Java 中的数组是存储在堆上还是栈上的?

在 Java 中,数组同样是一个对象,所以对象在内存中如何存放同样适用于数组;

所以,数组的实例是保存在堆中,而数组的引用是保存在栈上的。

7.Java 中的对象创建有多少种方式?

Java 中有很多方式可以创建一个对象,最简单的方式就是使用 new 关键字。

User user = new User();

除此以外,还可以使用反射机制创建对象:

User user = User.class.newInstance();

或者使用 Constructor 类的 newInstance:

Constructor<User> constructor = User.class.getConstructor();User user = constructor.newInstance();

除此之外还可以使用 clone 方法和反序列化的方式,这两种方式不常用并且代码比较复杂,就不在这里展示了,感兴趣的可以自行了解下。

8.Java 中对象创建的过程是怎么样的?

对于一个普通的 Java 对象的创建,大致过程如下:

1、虚拟机遇到 new 指令,到常量池定位到这个类的符号引用。
2、检查符号引用代表的类是否被加载、解析、初始化过。
3、虚拟机为对象分配内存。
4、虚拟机将分配到的内存空间都初始化为零值。
5、虚拟机对对象进行必要的设置。
6、执行方法,成员变量进行初始化。

9.Java 中的对象一定在堆上分配内存吗?

前面我们说过,Java 堆中主要保存了对象实例,但是,随着 JIT 编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。

其实,在编译期间,JIT 会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力,其中一种重要的技术叫做逃逸分析。

如果 JIT 经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。

10. 如何获取堆和栈的 dump 文件?

Java Dump,Java 虚拟机的运行时快照。将 Java 虚拟机运行时的状态和信息保存到文件。

可以使用在服务器上使用 jmap 命令来获取堆 dump,使用 jstack 命令来获取线程的调用栈 dump。


本文作者:Hollis

阅读原文

本文为云栖社区原创内容,未经允许不得转载。

正文完
 0