Day12 jvm 内存模型JMM

发布时间 2023-12-04 17:49:45作者: 问稻

1. jvm 内存模型 JMM

原帖链接

  • JMM控制 Java 线程之间的通信,决定一个线程对共享变量的写入何时对另一个线程可见。
  • 每条线程在自己的工作内存中对共享变量(副本)进行操作,JMM再负责把这些操作同步到主内存中
  • JVM1.8 用Meta space(元空间)(在JVM外的本地内存中)取代了方法区(Method Area)(在JVM堆中)

jvm内存分配

1.1 Java heap

Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap)

1.1.2 字符串常量池
  • 字符串常量池原本存放于方法区,jdk7开始放置于堆中。
  • 字符串常量池存储的是string对象的直接引用,而不是直接存放的对象,是一张string table
1.1.3 对象实例
  • 类初始化生成的对象
  • 基本数据类型的数组也是对象实例
1.1.4 线程分配缓冲区
  • 增加线程分配缓冲区是为了提升对象分配时的效率

1.2 本地内存(元空间实现的方法区)

元空间并不在虚拟机中,而是使用本地内存

  • JDK8开始用元空间实现原来java heap中的方法区

  • -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC(garbage collection)会对改值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。

  • -XX:MaxMetaspaceSize,最大空间,默认是没有限制的

1.2.1类元信息(Klass)(有点类定义的意思)
  • 类元信息在类编译期间放入方法区,里面放置了类的基本信息,包括类的版本、字段、方法、接口以及常量池表(Constant Pool Table)
  • 常量池表(Constant Pool Table)存储了类在编译期间生成的字面量、符号引用,这些信息在类加载完后会被解析到运行时常量池中
1.2.2运行时常量池(Runtime Constant Pool)
  • 运行时常量池主要存放在类加载后被解析的字面量与符号引用

1.3 本地方法栈

本地方法栈则是为执行本地方法(Native Method)服务的。简单地讲,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制。

2.TLAB机制

堆是全局共享的,因此在同一时间,可能有多个线程在堆上申请空间,在并发场景中,就会存在两个线程先后把对象引用指向了同一个内存区域,所以对象的内存分配过程需要一个同步机制,但是无论是使用哪种同步方案(实际上虚拟机使用的可能CAS),都会影响内存的分配效率。所以HotSpot虚拟机的提供了一个解决方案:TLAB分配,即Thread Local Allocation Buffer。(TLAB只是的一个优化方案,不代表所有的虚拟机都有这个特性)

  • 每个线程在Java堆中预先分配一小块内存,然后再给对象分配内存的时候,直接在自己这块”私有”内存中分配,当这部分区域用完之后,再分配新的”私有”内存。

因为有了TLAB技术,堆内存是线程共享的这个命题是不准确的

3. 对象在JVM中的内存布局

推荐看以下链接

Java对象实例化过程以及对象实例内存分布

Java对象实例内存分布

在new一个对象时,jvm创建instanceOopDesc,来表示这个对象,存放在堆区,其引用存放在栈区;平时说的Java Object Layout就是instanceOopDesc,它用来表示对象的实例信息;instanceOopDesc对应java中的对象实例。

instanceOopDesc包含三部分:
image

    1. 对象头,也叫Mark Word,主要存储对象运行时记录信息,如hashcode, GC分代年龄,锁状态标志,线程ID,时间戳等;
  • 对象头还包括 class pointer指向方法区的 Klass

    • 每个实例对象对klass的引用都是同一个
    1. 实例数据,是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)
    1. 填充。无实际意义, 。 HotSpot 要求对象起始地址都是 8 字节的整数倍,所以要对齐
      image

image

image