深入理解Java虚拟机
Java内存区域与内存溢出异常
运行时数据区域
Java虚拟机运行时数据区- 程序计数器
- 可以看作当前线程所执行的字节码的行号指示器,通过改变这个计数器的数值来选取下一条需要执行的字节码指令
- Java多线程式通过线程轮流切换、分配处理器执行时间的方式实现的,每条线程都有一个独立的程序计数器
- Java虚拟机栈
- 线程私有
- 每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)
- 用于存储局部变量表、操作数栈、动态连接、方法出口等信息
- 异常
- StackOverflowError:栈深度大于虚拟机允许的深度
- OutOfMemoryError:栈扩展无法申请到足够的内存
- 本地方法栈
- 与虚拟机栈功能类似,为虚拟机使用到的本地(Native)方法服务
- Java堆
- 被所有线程共享的一块内存区域,在虚拟机启动时创建
- 几乎所有的对象实例都在堆中分配内存
- 垃圾收集器管理的内存区域,也称为“GC堆”
- Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率
- 异常:
- OutOfMemoryError
- 方法区
- 线程共享
- 存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
- 别名“非堆(Non-Heap)”,目的与Java堆区分开来
- 运行时常量区
- 方法区的一部分
- Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常亮池表,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
- 具有动态新,运行期间也可以将新的常量放入池中
- 直接内存
- 不是虚拟机机运行时数据区的一部分
- NIO,引入一种基于通道与缓冲区的I/O方式,可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。避免在Java堆和Native堆中来回复制数据
HostSpot虚拟机对象探秘
- 对象的创建(new指令)
- 检查new指令的参数是否能在常亮池定位到一个类的符号引用,并且检查这个符号引用个代表的类是否已被加载、解析和初始化过
- 如果没有,执行相应的类加载过程
- 类加载通过后,为新生对象分配内存
- 指针碰撞(内存排列是规则的,指针往空闲的内存方向移动)
- 空闲列表(内存交错在一起,需要使用列表维护可以划分空间的内存地址)
- 并发情况下,对象的创建不是线程安全的
- 对分配空间的内存动作进行同步处理
- 内存分配的动作按照线程划分在不同的空间之中进行(本地线程缓冲,TLAB)
- 内存分配结束之后,将分配到的内存空间初始化为0(除对象头)
- 检查new指令的参数是否能在常亮池定位到一个类的符号引用,并且检查这个符号引用个代表的类是否已被加载、解析和初始化过
- 对象的内存布局
- 对象在堆中的存储布局可以划分为三个部分
- 对象头Header
- 实例数据(Instance Data)
- 对齐填充(Padding)
- 对象头
- Mark Word:用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁等
- 类型指针,即对象指向它的类型元数据的指针
- 对其填充
- 任何对象的大小都必须是8字节的整数倍
- 对象在堆中的存储布局可以划分为三个部分
- 对象的访问定位
- 句柄:reference存储的是稳定的句柄地址,在对象移动时,只会改变句柄的实例数据指针
-
直接指针:速度快,节省了一次指针定位的开销
通过句柄访问对象
通过直接指针访问对象
网友评论