JVM在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。 这些区域有各自的用途, 以及创建和销毁的时间, 有的区域随着虚拟机进程的启动而一直存在, 有些区域则是依赖用户线程的启动和结束而建立和销毁。

运行时数据区:JVM工作的时候用的内存区域
线程共享:方法区、堆,随着虚拟机的启动而创建
线程独有:程序计数器、虚拟机栈、本地方法栈,随着线程的创建而创建
1. 方法区
线程共享
Java8之后称为元空间
存放:
- 从.class文件里加载的二进制流,即类的信息
- 静态变量
- 常量
- 即时编译器编译后的代码缓存
2. 程序计数器
存储字节码指令地址
线程独有。为什么?线程恢复。
3. 虚拟机栈

线程独有,随着线程的创建而创建
虚拟机栈描述的是Java方法执行的线程内存模型: 每个方法被执行的时候, Java虚拟机都会同步创建一个栈帧用于存储局部变量表、 操作数栈、 动态连接、 方法出口等信息。每一个方法被调用直至执行完毕的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
调用方法时,给方法创建栈帧,入栈,执行完毕,出栈
栈帧:局部变量表(—>变量槽)、操作数栈、动态链接、方法出口。
Linux下栈的默认大小:1024KB
设置大小:-Xss。-Xss1m -Xss1024k
4. 堆
最大,线程共享,在虚拟机启动时创建,存放各种实例。
-Xms:初始大小
-Xmx:能扩张到的最大容量
生产环境一般都设置成一样,避免频繁GC
-Xms3G -Xmx3G
5. 本地方法栈
运行操作系统本地方法的时候用到,过程和虚拟机栈类似。
6. 直接内存
不是虚拟机运行时数据区的一部分,JVM规范也没有定义该区域。
读写性能优于堆
直接内存大小可以通过MaxDirectMemorySize设置,如果不设置,默认=-Xmx
7. 对象分配、 布局和访问
7.1 对象的创建过程
- 检查是否已经加载类
- 分配内存。
两种策略:指针碰撞、空闲列表
线程安全问题解决:TLAB(开启的情况)——>CAS+失败重试(未开启TLAB的情况) - 对象的初始化—>实例变量赋零值
- 对象的设置,对象头
- 执行构造函数
HotSpot源码
// 确保常量池中存放的是已解释的类if (!constants->tag_at(index).is_unresolved_klass()) {// 断言确保是klassOop和instanceKlassOopoop entry = (klassOop) *constants->obj_at_addr(index);assert(entry->is_klass(), "Should be resolved klass");klassOop k_entry = (klassOop) entry;assert(k_entry->klass_part()->oop_is_instance(), "Should be instanceKlass");instanceKlass* ik = (instanceKlass*) k_entry->klass_part();// 确保对象所属类型已经经过初始化阶段if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {// 取对象长度size_t obj_size = ik->size_helper();oop result = NULL;// 记录是否需要将对象所有字段置零值bool need_zero = !ZeroTLAB;// 是否在TLAB中分配对象if (UseTLAB) {result = (oop) THREAD->tlab().allocate(obj_size);}if (result == NULL) {need_zero = true;// 直接在eden中分配对象retry:HeapWord* compare_to = *Universe::heap()->top_addr();HeapWord* new_top = compare_to + obj_size;// cmpxchg是x86中的CAS指令, 这里是一个C++方法, 通过CAS方式分配空间, 并发失败的// 话, 转到retry中重试直至成功分配为止if (new_top <= *Universe::heap()->end_addr()) {if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {goto retry;}result = (oop) compare_to;}if (result != NULL) {// 如果需要, 为对象初始化零值if (need_zero ) {HeapWord* to_zero = (HeapWord*) result + sizeof(oopDesc) / oopSize;obj_size -= sizeof(oopDesc) / oopSize;if (obj_size > 0 ) {memset(to_zero, 0, obj_size * HeapWordSize);}}// 根据是否启用偏向锁, 设置对象头信息if (UseBiasedLocking) {result->set_mark(ik->prototype_header());} else {result->set_mark(markOopDesc::prototype());}result->set_klass_gap(0);result->set_klass(k_entry);// 将对象引用入栈, 继续执行下一条指令SET_STACK_OBJECT(result, 0);UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);}}}
7.2 对象的内存布局
7.2.1 对象头
对象自身的运行时数据
如哈希码(HashCode) 、 GC分代年龄、 锁状态标志、 线程持有的锁、 偏向线程ID、 偏向时间戳等,这些信息被称为Mark Word
类型指针
对象指向它的类型元数据的指针
数组长度
如果对象是数组,还会有记录数组长度的数据
7.2.2 实例数据
属性值
7.3 对象的访问

