java虚拟机内存管理机制之java内存区域

一、与C、C++语言区别

①对于C语言来说,既拥有每一个对象的“所有权”,又担负这每一个对象生命开始到终结的维护责任。

②对于java语言而言,在虚拟机自动内存管理的机制下,无需为每一个new操作去写对应的delete/free代码,不容易出现内存泄漏和内存溢出。

二、运行时数据区域

当程序员们讨论起java内存的时候,大部分都是以“堆内存”,“栈内存”来区分,这样区分是比较粗糙的。

java虚拟机在运行java程序的时候,会将其管理的内存划分为不同的区域,不同内存区域用途不同,创建及销毁的时间也不同。

虚拟机内存运行时数据区域按照是否为线程共享的角度可划分为两部分,线程私有部分,包括程序计数器,虚拟机栈,本地方法栈;线程共享部分,包括堆,方法区(Non_heap),运行时常量池。

1.程序计数器

①内存占用情况:占用较小的内存空间。

②是否线程私有:是。

③用途:可以看做当前线程所执行的字节码的行号指示器。

④java虚拟机规范:此区域是唯一一块没有规定任何OutOfMemoryError情况的区域。

java虚拟机多线程实现原理:通过多线程间的轮流切换并分配处理器执行时间来实现。注意,在任一时刻,一个处理器(多核处理器中的一个内核)都只会执行一条线程中的指令。

为了线程切换后能恢复到正确的执行位置,每一个线程都需要一个独立的程序计数器,各程序计数器之间相互独立,互不影响。如果当前线程正在执行java方法,则计数器记录了“正在执行的虚拟机字节码指令的地址”,如果正在执行的是Native方法,则这个程序计数器的值为空。

2.java虚拟机栈

①内存占用情况:--。

②是否线程私有:是。

③用途:描述的是java方法执行的内存模型,每个java方法执行时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。(每一次java方法的调用至执行完毕都会伴随着一次栈帧在虚拟机栈中的入栈到出栈的过程)

④java虚拟机规范:规范中为此内存区域规定了两种异常(StackOverflowError,OutOfMemoryError);当线程请求的栈深度大于虚拟机所允许的深度时,抛出StackOverflowError异常;当虚拟机动态扩展内存时,无法申请到足够的内存,则抛出OutOfMemoryError异常;

局部变量表:存放了编译器可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double),对象引用(reference类型),returnAddress类型(指向了一条字节码的指令地址)。其中64位long和double数据会占用2个局部变量空间,其余类型占用1个。局部变量表所需内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量的大小。

3.本地方法栈

①内存占用情况:--。

②是否线程私有:是。

③用途:与java虚拟机栈作用相似,只不过服务的对象不同;java虚拟机栈为虚拟机执行java方法服务,而本地方法栈为虚拟机执行Native方法服务。

④java虚拟机规范:未对本地方法栈中使用的语言,使用方式和数据结构作强制规定,可自由实现;同样定义了两种异常。

4.java堆(又称“GC堆”)

①内存占用情况:java虚拟机管理的内存中最大的一块。

②是否线程私有:否,所有线程共享。

③用途:唯一目的,存放对象实例(并不绝对)

④java虚拟机规范:所有对象实例以及数组都需要在java堆上分配;java堆可以处于物理上不连贯的内存空间,只要逻辑上连续即可,在实现时,既可以固定大小,也可以扩展(通过-Xms,-Xmx控制);如果没有内存完成实例分配,并且堆也无法在扩展时,抛出OutOfMemoryError异常。

java堆是垃圾收集器主要管理的地方,垃圾收集器基本都采用分代收集算法。所以从内存回收角度,java堆可以细分为新生代和老年代,再细致一点可以分为:Eden空间,From Survivor空间,To Survivor空间;从内存分配角度,线程共享的java堆中可能划分出多个线程私有的分配缓存区(TLAB,Thread Local Allocation Buffer)。

5.方法区

①内存占用情况:--。

②是否线程私有:否,所有线程共享。

③用途:存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码。

④java虚拟机规范:把方法区描述为堆的一个逻辑部分,Non-Heap;同样对方法区限制宽松,不需要连续内存,可选择固定大小或可扩展,还可以选择不实现垃圾回收;当方法区无法满足内存分配需求时,抛出OutOfMemoeyError异常。

“永久代”:仅仅是HotSpot虚拟机设计团队选择把GC分代收集扩展至方法区,或者说使用“永久代”来实现方法区,这样HotSpot的垃圾收集器可以像管理java堆一样管理这一部分内存,但这并不意味着数据进入方法区后就永久的存在了,这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。

6.运行时常量池

①内存占用情况:--。

②是否线程私有:否。

③用途:方法区的一部分;常量池,class文件除了有类的版本,字段,方法,接口等描述信息外,还有一项信息就是常量池(Constant Pool Table),用于存放编译器生成的各种字面常量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池存放。

④java虚拟机规范:当常量池无法申请到内存时,抛出OutOfMemoryError异常。

三、非运行时数据区

1.直接内存

①非java虚拟机规范中定义的内存区域。

②也可能会导致OutOfMemoryError异常。

jdk在1.4版本中增加了NIO(New Input/Output)类,引入了一种基于通道(channel)与缓冲区(Buffer)的IO方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用操作,这样可以在一些场景中显著提高性能,避免了java堆与Native堆中来回复制数据。

猜你喜欢

转载自blog.csdn.net/soongp/article/details/79963125