java程序运行时内存分配跟Java内存模型(Java Memory Model,简称JMM)不是一个概念,前者是栈、堆、方法区等内容。后者是java在并发模型中内存的工作模型。
根据Java虚拟机的规范,java程序在运行时虚拟机会将所管理的内存划分为诺干个不同的数据区域。
根据《Java 虚拟机规范(Java SE 7版)》规定,Java虚拟机所管理的内存将会包括一下几个运行时数据区域:
0.数据类型
-
Java虚拟机可以操作的数据类型可分为两类:原始类型(primitive type也叫基本类型)和引用类型(reference type)。与之对应也存在原始值(primitive value)和引用值(reference value)两种类型的数值。
-
原始类型包括数值类型(包括整数类型、浮点类型)、boolean类型和returnAddress类型。
-
returnAddress类型是指向某个操作码的指针,此操作码与Java虚拟机指令相对应。在Java语言没有直接与之对应的数据类型(JDK 7之后此类型基本不用了)。
-
boolean类型在编译后会用int数据类型来代替。
-
引用类型有三种:类类型(class type)、数据类型(array type)和接口(interface type)类型。
-
数组最外层一维元素的类型叫做该数组类型的组件类型。最内层非数组的元素叫做本数据类型的元素类型。
1.程序计数器
-
程序计数器(Program Counter Register) 是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条执行字节码指令。
-
每条线程都有一个独立的程序计数器。
-
如果执行的是java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址。如果是native方法,计数器为空。此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
2.Java虚拟机栈
-
栈是线程私有区域,描述Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。一个方法对应一个栈帧。
-
局部变量表存放了各种基本类型、对象引用和returnAddress类型(指向了一条字节码指令地址)。其中64位长度long 和 double占两个局部变量空间,其他只占一个。
-
栈上分配和释放内存的速度很快,随着jvm的优化栈上也可以分配对象,这设计两个概念:1,逃逸分析。2,标量替换。前者分析对象是否会在方法之外访问。后者是将对象分配在栈上的具体分配方法。server模式下默认会开启栈上分配对象。具体配置参考jvm配置。
-
规定的异常情况有两种:1.线程请求的栈的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;2.如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存,就抛出OutOfMemoryError异常。
-
-Xss1M栈大小,默认情况下大多情况下,支持1000~2000的递归调用。
-
简单的模型下可以认为虚拟机可以将机器的内存分成栈,堆,方法区三部分。所以栈的最大大小就是虚拟机减去其它两个的容量。在多线程的情况下,栈最大大小不变,每个线程占用的空间越大,所能创建的线程数目上限就越小。
3.本地方法栈
- 和Java虚拟机栈很类似,不同的是本地方法栈为Native方法服务。
4.Java堆
-
是Java虚拟机所管理的内存中最大的一块。由所有线程共享,在虚拟机启动时创建。堆区唯一目的就是存放对象实例。分配和释放速度慢于栈上操作。
-
堆是线程共享的,但是线程私有的分配缓冲区(TLAB,Thread Local Allocation Buffer)可以在给对象分配内存时使之成为线程私有。
-
堆中可细分为新生代和老年代,再细分新生代可分为Eden空间、From Survivor空间、To Survivor空间。新生代:新对象一般分配在此,大部分使用过后会被回收。老年代:大对象优先分配在老年代,一般是存活时间长的
-
堆大小的参数-Xms4G最小值,-Xmx4G最大值,-Xmn1G年轻代大小,-XX:SurvivorRatio=4年轻代中Eden区与Survivor区的大小比值。设置为4。
-
堆无法扩展时,抛出OutOfMemoryError异常
5.方法区
-
所有线程共享,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。永久代permanent。java8去除了永久带
-
当方法区无法满足内存分配需求时,抛出OutOfMemoryError
6.运行时常量池
-
它是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池(Const Pool Table),用于存放编译期生成的各种字面量和符号引用。并非预置入Class文件中常量池的内容才进入方法运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。
-
当方法区无法满足内存分配需求时,抛出OutOfMemoryError
7.直接内存
-
并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。
-
JDK1.4加入了NIO,引入一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。因为避免了在Java堆和Native堆中来回复制数据,提高了性能。
-
当各个内存区域总和大于物理内存限制,抛出OutOfMemoryError异常。