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

image.png

运行时数据区:JVM工作的时候用的内存区域

线程共享:方法区、堆,随着虚拟机的启动而创建

线程独有:程序计数器、虚拟机栈、本地方法栈,随着线程的创建而创建

1. 方法区

线程共享

Java8之后称为元空间

存放:

  1. 从.class文件里加载的二进制流,即类的信息
  2. 静态变量
  3. 常量
  4. 即时编译器编译后的代码缓存

JRocket,J9没有方法区

2. 程序计数器

存储字节码指令地址

线程独有。为什么?线程恢复。

没有GC、没有OOM

3. 虚拟机栈

image.png

线程独有,随着线程的创建而创建

虚拟机栈描述的是Java方法执行的线程内存模型: 每个方法被执行的时候, Java虚拟机都会同步创建一个栈帧用于存储局部变量表、 操作数栈、 动态连接、 方法出口等信息。每一个方法被调用直至执行完毕的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

调用方法时,给方法创建栈帧,入栈,执行完毕,出栈

栈帧:局部变量表(—>变量槽)、操作数栈、动态链接、方法出口。
image.png

Linux下栈的默认大小:1024KB
设置大小:-Xss。-Xss1m -Xss1024k

栈顶方法=当前方法=正在执行的方法

4. 堆

最大,线程共享,在虚拟机启动时创建,存放各种实例。

-Xms:初始大小
-Xmx:能扩张到的最大容量

生产环境一般都设置成一样,避免频繁GC

-Xms3G -Xmx3G

5. 本地方法栈

运行操作系统本地方法的时候用到,过程和虚拟机栈类似。

6. 直接内存

不是虚拟机运行时数据区的一部分,JVM规范也没有定义该区域。

读写性能优于堆

直接内存大小可以通过MaxDirectMemorySize设置,如果不设置,默认=-Xmx

7. 对象分配、 布局和访问

7.1 对象的创建过程

  1. 检查是否已经加载类
  2. 分配内存。
    两种策略:指针碰撞、空闲列表
    线程安全问题解决:TLAB(开启的情况)——>CAS+失败重试(未开启TLAB的情况)
  3. 对象的初始化—>实例变量赋零值
  4. 对象的设置,对象头
  5. 执行构造函数

HotSpot源码

  1. // 确保常量池中存放的是已解释的类
  2. if (!constants->tag_at(index).is_unresolved_klass()) {
  3. // 断言确保是klassOop和instanceKlassOop
  4. oop entry = (klassOop) *constants->obj_at_addr(index);
  5. assert(entry->is_klass(), "Should be resolved klass");
  6. klassOop k_entry = (klassOop) entry;
  7. assert(k_entry->klass_part()->oop_is_instance(), "Should be instanceKlass");
  8. instanceKlass* ik = (instanceKlass*) k_entry->klass_part();
  9. // 确保对象所属类型已经经过初始化阶段
  10. if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
  11. // 取对象长度
  12. size_t obj_size = ik->size_helper();
  13. oop result = NULL;
  14. // 记录是否需要将对象所有字段置零值
  15. bool need_zero = !ZeroTLAB;
  16. // 是否在TLAB中分配对象
  17. if (UseTLAB) {
  18. result = (oop) THREAD->tlab().allocate(obj_size);
  19. }
  20. if (result == NULL) {
  21. need_zero = true;
  22. // 直接在eden中分配对象
  23. retry:
  24. HeapWord* compare_to = *Universe::heap()->top_addr();
  25. HeapWord* new_top = compare_to + obj_size;
  26. // cmpxchg是x86中的CAS指令, 这里是一个C++方法, 通过CAS方式分配空间, 并发失败的
  27. // 话, 转到retry中重试直至成功分配为止
  28. if (new_top <= *Universe::heap()->end_addr()) {
  29. if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {
  30. goto retry;
  31. }r
  32. esult = (oop) compare_to;
  33. }
  34. if (result != NULL) {
  35. // 如果需要, 为对象初始化零值
  36. if (need_zero ) {
  37. HeapWord* to_zero = (HeapWord*) result + sizeof(oopDesc) / oopSize;
  38. obj_size -= sizeof(oopDesc) / oopSize;
  39. if (obj_size > 0 ) {
  40. memset(to_zero, 0, obj_size * HeapWordSize);
  41. }
  42. }
  43. // 根据是否启用偏向锁, 设置对象头信息
  44. if (UseBiasedLocking) {
  45. result->set_mark(ik->prototype_header());
  46. } else {
  47. result->set_mark(markOopDesc::prototype());
  48. }
  49. result->set_klass_gap(0);
  50. result->set_klass(k_entry);
  51. // 将对象引用入栈, 继续执行下一条指令
  52. SET_STACK_OBJECT(result, 0);
  53. UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
  54. }
  55. }
  56. }

7.2 对象的内存布局

7.2.1 对象头

对象自身的运行时数据

如哈希码(HashCode) 、 GC分代年龄、 锁状态标志、 线程持有的锁、 偏向线程ID、 偏向时间戳等,这些信息被称为Mark Word

类型指针

对象指向它的类型元数据的指针

数组长度

如果对象是数组,还会有记录数组长度的数据

7.2.2 实例数据

属性值

7.3 对象的访问

image.png