Java虚拟机管理的内存划分为:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区
- 线程共享:方法区、Java堆;线程私有:Java虚拟机栈、本地方法栈、程序计数器
程序计数器
Java虚拟机的多线程工作是通过轮流切换线程、分配处理器的执行时间的方法来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器老说就是一个内核)只会执行一条线程中的指令。所有每一条线程都有一个独立的程序计数器,用来记录程序执行位置,以便切换线程后再执行该线程时能找到执行位置。各个线程之间的程序计数器互不影响,独立存储。
特点
- 唯一一个不会发生OutOfMemoryError(内存溢出)的区域;
- 程序计数器是在线程创建的时候一同创建,并分配相应的内存;
- 如果线程正在执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码的指令地址,如果执行的是本地方法(Native)方法,计数器的值为空(Undefined);
- 线程私有,每一个线程对应一个程序计数器。
Java虚拟机栈
线程私有、生命周期与线程相同。描述的是Java方法执行的线程内存模型:每个方法被执行的时候,都会在Java虚拟机中创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用到执行完毕的过程,对应着一个栈帧在虚拟计栈中从入栈到出栈的过程。
- 如果线程请求的栈深度大于虚拟机所允许的深度,会抛出StackOverflowError(栈溢出)异常;如果Java虚拟机栈容量可以动态扩展(HotSpot虚拟机不可扩展,以前的Classic虚拟机可以扩展),当栈扩展时无法申请到足够的内存就会抛出OutOfMemoryError(内存溢出)异常。
局部变量表
- 存放了编译期可知的各种Java虚拟机基本数据类型(byte、char、short、int、float、long、double、boolean)、对象引用和returnAddress类型(指向了一条字节码指令的地址)。这些数据类型在局部变量表中的存储空间以局部变量槽(slot)来表示;其中64位的long和double类型的数据会占用两个变量槽,其余类型占用一个。
- 局部变量表所需的内存空间在编译期间完全分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间时完全确定的,方法运行期间不会改变局部变量表的大小,大小指变量槽的数量,虚拟机使用多大的内存空间(1个变量槽占32个比特、64个比特... ...)来实现一个变量槽,由虚拟机自行决定。
本地方法栈
本地方法栈与虚拟机栈类似,区别在于虚拟机栈为虚拟机执行的Java方法(字节码)服务,本地方法栈则为虚拟机使用到的本地方法服务。 HotSpot虚拟机将本地方法栈和虚拟机栈合二为一。也会发生StackOverFlowError和OutOfMemoryError。
Java堆
Java(Java Heap)堆是虚拟机所管理的内存中最大的一块。被所有的线程所共享,在虚拟机启动时创建。唯一作用:存放对象实例。《Java虚拟机规范》对堆的描述:所有的对象实例以及数组都应当在对堆上分配。
Java堆是垃圾收集器管理的内存区域,因此有些资料也称它为“GC堆”。Java堆可以被实现成固定大小的,也可以是扩展的,当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机会抛出OutOfMemeryError异常。
方法区
线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。会发生OutOfMemeryError异常。《Java虚拟机规范》对方法区的约束非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,甚至可以选择不实现垃圾收集。
补充关于静态变量的知识:
- java类的成员变量有两种:被static关键字修饰的变量,叫类变量或者静态变量;没有static修饰,为成员变量;
- 通俗点说: 类的静态变量在内存中只有一个,java虚拟机在加载类的过程中为静态变量分配内存,静态变量位于方法区,被类的所有实例共享。静态变量可以直接通过类名进行访问,其生命周期取决于类的生命周期;
- 而实例变量取决于类的实例。每创建一个实例,java虚拟机就会为实例变量分配一次内存,实例变量位于堆区中,其生命周期取决于实例的生命周期;
- JAVA中初始化的顺序:加载类;静态变量初始化;静态块;成员变量;构造方法;
运行时常量池
方法区的一部分。用于存储编译期生成的各种字面量与符号引用,一这部分内容将在类加载后存放到方法区的运行时常量池中。会发生OutOfMemeryError异常。