Java虚拟机JVM的内存数据区域

  • 一、JAVA的JVM的内存
  • (1)栈区
  • (2)堆区
  • (3)方法区
  • 二、线程私有内存区
  • (1)程序计数器
  • (2)虚拟机栈
  • (3)本地方法栈
  • 三、线程共享内存区
  • (1)java堆
  • (2)方法区
  • ——运行时常量池
  • 四、总结
  • 五、对Java堆的扩展补充


java 非堆区 堆区 比例 jvm堆区不包括_方法区


JAVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)

一、JAVA的JVM的内存

(1)栈区

栈区:
1、每个线程包含一个栈区,栈中只保存方法(不包括对象的成员变量)中的基础数据类型和自定义对象的引用(不是对象),对象都存放在堆区中
2、每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3、栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。

(2)堆区

堆区:
存储的全部是对象实例,每个对象都包含一个与之对应的class的信息(class信息存放在方法区)。
jvm只有一个堆区(heap),被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身,几乎所有的对象实例和数组都在堆中分配。

(3)方法区

方法区:

又叫静态区,跟堆一样,被所有的线程共享。它用于存储已经被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。

java 非堆区 堆区 比例 jvm堆区不包括_jvm_02


由此图可以看出

线程共享的数据区有 方法区和堆。
线程隔离的数据区有 虚拟机栈、本地方法栈和程序计数器。

二、线程私有内存区

理解如下:

(1)程序计数器

程序计数器:就是指示当前线程执行的字节码执行到哪了 (当前线程所执行的字节码的行号指示器 )
因为是线程私有的,所以每个线程都是有这样一个指示器的,字节码解释器就是按照这个指示器决定下一步执行哪条字节码
另外,程序计数器是唯一 一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

(2)虚拟机栈

虚拟机栈:就是我们常说的"堆"/"栈"中的 栈,虚拟机栈包含局部变量表,存放数据类型和对象引用(局部变量表所需的内存空间在编译期间完成分配 )。

Java虚拟机栈占有的内存空间也就是我们平常所说的“栈内存”,并且也是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时,都会创建一个栈帧,用于存储局部变量表(基本数据类型,对象的引用和returnAddress类型)、操作数栈、动态链接、方法出口等信息。

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

每个方法被调用直至执行完成的过程,就对应着一个栈帧从虚拟机栈中从入栈到出栈的过程。对于Java虚拟机栈,有两种以下情况:

  1. 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError异常。
  2. 如果虚拟机栈在动态扩展时,无法申请到足够的内存,就会抛出OutOfMemoryError异常。

(3)本地方法栈

本地方法栈:本地方法栈为虚拟机使用到的Native本地方法服务。
本地方法栈和虚拟机栈所发挥的作用非常相似,它们之间的区别主要是:虚拟机栈是为虚拟机执行的Java方法(即字节码)服务的,而本地方法栈则为虚拟机使用到的Native本地方法服务。
与虚拟机栈类似,本地方法栈也会抛出StackOverFlowError和OutOfMemoryError异常。

三、线程共享内存区

下面是线程共享的区域:

(1)java堆

Java堆是Java虚拟机所管理的内存中最大的一块。Java堆在主内存中,是被所有线程共享的一块内存区域,其随着JVM的创建而创建,堆内存的唯一目的是存放对象实例数组。同时Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做 “GC堆”。
Java堆在物理上不需要连续的内存,只要逻辑上连续即可。如果堆中没有内存完成实例分配,并且也无法再扩展时,将会抛出OutOfMemoryError异常。

从内存回收的角度来看可以分为:新生代和老年代
从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区

(2)方法区

方法区存储已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据
也被称为永久代,Java虚拟机规范把方法区描述为堆的一个逻辑部分
根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。

——运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。
Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量表,用于存放编译期生成的各种字面常量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放(JDK1.7开始,常量池已经被移到了堆内存中了)。

也就是说,这部分内容,在编译时只是放入到了常量池信息中,到了加载时,才会放到运行时常量池中去。运行时常量池现归于Class文件。常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用的比较多的是String类的intern()方法。
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常,常量池属于方法区,同样可能抛出OutOfMemoryError异常。

四、总结

java 非堆区 堆区 比例 jvm堆区不包括_jvm_03

五、对Java堆的扩展补充

Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。

在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden(Eden区)、From Survivor(From区)、To Survivor(To区)。

这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。

堆的内存模型大致为:

java 非堆区 堆区 比例 jvm堆区不包括_Java_04


从图中可以看出: 堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。

元数据区是用来替换永久代的,大概那个意思。
引入元数据区的意义: JDK8引入了一个新的native的内存区块,Metaspace(也就是题主所说的“元数据”区域)。也就是说,之后你在调优或者调查JVM问题的时候就不用和PermGen区域打交道了,也不会有java.lang.OutOfMemoryError: PermGen 这种内存不足的问题来骚扰你。

java 非堆区 堆区 比例 jvm堆区不包括_方法区_05


新创建的Object会在Young Generation的Eden中,当Eden满了之后会进行Garbage Collection,同时没被Garbage Collection的Object会移动到Young Generation的from Survivor或者to Surivor中,当然Garbage Collection检查和移动from Survivor或者to Surivor,使其中一个为空。在Young Generation的Garbage Collection叫Mintor Garbage Collection。在Young Generation多次仍未被Garbage Collection的Object会移动到Old Generation,同样当Old Generation满了之后会进行Garbage Collection,这种Garbage Collection叫做Major Garbage Collection。