Java内存模型 内存管理机制理解

 2019-12-10 16:02  阅读(1067)
文章分类:Java Core

JVM主要将内存分为栈和堆。如果细分的话有程序计数器,虚拟机栈,本地方法栈、堆、方法区。

如图所示

20191210001504\_1.png

程序计数器

由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。程序计数器(PC)是每个线程所私有的。可以看作是当前线程所执行的字节码的行号指示器。也可以理解为下一条将要执行的指令的地址或者行号。字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、 循环、 跳转、 异常处理、 线程上下文切换,线程恢复时,都要依赖PC。由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。
如果线程正在执行的是一个Java方法,PC值为正在执行的虚拟机字节码指令的地址

如果线程正在执行的是Native方法,PC值为空(未定义)

虚拟机栈

与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型: 每个方法被执行时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息. 每个方法被调用至返回的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程(VM提供了-Xss来指定线程的最大栈空间, 该参数也直接决定了函数调用的最大深度)。

局部变量表(对应我们常说的‘堆栈’中的‘栈’)存放了编译期可知的各种基本数据类型(如boolean、int、double等) 、对象引用 和 returnAddress类型(指向一条字节码指令的地址). 其中long和double占用2个局部变量空间(Slot), 其余只占用1个。

操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式。

动态链接就是将常量池中的符号引用在运行期转化为直接引用。每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。

在Java 虚拟机规范中,对虚拟机栈规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常(一般来说递归调用是常见的原因);如果虚拟机栈可以动态扩展(当前大部分的Java 虚拟机都可动态扩展,只不过Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError 异常。

本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。

什么是native方法

一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。 native方法主要用于加载文件和动态链接库,由于Java语言无法访问操作系统底层信息(比如:底层硬件设备等),这时候就需要借助C语言来完成了。被native修饰的方法可以被C语言重写。

Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”(GarbageCollected Heap)。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以堆中还可以细分为:新生代和老年代;再细分有Eden 空间、From Survivor 空间、To Survivor 空间等。这就涉及到了JVM的内存回收机制,此处不再深入,具体请看我其他的文章。

方法区

与堆一样,是被线程共享的区域。

方法区存储的大致内容如下:

1.每一个类的结构信息(包括类的名称、方法信息、字段信息)以及编译器编译后的代码
2.运行时常量池( Runtime Constant Pool)
3.字段和方法数据
4.构造函数和普通方法的字节码内容

5.类、实例、接口初始化时用到的特殊方法

有时候方法区也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型的卸载。

运行时常量池,用来存储编译期间生成的字面量和符号引用。在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。

名称 特征 作用 配置 异常
栈区 线程私有,使用一段连续的内存空间 存放局部变量表、操作栈、动态链接、方法出口 -XSs StackOverflowErrorOutOfMemoryError
线程共享,生命周期与虚拟机相同 保存对象实例 -Xms-Xmx-Xmn OutOfMemoryError
程序计数器 线程私有、占用内存小 字节码行号
方法区 线程共享 存储类加载信息、常量、静态变量等 -XX:PermSize-XX:MaxPermSize OutOfMemoryError
点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Java内存模型 内存管理机制理解

相关推荐