热门课程

免费试听

上课方式

开班时间

当前位置: 首页 -   文章 -   根域文章 -   正文

JVM内存分配概述

zhiliaoadmin
2021-07-28 10:53:24
0

Java内存分配是Java基础中非常重要的一部分。深入了解JVM内存分配机制,将帮助你解决开发中面临的诸多问题。同时,内存分配也是面试官常问的问题。作为面试官,作者也问了很多关于内存分配的问题,但令人惊讶的是,很多3-5年经验的面试官都含糊其辞,并没有完全理解这个话题。因此,本节系统地回顾了Java的内存分配机制。


JVM内存分配概述

首先你要明白JVM并不是一个单一的实体,在Java发展20多年的历史中,涌现出了很多伟大的虚拟机,包括大名鼎鼎的HotSpot虚拟机。虚拟机之间的内存分配略有不同,但通常遵循《Java虚拟机规范》。有5 个虚拟机运行时数据区分配给《Java虚拟机规范》。它们是程序计数器、Java 虚拟机堆栈、本地方法堆栈、本地方法区、Java 堆和方法区。如下所示:


(950.163.com)

='http: 接下来,我们来详细分析一下以上五个内存区域。


一、程序计数器(Program Counter)

程序计数器是一块内存,用来存放下一条指令的设备地址。在Intel x86 和Itanium 微处理器上,它显然被称为指令指针(指令指针,IP)。 “指令指针”这个名字让这个概念更容易理解。在JVM中,程序计数器是一个很小的内存占用,可以看作是当前线程所执行字节码的行号指示器。当字节码解释器运行时,它会改变这个计数器的值来选择下一个要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等功能都依赖这个计数器来完成。


我们知道,在多线程的情况下,线程被调度为由CPU 执行指令。在单核CPU 上,一次只有一个线程执行指令,因此CPU 会频繁切换线程。线程切换时,每条线程都有一个独立的程序计数器必须恢复到它需要的正确执行位置,每个线程之间的计数器互不影响。

此外,关于程序计数器的一些知识是Java 虚拟机规范没有为程序计数器指定OutMemoryError。为什么?我个人认为程序计数器占用的内存很小,线程数是有限的。因此,在说明书中对此没有限制。综上所述,可以总结出以下几点。

程序计数器是线程私有的,每条线程都有一个程序计数器。程序计数器存放了下一条要执行的指令的位置字节码解释器通过给改变计数器的值来执行指令程序计数器是JVM内存中唯一没有规定OutMemoryError的区域

二、Java虚拟机栈(Java Virtual Machine Stacks)

说到Java的栈内存,大家应该都不陌生,甚至很多人不知道如何堆栈内存。作为“堆栈内存用于存储原始数据类型和对象引用”的头部。但这是真的吗?其实不准确!这只是Java 虚拟机堆栈功能的一部分。


虚拟机栈描述了Java 方法执行的内存模型。每个方法执行时,都会创建一个栈帧来存储局部变量表、操作数栈、动态链接、方法终止等信息。从调用各个方法到执行完成的过程,对应的是通过压入虚拟机栈的栈帧来出栈的过程。

栈帧是用于支持虚拟机的方法调用和方法执行的数据结构,是虚拟机运行时虚拟机数据区中虚拟机栈的栈元素。帧存储变量表、操作数堆栈和方法的动态链接,方法返回其他信息。这篇文章的内容应该就明白了,不用过多解释栈帧。

而我们通常所说的“栈内存”其实是指虚拟机的局部变量表部分。局部变量表存储了编译器已知的各种原始数据类型(boolean、type、char、short、int、final、long、double)、对象引用(引用类型,可以是指向起始地址的引用指针)。 object) ,或表示该对象的句柄,或可能指向相对于该对象的某个其他位置)和returnAddress 类型(指向字节码指令地址)。 long和double类型的数据占用两个局部变量空间(Slots),其余数据类型只占用一个。局部变量表所需要的空间是在编译过程中分配的,在进入一个方法的时候,这个方法应该在帧中分配的局部变量空间是完全确定的,在执行过程中局部变量的大小不会发生变化。方法。

同时,Java 虚拟机堆栈对于线程来说是私有的,就像程序计数器一样。 Java 虚拟机堆栈的生命周期与线程的生命周期相同。在Java 虚拟机规范中,这方面也有两个例外。如果线程请求的堆栈深度大于虚拟机允许的深度,则会引发StackOverflowError 异常。动态扩展(当前一些大型虚拟机可以动态扩展)如果在扩展期间无法应用足够的内存,则会引发OutOfMemoryError。

由以上可以得出以下结论。

虚拟机栈是线程私有的,生命周期与线程相同虚拟机栈的功能不仅仅是存储基本数据类型和对象的引用虚拟机栈描述的是Java方法执行的内存模型Java虚拟机栈内存不足时会抛出StackOverflowError异常和OutOfMemoryError异常

三、本地方法栈(Native Method Stack)

虽然本地方法栈和虚拟机栈的功能很相似,但不同的是虚拟机栈机器栈是负责执行方法(字节码),本地方法栈提供虚拟机默认使用的方法。虚拟机规范对本地方法栈中方法的语言、用法和数据结构没有强制性规定,因此虚拟机可以自由实现。与HotSpot 虚拟机一样,本地方法栈和虚拟机栈合二为一。


与虚拟机堆栈一样,StackOverflowError 和OutOfMemoryError 也在本地方法堆栈区域中引发。

关于本地虚拟机栈,这里总结一下:

本地方法栈为虚拟机使用到的Native方法服务某些虚拟机本地方法栈与虚拟机栈合二为一本地方法栈区域会抛出StackOverflowError和OutOfMemoryError

四、Java堆

堆内存非常重要,应该是Java开发者最熟悉的内存区域!我们创建的几乎每个对象实例都存储在堆内存中。因此,Java 堆是虚拟机管理的最大内存区域。Java堆是被所有线程共享的一块内存区域,虚拟机启动时创建。 Java 虚拟机规范将堆内存描述为:所有的对象实例以及数组都要在堆上分配,但是,随着JIT编译器的演进和逃逸分析技术的逐渐成熟,栈分配和标量替换优化技术会发生一些微妙的变化。并非所有对象都足够绝对可以在堆上分配。


对于Java堆内存也可以细分为新生代(Young Generation)老年代(Old Generation)。新一代可以细分为伊甸空间、从幸存者空间和到幸存者。如下图(注:图中Permanent Generation不属于堆内存,是方法区的一部分):

Java 中的垃圾收集是对Java 堆不同区域的扫描和回收。有关Java GC 机制的更多详细信息将在后续文章“回顾过去并了解新事物”系列中讨论。

在内存分配方面,线程共享的Java堆可以划分为多个线程本地分配缓冲区(TLAB)。

但是,无论如何划分,堆中存储的内容都是对象的实例。进一步分区是为了更好地回收内存或更快地分配内存。

Java 虚拟机规范还规定Java 堆可以驻留在物理上离散的内存空间中。为实例分配内存时,如果虚拟机空间不足,堆无法扩容,则会抛出OutOfMemoryError异常。

Java堆可以总结如下:

堆内存是被所有线程共享的一块内存区域Java中创建的对象几乎都存放在堆内存中垃圾回收机制会对堆内存进行扫描和垃圾对象的回收堆内存不足时会抛出OutOfMemoryError异常

五、方法区(Method Area)

方法区用于存储类信息、常量、静态变量和即时编译器。虚拟机加载的代码和其他数据。方法区和堆内存一样,是所有线程共享的区域。 Java虚拟机(JVM)对方法区的限制非常宽松,因此方法区的性能在不同的虚拟机中是不同的。以HotSpot虚拟机为例:方法区在JDK1.7之前是一块单独的区域很多人喜欢称其为“永生代”,但本质上两者并不等价。 GC 集合已扩展到方法区域或使用永久代来实现了方法区。这样,HotSpot 的垃圾收集器就可以像管理Java 堆一样管理这部分内存。但是,对于其他虚拟机(BEA JRockit、IBM J9 等),确实没有永久创建的概念。不建议使用持久构造实现方法区域,因为它容易出现内存溢出问题。 HotSpot 团队很清楚这个问题,所以在JDK1.7 中他们将方法区中的字符串常量池移到堆内存中,而在JDK1.8 中他们完全弃用了“永久创建”并替换了元空间。永久一代。


另外,根据Java 虚拟机规范,如果方法区不满足内存分配要求,则会抛出OutOfMemoryError 异常。

方法领域的总结如下。

方法区与堆内存一样是所有线程共享的一块区域方法区是用来存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据不同的虚拟机对于方法区的实现不同HotSpot虚拟机在JDK1.7中将字符串常量池移到了堆内存,并在JDK1.8中用元空间去掉了“永久代”。方法区内存不足时会抛出OutOfMemoryError异常


大家都在看

什么是防抖和节流?防抖和节流有什么区别?

2021-07-28 浏览次数:0

session 和 cookie 有什么区别?J...

2021-07-28 浏览次数:0

没有基础学java难吗?基础差的人怎么学Java

2021-07-28 浏览次数:0

知了汇智与西南石油大学合作开展网络安全生产实习,...

2021-07-28 浏览次数:0

java开发技能培训学校

2021-07-28 浏览次数:0

官宣丨短视频 & 直播冬令营 此刻登场

2021-07-28 浏览次数:0
最新资讯