JVM之内存区域介绍

JVM所管理的内存分为以下几个运行时数据区域:

程序计数器:它是一块很小的内存空间,可以看作是当前线程所执行的字节码的行号指示器,在字节码解释器工作时就是通过改变这个计数器的值来判断下一条执行哪一条字节码指令,

由于java虚拟机的多线程是通过线程轮流切换并分配处理处理器执行时间额方式来实现,所以在一个时刻,只会执行一条线程中的指令.为了线程切换后能恢复到正确的执行位置,每条线程都会有一个独立的程序计数器.这个就是"线程私有"的内存.

如果执行的是java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是Native方法,那么这个计数器为空(调用Native方法会下面介绍).

这块区域内存是唯一一个在java虚拟机中没有规定任何OutOfMemoryError情况的区域.

java虚拟机栈:与程序计数器一样,这个内存区域也是线程私有的,他的生命周期和线程相同,每一个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法入口等信息,每一个方法调用直至执行完成额过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程.

在java虚拟机中对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟(如递归深度),所允许的深度将会抛出StackOverflowError异常;如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常(如创建对象时,栈内存不足以创建这个对象时).

在这里大家都知道栈是什么,但是栈帧和栈的区别的话我在此介绍一下

栈帧和栈的区别:一个栈中可以有多个栈帧,栈帧随着方法的调用而创建,随着方法的结束而消亡。该栈帧中存储该方法中的变量,原则上各个栈帧之间的数据是不能共享的,但是在方法间调用时,jvm会将一方法的返回值赋值给调用它的栈帧中。每一个方法调用,就是一个压栈的过程,每个方法的结束就是一个弹栈的过程。压栈都将会将该栈帧置于栈顶,每个栈不会同时操作多个栈帧,只会操作栈顶,当栈顶操作结束时,会将该栈帧弹出,同时会释放该栈帧内存,其下一个栈帧将变为栈顶。栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。

本地方法栈:本地方法栈与虚拟机栈所发挥的作用是非常相似的,他们之间的区别就是虚拟机栈调用的是java方法,而本地方法栈调用的Native方法,Native方法所指的就是调用的是其他语言的方法,比如C语言,那么这个方法就会被Native修饰符修饰.

堆:java堆是java虚拟机占用内存最大的内存区域,java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,次内存区域唯一目的就是用来存放对象实例,几乎所有对象实例都在此分配内存,但是现在技术逐渐的成熟,所有的对象都分配在堆上面也渐渐不是那么绝对了.

如果在堆上面没有足够的内存分配给实例,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常

方法区:方法区和推一样,是各个线程共享的内存区域,他用于存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据.方法区也叫永久代,但是本质上并不等价,因为方法区也会发生回收,只是回收数量很少.

运行时常量池:运行时常量池是方法区的一部分,他是用于存放编译期生成的各种字面量和符号引用,这部分将在类加载后进入方法区的运行时常量池存放.

既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法申请到内存空间是会抛出OutOfMemoryError异常

直接内存:在JDK1.4中新加入NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作.这样能在一些场景中显著提高性能,因为避免了在Java堆和Native对中来回复制数据.

显然,本机直接内存的分配不会受到Java堆大小的影响,但是既然是内存,肯定还是会受到本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限制.服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但是经常忽略直接内存,是的各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常.这个直接内存也就是堆外内存.
 

猜你喜欢

转载自blog.csdn.net/qq_41383905/article/details/99220874