自动内存管理 -- 总结篇
内存划分及作用
常见问题
内存划分及作用
程序计数器
线程私有, 字节码行号指示器.
执行 Java 方法, 计数器记录的是字节码指令地址; 执行本地 (Native) 方法时, 为空.
本地方法栈
与虚拟机栈类似, 为 Native 方法服务
Java 虚拟机栈
每个方法执行对应一个栈帧, 存储局部变量表, 操作数栈, 动态连接, 方法出口等信息
局部变量表: 存放编译期可知的基本数据类型, 对象引用, 返回值地址
局部变量表以局部变量槽为单位, long 和 double 占两个槽位, 其余一个
栈帧中的内存大小在编译期间已经确定
线程请求的内存大于虚拟机允许的深度, 报错 stackoverflowerror; 栈拓展时无法申请足够内存, 报错 OutOfMemoryError
Java 堆
线程共享, 唯一目的存放对象实例
方法区
存储类型信息, 常量, 静态变量, 代码缓存等
运行时常量池
编译期生成的字面量和符号引用
直接内存
Java 堆中的 DirectByteBuffer 对象对这块内存直接操作, 避免数据在 Native 和 Java 堆中来回复制.
常见问题
普通对象的创建过程
检测类是否已被加载
当虚拟机遇到 new 指令时, 首先先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用, 并且检查这个符号引用代表的类是否已被加载, 解析和初始化过. 如果没有, 就执行类加载过程.
为对象分配内存
类加载完成以后, 虚拟机就开始为对象分配内存, 此时所需内存的大小就已经确定了
为分配空间初始化零值
保证对象没有赋初始值也可以使用
其他设置
设置对象头信息, 如所属类, hashcode,gc 分代年龄
执行 init 方法
按程序代码分配初始值
Java 堆为实例分配内存的方式
选择哪种分配方式由 Java 堆是否规整决定的, 而 Java 堆内存是否规整由垃圾回收器是否带有空间压缩整理能力决定的
Serial,ParNew ===> 指针碰撞
CMS===>空闲列表
连续空间
使用指针碰撞方式, 移动被占内存和可用空间的指针来分配. 多线程发生内存冲突时, 利用 CAS 加失败重试保证分配; 或者本地线程分配缓存 (TLAB) 方式分配内存
非连续空间
维护一张列表, 记录可用空间, 分配内存更新列表
对象内存布局
对象头
第一部分 "Mark Word": 运行时数据, 哈希码, GC 分代年龄, 锁状态
第二部分: 类型指针, 指向类的元数据;
实例数据
对其填充(因为对象起始地址必须是 8 字节的整倍数)
对象的两种访问定位
栈中的 reference 数据引用, 引用分为 "句柄访问","直接指针访问" 两种
句柄访问
堆中划分句柄池, 句柄中包含对象实例数据和类型数据各自具体地址. 优点: 对象被移动时, 只需改变句柄中实例数据指针
直接访问
直接访问对象地址. 优点: 少了一次开销, 访问速度更快
来源: https://www.cnblogs.com/CodeMLB/p/13257725.html