JVM_java内存区域

运行时数据区域

java 虚拟机的内存区域有各自的创建和销毁时间,如,栈,程序计数器依赖于线程的启动,而方法区和堆随着 java 虚拟机的启动而存在。

ANDRtx.png

程序计数器

程序计数器可以理解为,保存当前字节码执行的行号,cpu 按照程序计数器的指示,有序地执行代码逻辑。当线程恢复时,也需要程序计数器指示,线程被挂起前的执行位置。

因此,每个线程都有一个程序计数器,来保证线程切换后的恢复。

如果执行的是 native 方法,程序计数器为空。

java 虚拟机栈

虚拟机栈也是线程私有的区域,程序运行时,执行方法的调用之前,会将所有参数入栈,被调用的方法,按照所需要的参数个数,从栈中取用。方法调用完毕,则会出栈。

异常情况

  • 线程请求的栈深度大于虚拟机允许的最大深度,如递归,死循环(方法迟迟无法执行完毕,参数不断入栈)
  • 动态扩展时申请不到足够的内存

本地方法栈

本地方法栈用于存放 native 方法的参数

java 堆

用于存放对象实例,java 堆是线程共享的。
java 堆中的区域还能再细分,比如新生代,老年代。划分的目的主要是为了内存回收。

异常情况:OOM

  • 内存泄露:对象得不到回收,无法释放已申请的内存空间
  • 内存溢出:申请不到足够的空间

方法区

与 java 堆一样,属于线程共享的区域,存放编译后的代码数据,如加载的类信息,常量,静态变量等。

常量池

属于方法区的一部分

HotSpot 虚拟机对象

内存分配

分配方式
  • 指针碰撞:假设堆中内存规整,用过的放在一边,没用过的放另一边,那么内存的分配就是移动指针,找到一块没用的区域。
  • 空闲列表:虚拟机维护一张列表,记录哪些内存块可用。

java 堆是否规整,取决于垃圾收集器。

线程安全
  • 对分配内存的动作进行同步处理(原子性操作)
  • 将内存分配的动作按照线程划分在不同的空间中执行

对象的内存布局

对象在内存中的布局可以分为:对象头,实例数据,对齐填充

对象头
  • 储存运行时的自身数据,如哈希码,锁状态标志
  • 类型指针,指向元数据
实例数据

对象真正存储的有效信息,代码中定义的各种类型的字段内容

对齐填充

占位符

对象的访问定位

对象的访问方式由虚拟机决定

  • 句柄:java 堆中划分出句柄池,reference 中指向句柄地址,句柄包含对象的实例数据和类型数据地址

AN5Yxs.png

  • 直接指针:reference 直接指向对象的实例数据,而实例数据中包含类型数据的地址

AN5NMn.png

使用句柄的好处是,句柄比较稳定,如果对象被移动,只需要修改句柄而不用修改 reference,使用指针的好处是速度快,节省了指针定位的时间开销。