文章目录
- 线程私有
- 线程共享
- 堆
- 对象创建
Java内存区域
Java虚拟机会在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域
线程私有
线程私有区域是随着线程的创建而创建,随着线程的销毁而销毁,每个线程各自独立,互不干扰
程序计数器
作用:
- 当前线程执行的字节码的行号指示器
- 程序控制流的指示器
此区域是唯一不会出现OOM的区域
虚拟机栈
虚拟机栈是Java方法执行的线程内存模型
- 每个方法执行时,Java虚拟机栈都会同步创建一个栈桢(存储局部变量表,操作数栈,动态链接,方法出口等)
- 方法从被调用到执行完成返回,就是一个栈桢入栈到出栈的过程
线程共享
随虚拟机进程的启动而一直存在
堆
堆是虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,此区域的唯一目的就是存放对象和数组实例
Java堆是垃圾收集器管理的内存区域,也叫GC堆
堆分代
- Hotspot虚拟机基于经典堆分代来设计
- 新生代(一个eden,两个survivor)老年代
- 分代的根本原因还是为了能更好的回收内存,更高效的回收和减少内存碎片
如果在Java堆中没有内存完成实例分配,并且堆中也无法再扩展时,就会抛出OOM
方法区
- 方法区也是线程共享的区域
- 用于存储已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等数据
- JDK8之前是存放在永久代,JDK8后存放在本地内存(实现了元空间),废弃了永久代的概念
- 永久代是在堆中的,如果类型信息,常量,静态变量,代码缓存一多,可能会导致堆OOM,如果放在本地内存,发生OOM的概率相对低很多
运行时常量池
- 运行时常量池是方法区的一部分,常量池表用于存放编译器生成的各种字面量和符号引用
- 既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存 时会抛出OutOfMemoryError异常。
对象创建
- 当虚拟机遇到一条字节码new指令时,首先会去常量池中定位该类的符号引用,判断该符号引用代表的类是否被加载,解析,初始化过,没有则进行类加载
- 类加载检查通过后,为新生对象分配内存(对象所需内存大小在类加载完成后就可以知道)
- 指针碰撞:假设Java堆中内存是绝对规整的,所有被使用过的内存都被放在一 边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那 个指针向空闲空间方向挪动一段与对象大小相等的距离
- 空闲列表:如果Java堆中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,那 就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分 配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录
指针碰撞的线程安全问题
- 正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况
- 解决:CAS
对象内存布局
在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例 数据(Instance Data)和对齐填充(Padding)。
HotSpot虚拟机对象头的包括两类信息:
- 对象自身运行时数据(hashcode,gc分代,锁标志,是否偏向,偏向线程ID)Markword
- 类型指针