运行时数据区
内存概览图
程序计数器
这里的程序计数器也就如同操作系统中的程序计数器,可以看作用于指定当前线程所执行的字节码。在Java虚拟机中,一个处理器内核只会执行一条线程的指令。各条线程之间的程序计数器互不影响,独立存储。
如果当前线程正在执行Java方法,则计数器指向虚拟机字节码的内存地址。如果执行Native方法,则计数器为空
虚拟机栈
虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行时,虚拟机会同步创建一个栈帧用于存储局部变量表,操作数栈、动态连接、方法出口等。
方法的调用与完成对应着栈帧在栈空间中的入栈与出栈
模型如下:
异常信息:
- 当线程请求的栈深度大于虚拟机允许的深度,将抛出StackOverflowError
- 如果栈容量可以动态扩展,当栈需要扩展且无法申请到足够的内存将抛出OutOfMemoryError
本地方法栈
为虚拟机使用到的本地方法服务
异常信息同上
堆
Java堆几乎是所有对象实例分配的空间。也是被GC管理的内存区域
在线程共享的堆内存中也可以分配出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)
主流Java堆内存大小是可扩展的(通过参数-Xmx:最大,-Xms:最小;来进行设定)
如果堆无法再扩展,则将抛出OutOfMemoryError异常
方法区
用于存储已经被虚拟机加载的类型信息常量,静态变量,及时编译器编译后的代码缓存等数据
《Java虚拟机规范》并未对方法区做严格的实现规范。
运行时常量池
运行时常量池是方法区的一个部分。Class文件中的常量池表用于存放编译期生成的各种字面量与符号引用。而这部分内容将存放到运行时常量池
此外,运行时常量池还会存储由符号引用翻译出来的直接引用
直接内存
在JDK1.4中加入了NIO,可以使用Native函数库直接分配堆外内存。
HotSpot虚拟机对象
对象创建
使用new关键字创建对象时
- 检查指令的参数能否在常量池中定位到类的符号引用,(并检查这个类是否已被加载、解析和初始化)
- 为新生对象分配内存,
- 指针碰撞——所有被使用过的内存放一边,空闲内存放另一边,中间用指针分隔
- 空闲列表——内存不规整,需要维护一个空闲内存列表,分配时从列表中找到足够的空间分配给对象
此外,由于对象创建非常频繁,在并发情况下:可能出现正在给对象A分配内存,指针还未修改,对象B又同时使用原来的指针进行内存分配。
解决上述问题又两种可选方案:①对分配内存空间操作进行同步处理(CAS+失败重试)②把内存分配动作按照线程划分在不同的空间中,每个线程在堆中预先分配一块内存,称为Thread Local Allocation Buffer(TLAB),只有当线程的本地缓冲区用完,分配新缓冲区时同步。可使用-XX:+/-UseTLAB
指定是否启用TLAB
- JVM需要对对象进行必要的设置
对象内存布局
对象在堆内存存储布局可划分为:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
Header
Header存储两类信息:
第一类:存储对象运行时数据,如:哈希码,GC分代年龄、锁状态、偏向线程ID、偏向时间戳
第二类:类型指针,JVM通过指针确定对象是哪个类的实例
在32位机上占32字节
Instance Data
在程序中定义的各种字段,字段内存分配原则:相同宽度字段在一起,在这个前提下,弗雷定义的变量会在子类之前
Padding
虚拟机要求任何对象大小必须是8字节整数倍
对象的访问
主流引用访问的方式有:
-
使用句柄
-
使用直接指针访问