当前位置:首页 > Java技术 > 不止面试02-JVM内存模型面试题详解

不止面试02-JVM内存模型面试题详解

2022年08月06日 08:43:22Java技术4

第一部分:面试题

本篇文章我们将尝试回答以下问题:

  1. 描述一下jvm的内存结构
  2. 描述一下jvm的内存模型
  3. 谈一下你对常量池的理解
  4. 什么情况下会发生栈内存溢出?和内存溢出有什么不同?
  5. String str = new String(“abc”)创建了多少个实例?

第二部分:深入原理

ok,开始。怎们还是先讲原理,再说答案。如果时间不足,也可以直接跳到最后看答案。

本次分享我们主要围绕jvm内存结构展开,这也是java面试必考知识点之一。所以我们先来看看jvm内存结构到底是啥样子。

1. jvm内存模型

我们首先看下面这张图:
不止面试02-JVM内存模型面试题详解 _ JavaClub全栈架构师技术笔记

这张图是虚拟机的结构图,当我们在讨论jvm内存模型时,指的就是中间五彩的那条区域:运行时数据区(runtime data area)。

我们把这个区域单独画出来,如下图所示:

不止面试02-JVM内存模型面试题详解 _ JavaClub全栈架构师技术笔记
现在我们来看看每个区域的用途

1.1 栈(Stack)

每当一个线程去执行方法时,就会同时在栈里面创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等。

每一个方法从被调用到执行完成的过程,都对应着一个栈帧从入栈到出栈的过程。

栈是线程私有的,每个线程在栈中保有自己的数据,别的线程无法访问。

在栈中我们可能遇到两种异常:StackOverflowError和OutOfMemoryError

StackOverflowError是指线程请求的栈深度大于虚拟机所允许的深度

OutOfMemoryError是指栈扩展时无法申请到足够的内存

这两种异常我们会在后面的文章中详细讲到。

1.2 本地方法栈(Native Method Stack)

本地方法栈和栈差不多,区别只在于本地方法栈为Native方法服务。

Native 方法就是一个Java调用非Java代码的接口,比如JNI。

本地方法栈也是线程私有的

1.3 程序计数器(PC Register)

要理解程序计数器,我们需要知道java代码最终都要编译成一条条的字节码,然后由字节码解释器一条条的执行的。而程序计数器可以看作是当前线程所执行的字节码的行号计数器

如果正在执行的是一个java方法,那么这个计数器记录的是正在执行的字节码指令地址。如果正在执行的是Native方法,那么计数器的值为Undefined。

程序计数器也是线程私有的,每条线程都有一个独立的程序计数器,各线程的程序计数器互不影响。

程序计数器是唯一一个不会OOM的内存区域

1.4堆(Heap)

堆应该是java内存中占用空间最大的一个区域,大家喜闻乐见的垃圾回收就主要发生在这个区域。

堆的唯一作用就是存放对象,不过并非所有对象都在堆中。这个我们以后会讲到。

堆如果空间不足,就会抛出OOM异常。

堆是可以让多个线程共享的

1.5 方法区(Method Area)

方法区也是可以让多个线程共享的

方法区主要用来存放类的版本,字段,方法,接口、常量池和运行时常量池。

常量池里存储着字面量和符号引用

还记得我们在jvm类加载面试题详解里说到的“加载”过程吗?其中说到“类加载器把类读入内存”。这里的所说的“内存”,指的就是方法区。

和方法区相关的知识点有永久代、元空间、常量池等。这几个概念非常容易把人绕晕,所以接下来我会尽量画图说明,给大家讲清楚。

1.5.1 永久代与元空间

作为和堆一样可以让线程共享的区域,堆之外的的空间被叫做非堆(Non-Heap)。可以粗略地理解为非堆里包含了永久代,而永久代里又包括了方法区

我们常常把永久代和方法区等同起来,然而永久代其实是Hotspot虚拟机把分代GC的范围扩展到方法区的产物。(分代GC我们会在后面讲到)。如下图:

不止面试02-JVM内存模型面试题详解 _ JavaClub全栈架构师技术笔记
所以,永久代和方法区虽然基本上指的是同一片内存区域,但是实质上是有差别的。

而到了jdk1.8中,永久代被取消,取而代之的便是元空间

元空间跟永久代最大的区别就在于,元空间直接使用机器内存,不在jvm虚拟机内。所以理论上你的机器内存有多大,元空间就能有多大。

此外,之前永久代的符号引用(Symbols)转移到了堆外内存(native heap);字面量(interned strings)和类的静态变量(class statics)转移到了堆内(heap)。

这样做的好处在于可以减少OOM,同时方便HotSpot和JRockit合并。

1.5.2 虚拟机的多实现

上一个小节我们提到了HotSpot和JRockit,这是个啥呢?

要知道,jdk的全称是Java Development Kit ,Java开发工具包。可以简单认为,jdk就是各种java开发工具+java虚拟机。而HotSpot就是目前Oracle jdk默认使用的虚拟机。

我们可以用java -version查看目前使用的虚拟机:

~ java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)

看见最后一行了吗?“Java HotSpot(TM) 64-Bit”说明我目前正在用的就是HotSpot。

除了HotSpot之外,还有很多其他的虚拟机,比如JRockit就是由BEA公司开发的一款java虚拟机,号称世界最快。

后来 JRockit被Oracle收购,于是Oracle就同时拥有了HotSpot和JRockit两款虚拟机,这也是为什么要把它们合并的原因之一。

1.5.3 各种常量池

上面我们提到了常量池、运行时常量池和字符串常量池,现在我们来看看这几个池子到底是个什么关系。

以下为jdk1.7的情况。

首先还是来张图:

不止面试02-JVM内存模型面试题详解 _ JavaClub全栈架构师技术笔记

数据是怎么在这几个池子里流转的呢?

首先,当类被加载的时候,class文件就会被加载到方法区,里面有块区域就被称为常量池。常量池主要存储两个东西:字面量(Literal)和符号引用(Symbolic References)。

下面这个图清楚的展示了常量池的内容:

不止面试02-JVM内存模型面试题详解 _ JavaClub全栈架构师技术笔记

当程序运行到该类的时候,常量池的大部分内容都会进入运行时常量池。但是String不同,String对象会存在堆里,然后在字符串常量池保存一个引用。

当主线程开始实例化字符串的时候,先到字符串常量池找有没有相等的字符串,有的话就直接把引用赋值给变量。
不过,需要注意的是,如果以创建对象的方式创建字符串,比如new String("abc"),那么,会在内存中开辟一块全新的内存地址,创建一个新对象,然后把引用赋值给变量,就没有字符串常量池的事了。

1.6 直接内存(Direct Memory)

看到这儿你可能要问了,what?直接内存是什么鬼?

直接内存并不是虚拟机运行时数据区的一部分,但是这部分区域却可以被虚拟机使用,且使用不当也会OOM,所以干脆在这讲一下。

在jdk1.4中加入的NIO类引入了一种基于通道和缓冲区的IO方式,可以直接分配堆外内存,并操作。理论上来说,直接内存由于不在虚拟机内,所以不受虚拟机内存大小控制(是不是有点像元空间?)。但是如果这块空间太大,可能会挤占虚拟机的内存,毕竟物理内存有限,从而使虚拟机在动态扩展的时候由于空间不足而抛出OOM。

jvm内存结构到此结束,下面我们来说说java内存模型。

1.7 java7和java8的jvm内存模型区别

看下面这张图:

不止面试02-JVM内存模型面试题详解 _ JavaClub全栈架构师技术笔记

java8中,元空间(METASPACE)取代了永久代(PREM GEN),并且移到了堆外内存(Native Memory)中。

2. Java内存模型

看到这你是不是在想:什么鬼?难道刚刚我们说的不是java内存模型吗?

实际上,jvm内存模型和java内存模型的确是两个比较容易混淆的概念。

java内存模型是指Java Memory Model,简称JMM。用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果,JMM规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。

好吧,上面这段话着实不大好理解。我们化繁为简,主要记住JMM规范了程序中变量的访问规则,保证了操作的原子性、可见性、有序性。

不止面试02-JVM内存模型面试题详解 _ JavaClub全栈架构师技术笔记

第三部分:面试题答案

1.描述一下jvm内存模型

jvm内存模型分为5个区域,其中线程独占的区域是栈、本地方法栈、程序计数器,线程共享的区域是堆、方法区。

2.描述一下java内存模型

回答这个问题一定要问清楚是不是要问java内存模型,确认是的话,可以回答:java内存模型规定了变量的访问规则,保证操作的原子性、可见行、有序性。

3.谈一下你对常量池的理解

常量池是类在编译后储存常量的一块区域,当程序运行到该类,常量池的大部分数据会进入运行时常量池,字符串会进入字符串常量池。

4.什么情况下会发生栈内存溢出?和内存溢出有什么不同?

栈内存溢出指程序调用的栈深度多大或栈空间不足时抛出的StackOverflowError。

一般所谓内存溢出指的是堆内存溢出,抛出的是OutOfMemoryError:java heap space。

在jdk1.7中,还可能出现永久代内存溢出,抛出的是OutOfMemoryError: PermGen space

在jdk1.8中,则会出现元空间溢出,抛出的是OutOfMemoryError:MetaSpace

5.String str = new String(“abc”)创建了多少个实例?

虽然很多博客都告诉我们创建了两个对象:一个字符串abc对象和一个字符串常量池里指向abc的引用对象。

但实际情况要更复杂一点。

实际上在执行完String str = new String(“abc”)之后,其实只创建了一个对象:堆里的字符串对象。而str直接指向该对象。在执行intern()方法后,才会到字符串常量池创建引用对象。当然有时候这个过程会自动完成,但情况比较复杂,难以确定。

有很多面试官其实自己也搞不清,所以不妨先告诉他创建了两个对象,然后再分析一番,效果更好。

引用文献

JVM虚拟机种类

什么是HotSpot VM

永久代(PermGen)和元空间的区别(Metaspace)

JVM的Heap Memory和Native Memory

JAVA8里的native heap究竟是个什么概念?

方法区和常量池

java用这样的方式生成字符串:String str = "Hello",到底有没有在堆中创建对象?

Java内存模型(JMM)总结

JVM内存溢出详解
系列文章总目录:https://mp.weixin.qq.com/s/56JgXLArTAEDj1f3y4arLA

作者:百里弈
来源链接:https://www.cnblogs.com/bailiyi/p/11967396.html

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/18172.html

分享给朋友:

“不止面试02-JVM内存模型面试题详解” 的相关文章

性能优化|快速掌握JVM内存分配机制

性能优化|快速掌握JVM内存分配机制

JVM整体结构 堆 线程共享的区域,也是垃圾回收器要收集的区域,这地方主要保存用户创建的对象。例如 new User(),这个对象是保存在堆上面的。...

性能优化|一张图带你了解JVM是如何进行内存分配

性能优化|一张图带你了解JVM是如何进行内存分配

对象优先在新生代分配 如果对象能够在eden区分配,那么就直接在eden区分配 如果eden区域大小不够新对象存储的话,则会触发一次minor gc, 如果minor gc后发现对象也无法在s区分配,则会直接在老年代中分配...

[JVM教程与调优] 为什么要学习JVM虚拟机?

[JVM教程与调优] 为什么要学习JVM虚拟机?

JVM在我们开发阶段不会用到,但是到了生产环境中,那么就会变得非常重要了。 为什么这么说呢? 一方面,因为我们的生产环境是比较复杂的。各种可能的问题都会出现,比如说:硬盘坏了、网络坏了、CPU利用率高了等问题层次不穷。 另外一方面,在我们生产环境出现问题,还不好进行定位。因为没...

Java String(JVM角度)

Java String(JVM角度)

基本特性 存储结构变更 jdk8及之前的jdk版本中,String的内存存储结构是char[]字符数组,但是在Jdk9及之后改成了byte[]字节数组。 原因是,堆空间中大部分的字符串内容都是latin字符,基本上...

JVM 从入门到精通(二)JVM和Java体系结构

JVM 从入门到精通(二)JVM和Java体系结构

写在前面:我是「云祁」,一枚热爱技术、会写诗的大数据开发猿。昵称来源于王安石诗中一句 [ 云之祁祁,或雨于渊 ] ,甚是喜欢。 写博客一方面是对自己学习的一点点总结及记录,另一方面则是希望能够帮助更多对大数据感兴趣的朋友。如果你也对 数据中台...

JVM学习1--数字存储,内存模型,指令重排

JVM学习1--数字存储,内存模型,指令重排

一、数字在计算机中的存储   整数:以补码形式存储。     补码:正数的补码是自身,负数的补码是取反码加1(取反码时符号位还是1)   浮点型:以float类型表示            注意一下,这八位指数实际上是(127...

浅谈JVM内存模型

浅谈JVM内存模型

JAVA虚拟机在执行JAVA程序的时候,会把它管理的内存分成若干不同的数据区域,每个区域都有各自的用途。目前大致把JVM内存模型划分为五个区域:程序计数器,虚拟机栈,本地方法栈,堆和方法区。   程序计数器 程序计数器(ProgramCounterR...

深入理解JVM(1)——JVM内存模型

Java虚拟机的内存空间分为五个部分,分别是: 程序计数器; Java虚拟机栈 本地方法栈 堆 方法区 接下来对这五部分分别进行详细的介绍 1、程序计数器:   a)什么是程序计数器:程序计数器是内存中的一个很小...

JVM - 双亲委派

JVM - 双亲委派

# JVM - 双亲委派 JDK版本:1.8 # 1、双亲委派机制 Java虚拟机对于class文件采用的加载策略是按需加载。也就是当需要使用该类时才会将该类的.class文件加载到内存中生成Class对象。并且加载某...

深入理解JVM—JVM内存模型

深入理解JVM—JVM内存模型

原文地址:http://yhjhappy234.blog.163.com/blog/static/316328322011101723933875/?suggestedreading&wumii 我们知道,计算机CPU和内存的交互是最频繁的,内存是我们的高速缓存区,用户磁...

发表评论

访客

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