Java虚拟机JVM的内存数据区域
- 一、JAVA的JVM的内存
- (1)栈区
- (2)堆区
- (3)方法区
- 二、线程私有内存区
- (1)程序计数器
- (2)虚拟机栈
- (3)本地方法栈
- 三、线程共享内存区
- (1)java堆
- (2)方法区
- ——运行时常量池
- 四、总结
- 五、对Java堆的扩展补充
JAVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)
一、JAVA的JVM的内存
(1)栈区
栈区:
1、每个线程包含一个栈区,栈中只保存方法(不包括对象的成员变量)中的基础数据类型和自定义对象的引用(不是对象),对象都存放在堆区中
2、每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3、栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
(2)堆区
堆区:
存储的全部是对象实例,每个对象都包含一个与之对应的class的信息(class信息存放在方法区)。
jvm只有一个堆区(heap),被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身,几乎所有的对象实例和数组都在堆中分配。
(3)方法区
方法区:
又叫静态区,跟堆一样,被所有的线程共享。它用于存储已经被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。
由此图可以看出
线程共享的数据区有 方法区和堆。
线程隔离的数据区有 虚拟机栈、本地方法栈和程序计数器。
二、线程私有内存区
理解如下:
(1)程序计数器
程序计数器:就是指示当前线程执行的字节码执行到哪了 (当前线程所执行的字节码的行号指示器 )
因为是线程私有的,所以每个线程都是有这样一个指示器的,字节码解释器就是按照这个指示器决定下一步执行哪条字节码
另外,程序计数器是唯一 一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
(2)虚拟机栈
虚拟机栈:就是我们常说的"堆"/"栈"中的 栈,虚拟机栈包含局部变量表,存放数据类型和对象引用(局部变量表所需的内存空间在编译期间完成分配 )。
Java虚拟机栈占有的内存空间也就是我们平常所说的“栈内存”,并且也是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时,都会创建一个栈帧,用于存储局部变量表(基本数据类型,对象的引用和returnAddress类型)、操作数栈、动态链接、方法出口等信息。
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
每个方法被调用直至执行完成的过程,就对应着一个栈帧从虚拟机栈中从入栈到出栈的过程。对于Java虚拟机栈,有两种以下情况:
- 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError异常。
- 如果虚拟机栈在动态扩展时,无法申请到足够的内存,就会抛出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堆的扩展补充
Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。
在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden(Eden区)、From Survivor(From区)、To Survivor(To区)。
这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
堆的内存模型大致为:
从图中可以看出: 堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。
元数据区是用来替换永久代的,大概那个意思。
引入元数据区的意义: JDK8引入了一个新的native的内存区块,Metaspace(也就是题主所说的“元数据”区域)。也就是说,之后你在调优或者调查JVM问题的时候就不用和PermGen区域打交道了,也不会有java.lang.OutOfMemoryError: PermGen 这种内存不足的问题来骚扰你。
新创建的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。