JVM内存分布

定义

JVM的结构可以分为三个部分:类加载系统、运行时数据区、执行引擎与本地方法接口,本文关注的只是运行时数据区。JVM内存结构可以分为:堆、栈(虚拟机栈,本地方法栈)、方法区和程序计数器(CPU寄存器暂且不说)几个部分,按照线程隔离性又可以分为两类:线程共享的堆和方法区,线程私有的栈和程序计数器。

配图如下


程序计数器

程序计数器作为线程私有的一小块内存,记录线程的字节码指令执行地址,这样可以保证在多线程环境中,当线程切换之后,从原来的地方恢复执行,即存储的是当前进程的指令地址,如果执行的本地方法,则不保存,且该内存区域不会发生内存溢出。

虚拟机栈

虚拟机栈的结构为一个个栈帧,对应着方法的调用,方法的调用和执行结束对应着一个栈帧的入栈和出栈(因此如果递归调用发生死循环则可能会抛出内存溢出,通过-Xss设置大小)。栈帧对应着方法,则其内部存储的有局部变量表、操作数栈、动态连接、方法出口等信息。

1、局部变量表

局部变量表中存放的有方法内部使用到的基本类型的变量、对象的引用变量、returnAddress类型地址变量。基本类型就是short、int、long等基本类型,这里关于64位的数据,并没有限制每个变量槽slot为32位,根据具体的虚拟机做出调整,调整结果就是要保证使用方式类似与32位情形,对于64位则分为两个slot(目前虚拟机已经可以优化为原子操作,当然此处讨论为线程局部变量,不存在并发的考虑);对象引用变量中存储的为对象的地址或者句柄地址,如果该方法为实例方法,则另存有隐式this对象的引用变量;returnAddress存有一条字节码指令地址。这里需要注意的一点就是局部变量不会自动赋默认值,所以使用前需要设定值,否则编译会出错。

2、操作数栈

操作数栈可以理解为操作变量的临时运算数据空间,虚拟机执行引擎所谓的“基于栈的执行引擎”,讲的也就是操作数栈,与局部变量表的数组空间类似,不过存储方式是以压栈、出栈的方式进行。所以在方法开始执行时,操作数栈是空的,之后进行不断的入栈、出栈操作,对局部变量表中变量计算和赋值。理论上各个栈帧是独立的,但是在实现中对结构进行了优化,值的传递可以通过部分内存共享的方式进行,即下一栈帧的操作数栈直接处理上一栈帧的部分局部变量。


3、动态连接

动态连接是符号引用到直接引用的一个转化形式,对应的还有一个静态连接。静态连接是指符号引用在初始化之前就已经转化为直接引用,属于前期连接,不支持运行时连接(称为不支持运行时绑定或许更有名),例如静态方法和构造方法;动态连接则更为普遍,因为Java的方法基本都属于运行时连接(多态的基础)。这里在栈帧中存有对方法区中类方法信息的引用,就是为了在方法调用中进行动态连接。

4、方法出口

方法的退出有两种方式,正常退出和异常退出。正常退出即为一般遇到的返回字节码指令,异常退出则是执行过程中产生异常且方法体内没有相应的异常处理程序,该方式下相当于将异常抛出给调用者处理,不会有返回值。无论哪一种方式都需要返回到调用者处继续执行,正常退出即为下一步执行地址从调用者处开始执行,也就是程序计数器中保存的地址,如果有返回值则将返回值压入上一栈帧的操作数栈中(可能会有操作数栈共享),异常退出则由异常处理器来指定退出地址。

本地方法栈

本地方法栈同样是一个为方法提供结构信息的存储空间,根据其名字可知,它所服务的方法使用的是本地语言,作用类似与虚拟机栈

提到堆也就有了两个基本概念:

1、线程共享的区域

既然是多线程共享区域,免不了的一个问题就是并发冲突,可以设想空闲内存地址由一个空闲列表来记录,系统内并发进行对象的创建时,申请空间可能存在分配内存后尚未更新空闲列表,同一个地址内存又被分配,即存在冲突现象。

解决内存分配冲突方式有两种:

1、将内存分配操作变为原子性操作,利用CAS机制加失败重试(CAS:当前值与预期值一致,则更新,否则不更新)。

2、TLAB(Thread Local Allocation Buffer),线程私有分配区,类似一种ThreadLocal思想,给每个线程分配一块区域,线程内创建对象则在该区域上分配。通过-XX:+/-UserTLAB来选择启用。

2、对象的主要分配区域

堆作为虚拟机管理的最大的一个内存区域,是对象和数组的主要生存地(因为虚拟机优化技术,线程私有的对象可能放到栈中),所以也是垃圾收集器重点关注的区域。根据垃圾收集器的分代收集的特性,可以将堆分为新生代和老年代两个部分(对应的也必须存在内存整理操作)。

方法区

同样属于线程共享的区域,存储类信息、常量、静态变量、即时编译器编译后代码等。号称“永久代”,即基本不会发生GC操作,因为回收该区域获得的收益相对于付出的代价比较低,不过虽然少,但是收集操作还是有的。主要回收不使用的常量和类,常量不被使用,即引用数为零,类不被使用意思是:

1、虚拟机中不存在该类实例

2、类加载器已经被回收

3、类对象的Class对象没有被引用

方法区中有一个运行时常量池的区域,由运行两个字就可以看出其带有动态性,即运行时可以将常量放入其中(String类的intern方法)。

总结

除了程序计数器标志指令执行地址之外,其他区域都有可能抛出OutOfMemoryError异常。在以上运行时内存结构之外,还有一个直接内存的概念,不属于虚拟机内存,但是可以在运行时使用。在NIO中引入通道和缓冲区,可以直接利用通道映射一块物理内存为缓冲区使用,用以提升操作传统IO的效率。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值