一 JVM内存模型

目录:

1.JVM内存区域

2.一个对象是怎么创建的

3.对象在内存中的布局

4.对象的访问定位


1.JVM内存区域

分为线程共享区,和线程独占区

线程共享区:

1.方法区(HOTSPOT里的永久代,JDK8的METASPACE;存放:类信息,静态变量,编译后代码,常量(运行时常量池));

2.堆

(存放对象实例;垃圾收集器管理的主要区域;新生代,老年代(标量替换,逃逸分析,栈上分配)也会分配线程私有的堆,方便垃圾收集;-xmx -xms 设置堆的最大,最小值)

逃逸分析的三种情况:全局变量赋值,方法返回值,实例引用传递(https://blog.csdn.net/scythe666/article/details/51919015)

没有逃逸的对象,可以在栈上分配。逃逸分析必须在JIT里完成,因为可以通过运行时的动态代理改变一个类的行为。在运行时只有一个线程访问,会进行同步锁消除。

标量替换(http://rednaxelafx.iteye.com/blog/659108/)

在堆中分配对象的时候有2个算法;

1.指针碰撞 (适用于内存整齐的情况[ GC 算法:标记-整理 compact][ 新生代],已使用在一边,未使用在一边,分配内存既是指针移动。

2.空闲链表(已分配与未分配内存相互交错[ GC 算法:标记-清理 sweep][ 老年代],用链表记录空闲内存块,根据空闲列表分配内存。CMS垃圾回收器使用,是为了支持Mark-Sweep 算法)

线程独占:

1.程序计数器(存放的是当前线程执行到的字节码行号,在执行NATIVE,计数器值为UNDEFINED,唯一一个在JAVBA虚拟机规划中没有规定任何OUT OF MEMORY ERROR情况的区域)

2.虚拟机栈(往栈里推入的元素叫栈帧

栈帧:每个方法执行,创建一个栈帧。用于存储局部变量表,操作数栈,动态链接,方法出口。

局部变量表: 存放编译器可知的各种基本数据类型,引用类型,returnAddress类型

内存空间在编译器完成分配,当进入一个方法时,这个方法需要在帧分配多少内存是固定的,在方法运行期间是不会改变局部变量表的大小。)

3.本地方法栈  (用于执行NATIVE方法的栈)


最后还有一块直接内存,也会引起OOM


直接内存:NIO DirectByteBuffer,能够直接分配堆外内存,为了提高性能,受到物理内存制约。


2.一个对象是怎么创建的

1.去方法区找对象类的符号引用

2.如果没找到,需要调用类加载器去加载,解析和初始化

3.在堆上分配内存

4.将分配的内存初始化0值

5.调用对象init方法(包含构造函数和方法块)


3.对象在内存中的布局

1. Header (对象头)

      a. 自身运行时数据(Mark Word)(哈希值,GC分代年龄, 锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳)

      b. 类型指针(指向类的元数据的指针,并非一定需要这个指针)

      c. JAVA数组 在对象头里还有一块记录数组长度的空间

2. InstanceData

3. Padding (要求对象起始地址是8个字节的整数倍)


了解了对象头的结构,可以拓展一下SYNCHRONIZED的实现原理:

java synchronized 解析

里面介绍了偏向锁,轻量级锁,重量级锁的知识。

(以HashMap为例):

其只有Key和Value是有效数据,共2*8B=16B,包装成Long对象后分别具有了8B标记字和8B的类型指针,共16B*2=32B;两个对象组成Map.Entry后多了16B对象头、一个8B的next字段、4B的int类型的hash字段,还必须添加4B的空白填充。共32B;最后还有对HashMap中对此Entry的8B的引用。所以空间利用率为 16B / (16B+32B+32B+8B) ≈ 18%

4.对象访问定位

对象的访问定位如下图,HOTSOPT用的是第2种算法:

1.使用句柄(先指向堆里的句柄池,再从句柄池找到指针,优点是只需要修改句柄),

2.直接指针(减少性能开销)

需要存2个数据, 到对象实例数据的指针,到对象类数据的指针


你可能感兴趣的:(一 JVM内存模型)