Jvm的主要区块分布
大概分为以下个部分
- 程序计数器
- java虚拟机栈
- 本地方法栈
- 方法区
- 堆内存
- 常量池
详细介绍
程序技术器
是一块较小的内存空间, 在未优化的编译器中他用来指向当前线程执行的字节码的行号. 因为是指向当前线程的程序, 所以程序计数器是线程私有的, 每个线程都有一份程序计数器.
此区域理论上不会发生OOM
java虚拟机栈
虚拟机栈的生命周期和线程是相同的, 虚拟机栈是java用来描述方法执行的内存模型. 与程序计数器相同, 是对线程私有的, 每一个线程都会开辟一片虚拟机栈, 当进入到一个方法的时候, 就会创建一个栈帧(Stack Frame), 主要用于储存局部变量表, 操作数栈, 动态链接和方法返回地址
java虚拟机栈中, 可能会存在两种异常, StackOverflowError和OOM
补充知识:
- 栈帧(Stack Frame) 用于支持虚拟机进行方法调用和方法执行的数据结构, 线程私有, 生命周期为方法的生命周期
- 局部变量表 一组变量值存储空间,用于存放方法参数和方法内定义的局部变量, 将每一个变量储存在一个变量槽(Variable Slot)中, jvm虚拟机规范中没有限制slot所占空间大小, 但是规定了一个slot要可以放入一个32位的变量, class文件的code属性中的max_locals描述了该方法中所需slot的最大数量
- 操作数栈 它是一个后入先出栈(LIFO), 在方法刚刚开始的时候, 该栈是空的, 随着方法执行和字节码的指令的执行, 会将变量或者常量从局部变量表或对象实例中复制过来, 入栈, 经过字节码指令的操作后, 再返回或者写入局部变量表或对象实例中, 同样, class文件中的code属性中的max_stack描述了该方法中所需的最大stack深度
- 动态链接 一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于方法区中的运行时常量池, Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态连接(Dynamic Linking)。这些符号引用一部分会在类加载阶段或者第一次使用时就直接转化为直接引用,这类转化称为静态解析。另一部分将在每次运行期间转化为直接引用,这类转化称为动态连接。
- 方法返回地址 分为正常完成和异常完成, 正常完成就是表面上理解的这样, 方法执行完后, 返回给上层方法, 当发生异常时, 会现在异常表中寻求处理方法, 如果没有处理方法, 此时就会调用异常完成, 异常完成时返回地址是通过异常处理器表确定
本地方法栈
本地方法栈和java虚拟机栈非常类似,也是线程私有,也是具有一些信息存储。他们之间唯一的差别就是java虚拟机栈是为了java程序中的方法也就是字节码的方法服务的,而本地方法栈是给Native方法服务的。
方法区
用于存储已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据。
位置在堆内存的一块划分出来的区域内, 因为GC对此处的垃圾回收效率很差, 或者说几乎不回收, 所以也被称之为永久代
字符串常量池就存在于方法区中
堆内存
java堆(JAVA Heap) 是java虚拟机所管理的内存中最大的一块。java堆是被所有线程共享的一块区域,在虚拟机启动时创建。此内存的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。注意这里我们采用了几乎一词,因为随着编译器和虚拟机的发展,类似栈上分配、标量替换技术的出现使得部分对象实际上也不是一定在堆上分配。
垃圾回收器(GC) 主要也是针对该部分进行
除此之外,从内存分配的角度来看,多个线程可能会在java堆中划分出多个线程私有分配缓冲区(Thread Local Allocation Buffer , TLAB )。
补充知识:
- 栈上分配(Stack Allocation) 因为在堆上的对象, 回收的时候都是需要耗费一定的时间和性能, 如果一个对象被确定不会逃逸出方法之外,那让这个对象在栈上分配是一个不错的想法,对象占用的内存空间就可以随栈帧的出栈而被销毁。
- 标量替换(Scalar Replacement) 有一些小到不可再分割的量(int, double)称之为标量, 如果一个对象可以在栈上分配, 我们可以不创建整个对象, 而是使用多个标量来表达整个对象.
常量池
就如在方法区中的描述一样, 常量池用于储存编译器生成的各种字面量和符号引用, 包括但不限于编译器生成的各种字面量和符号引用, 也包括我们放入常量池的数据, 比如String的intern方法
Jvm之外的内存
当然, 我们可以使用一些方法来操作和开辟新的内存区域, 这些内存区域不属于Jvm一开始申请的内存, 而是在Jvm之外的一部分内存区域, 比如NIO所使用的的缓存区. 还有就是用虚引用来表示的一部分区域, 当然也可以使用unsafe类来操作的内存块