【Java】Java内存区域

Java相对于C++来说的一个显著的区别就是其内存由虚拟机自动管理、动态分配,因此虚拟机将其管理的内存区域分为一下几大块:

【Java】Java内存区域

一、线程隔离数据区

1、程序计数器

Java源代码经过编译之后生成字节码,在Java程序运行的过程中,必须知道当前运行的是哪一条指令,并且知道下一条指令的位置或者指令内容,程序计数器就是当前线程用来记录所执行的字节码的行号指示器,如果当前所执行的字节码不是跳转指令,则当前指令执行完毕后,程序计数器+1,表示选取下一条指令,否则跳转到跳转指令所指示的地址处。

同时由于每个线程的执行内容都是不一致的,所以每个线程都必须要有自己的程序计数器,因为是线程隔离的。

2、虚拟机栈

虚拟机栈描述了Java方法执行的内存模型:每个方法执行时都会创建一个栈帧(Stack Frame),用来存储线程的局部变量表、操作数栈、动态链接、出口信息等。

每个方法从调用到执行完毕就意味着这个方法的栈帧从入栈到出栈的过程。

局部变量表存放了编译器克制的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,即指向该对象起始地址的指针或代表一个对象的句柄)和returnAddress类型(指向一条字节码指令的地址)

局部变量表所需内存空间在编译器完成分配,因此进入一个方法时要分配多少内存是确定的。

两种异常状态:*Error:栈深度超过上限、OutOfMemoryError:虚拟机栈扩展时无法申请到足够内存

3、本地方法栈

与虚拟机栈类似,但虚拟机栈为虚拟机所执行的Java方法服务,而本地方法栈为Native方法服务,同样定义了*Error以及OutOfMemoryError两种异常。

 

二、线程共享数据区

1、堆

用来存放对象实例,所有的对象实例和数组都要在堆上分配,因此这里也是GC的主要区域,Java堆根据GC算法的原理又可根据年龄代细分,堆中也可以划分线程隔离的分配缓冲区(Thread Local Allocation Buffer,TLAB),所以不能说堆中所有的内存区域都是线程共享的。

但无论是哪一个区域、怎样划分,其存储的都是对象实例,之所以划分为多个子区域是为了更好地管理Java堆、更快地分配内存、回收内存等。

Java堆在物理内存中不一定连续,但是逻辑上连续。

一种异常:OutOfMemoryError:堆扩展时没有足够内存。

2、方法区

方法区用来存储已被虚拟机加载的各种类信息、常量、静态变量、即时编译器编译后的代码等数据,方法区被描述为堆的一个逻辑部分,但其与Java堆还是有很大区别的,因为又称为(Non-Heap)。

这里也被称为“永久代”,因为其存储的数据是类信息、常量、静态变量等一般不会或者不能修改的数据,因此称为“永久代”,在HotSpot虚拟机中,GC收集器同样可以像堆中一样对这部分内存进行管理。

但“永久代”用来代替方法区更容易发生内存泄漏分体,不是一个很好的方法区解决方案。

一种异常:OutOfMemoryError:方法区扩展时没有足够内存。

3、运行时常量池

常量池用于存放编译器生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中存放。除了编译期生成加入常量池的常量,在程序运行的过程中也可能加入新的常量,比如String类的intern()方法就会将一个String引用类型加入到常量池中。

 

三、直接内存

 直接内存不是虚拟机运行时数据区的一部分,NIO类可以操作虚拟机内存外的内存区域,使用Native函数库直接分配堆外内存,并对其进行操作,这样避免了在Java堆和Native堆来回复制数据,以提高IO性能。

很显然,由于直接内存并非虚拟机内存的一部分,因此不受虚拟机内存上限的影响,但还是会受到物理内存或者操作系统对于每个进程分配的内存的限制,因此也会产生OutOfMemoryError异常。