JVM内部存款和储蓄器模型——JAVA的功底【彩民之

2019-10-11 05:41 来源:未知

在固定大小的情况下,JVM会为每个线程的虚拟机栈分配一定的内存大小,因此虚拟机栈能够容纳的栈帧数量是有限的,若栈帧不断进栈而不出栈,最终会导致当前线程虚拟机栈的内存空间耗尽,会抛出StackOverflowError异常。

Java 堆可以处于物理上不连续的内存空间内,只要逻辑上连续即可。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用。

方法区同样存在垃圾收集,因为通过用户定义的类加载器可以动态扩展Java程序,这样可能会导致一些类,不再被使用,变为垃圾。这时候需要进行垃圾清理。

JDK 1.4 中新加入的NIO(New Input / Output)类引入了一种基于通道与缓冲区的 IO 方式,可以使用 Native 函数库直接分配堆外内存,再通过存储在 Java 堆中的DirectByteBuffer 对象做为这块内存的引用进行操作。

java虚拟机栈

虚拟机栈(Java Virtual Machine Stacks)是线程私有的。java虚拟机栈是描述java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用来存储局部变量表操作数栈动态链接方法出口等信息。每一个方法的调用直至执行完成,都对应着一个栈帧在虚拟机栈中入栈到出栈的过程。通常所说的“堆”中存储“栈”中运行中的“栈”指的就是虚拟机栈,或者说是虚拟机栈中的局部变量表

局部变量表:存放了编译期可知的各种基本类型(其中64位长度的long和double类型会占用2个局部变量空间(Slot))、对象引用returnAddress类型

规定的异常

  • StackOverflowError:当线程请求的栈深度大于虚拟机所允许的深度时抛出。
  • OutOfMemoryError:当虚拟机栈进行动态扩展时无法申请到足够的内存空间时抛出。

本地方法栈

虚拟机栈为线程私有,生命周期与线程相同,描述 Java 方法执行的内存模型。方法执行时创建栈帧,用于存储局部变量表、操作数栈、方法出口等信息。

java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。

可通过参数 -Xmx -Xms 来指定运行时堆内存的大小,堆内存空间不足也会抛OutOfMemoryError异常。

JVM 规范对方法区的限制非常宽松,不需要连续内存、固定大小或可扩展均可,还可以不实现垃圾回收。垃圾收集行为在这个区域较少出现。

彩民之家论坛9066777 1

JVM的多线程是通过线程轮流切换并分配CPU执行时间片的方式来实现的,任何一个时刻,一个CPU都只会执行一条线程中的指令。为了保证线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程间的程序计数器独立存储,互不影响。

在堆中没有内存完成实例分配也无法再扩展时抛出 OutOfMemoryError。

本地方法栈

本地方法栈(Native Method Stack)与虚拟机栈的作用相似,它们的区别为虚拟机栈为虚拟机执行java方法服务,面本地方法栈则为虚拟机使用到的Native方法服务。
在虚拟机规范中没有对本地方法栈的使用语言、使用方式、数据结构作强制规定,都由具体的虚拟机自由实现。有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。
规定的异常

  • StackOverflowError:当线程请求的栈深度大于虚拟机所允许的深度时抛出。
  • OutOfMemoryError:当虚拟机栈进行动态扩展时无法申请到足够的内存空间时抛出。

堆是垃圾收集器管理的主要区域,因此也被称为“GC堆”。

方法区由各个线程共享,用于存储已被 JVM 加载的类信息、常量、静态变量、即使编译器编译后的代码等。

方法区

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。从JDK1.7的HotSpot中,已经把原本放在方法区中的字符串常量池移出。从JDK1.8 HotSpot JVM开始使用本地化的内存存放类的元数据,这个空间叫做元空间(Metaspace)这部分可参考文章: JAVA 8 :从永久区(PermGen)到元空间(Metaspace)

虚拟机栈是一个后入先出的数据结构,线程运行过程中,只有处于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法,当前活动帧栈始终是虚拟机栈的栈顶元素。

当方法区无法满足内存分配需求时会抛出 OutOfMemoryError。

Java堆

Java堆(Java Heap)是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存的唯一目的就是存入对象实例,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的主要区域。从内存回收角度来看,Java堆还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。
Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。可以设置固定大小也可以是可扩展的,可以通过参数 -Xmx-Xms来控制。
规定的异常

  • OutOfMemoryError:当在堆中没有完成实例分配,并且堆也无法再扩展时抛出。

从内存回收的角度上看,可分为新生代(Eden空间,From Survivor空间、To Survivor空间)及老年代(Tenured Gen)。

方法区包含运行时常量池,Class 文件中的常量信息在类加载后存放于运行时常量池中。运行时常量池具备动态性,运行期间也可能将新的常量放入池中。

程序计数器

程序计数器(Program Counter Register)是线程私有的。是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

64位的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。

JVM 在执行 Java 程序的过程中会把它所管理的内存划分为如下几个数据区域:

JVM运行时数据区

程序计数器

程序计数器

栈的大小可以固定也可以动态扩展。

程序计数器是一块很小的内存空间,是当前线程所执行字节码的行号指示器,线程间私有不共享。执行 Java 方法时计数器的值为字节码指令的地址,执行 Native 方法时值为空。不会出现OutOfMemoryError。

Java 堆是 JVM 所管理内存中最大的一块,所有线程共享,在虚拟机启动时创建,用于存放对象实例,是垃圾回收的主要区域。

JAVA堆的分类:

查看原文

JVM管理的最大的一块内存区域,存放着对象的实例,是线程共享区。

方法区

从内存分配的角度上看,为了解决分配内存时的线程安全性问题,线程共享的JAVA堆中可能划分出多个线程私有的分配缓冲区。

程序计数器

局部变量表存放了编译期可知的各种基本数据类型和对象引用类型。通常我们所说的“栈内存”指的就是局部变量表这一部分。

线程请求栈深度大于虚拟机允许深度时抛出 StackOverflowError(大部分虚拟机可以动态扩容,但也允许固定长度的虚拟机栈),扩展时无法申请到足够内存抛出 OutOfMemoryError。

栈溢出代码:

虚拟机栈

虚拟机栈(Java Virtual Machine Stacks)是线程隔离的,每创建一个线程时就会对应创建一个Java栈,即每个线程都有自己独立的虚拟机栈。这个栈中又会对应包含多个栈帧,每调用一个方法时就会往栈中创建并压入一个栈帧,栈帧存储局部变量表、操作栈、动态链接、方法出口等信息,每一个方法从调用到最终返回结果的过程,就对应一个栈帧从入栈到出栈的过程。

解析

本地方法栈

HotSpot虚拟机不区分虚拟机栈和本地方法栈,两者是一块的。

直接内存 *

保存在着被加载过的每一个类的信息(虚拟机加载的类信息(类的版本、字段、方法、接口),常量,静态变量,即时编译器编译后的代码等数据);这些信息由类加载器在加载类的时候,从类的源文件中抽取出来;static变量信息也保存在方法区中。

方法区

彩民之家论坛9066777 2

Java 堆

堆溢出代码:(由于在Windows 平台的虚拟机中, Java 的线程是映射到操作系统的内核线程上的,所以多线程代码执行时有较大的风险,可能会导致操作系统崩溃。)

虚拟机栈

不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法。

直接内存不受 Java 堆大小限制,但会受到本机总内存及处理器寻址空间限制,动态扩展超出限制会抛出 OutOfMemoryError 异常。

(在单个线程下,无论是由于栈帧太大,还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是 StackOverflowError 异常,而不是OOM。)

本地方法栈

方法区是线程共享的;当有多个线程都用到一个类的时候,而这个类还未被加载,则应该只有一个线程去加载类,让其他线程等待。

从内存回收的角度可分为新生代和老年代,再细致划分可分为 Eden 空间、From Survivor 空间 和 To Survivor 空间等。

彩民之家论坛9066777 3

局部变量表存放了编译器可知的基本数据类型、对象引用和 returnAddress类型,所需内存空间在编译器完成分配,运行期间不改变大小。

抽象

JVM 规范把方法区描述为堆的一个逻辑部分,别名 Non-Heap(目的是与 Java 堆区分开)。

方法区

本地方法栈为虚拟机执行 Native 方法服务,线程隔离。虚拟机规范中无强制规定,Sun HostSpot 将本地方法栈和虚拟机栈合二为一。能够抛出 StackOverflowError 和 OutOfMemoryError 异常。

彩民之家论坛9066777 4

直接内存不是 JVM 运行时数据区的一部分,也不是 JVM 规范中定义的内存区域,但这部分内存会被频繁使用,而且可能会导致 OutOfMemoryError 异常。

局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧分配多少内存是固定的,运行期间不会改变局部变量表的大小。

彩民之家论坛9066777 5

虚拟机栈

可以看做是将类的元数据,保存在方法区里。

在动态扩展的情况下,当整个虚拟机栈内存耗尽,并且无法再申请到新的内存时,就会抛出OutOfMemoryError异常。

方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。jvm也可以允许用户和程序指定方法区的初始大小,最小和最大限制。

彩民之家论坛9066777 6

彩民之家论坛9066777 7彩民之家论坛9066777 8

彩民之家论坛9066777 9

方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。

如果线程执行的是java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址(可以理解为上图所示的行号),如果正在执行的是native方法,这个计数器的值为undefined。

本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出StackOverflowError和OutOfMemoryError异常。

HotSpot虚拟机使用永久代来实现方法区,使得HotSpot虚拟机的垃圾收集器可以像管理堆内存一样来管理这部分内存,能省去专门为方法区编写内存管理代码工作。所以开发者喜欢将方法区称为永久代,本质上两者并不等价,对于其他虚拟机来说不存在永久代的概念。

此区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域,因为程序计数器是由虚拟机内部维护的,不需要开发者进行操作。

JAVA堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

程序计数器(Program Counter Register)是JVM中一块较小的内存区域,保存着当前线程执行的虚拟机字节码指令的内存地址(可以看作当前线程所执行的字节码的行号指示器)。

彩民之家论坛9066777 10

TAG标签: 区域 j 模型 根基 内存
版权声明:本文由彩民之家高手论坛发布于编程技术,转载请注明出处:JVM内部存款和储蓄器模型——JAVA的功底【彩民之