《深入理解java虚拟机》读书笔记(一)java内存区域

1 篇文章 0 订阅

1.Java虚拟机运行时数据区

Java虚拟机运行时数据区

1.1 程序计数器

当前线程所执行的字节码的行号指示器,它的值由字节码解释器指定,决定了下一条需要执行的字节码指令,控制着程序的分支、循环、跳转、异常处理、线程恢复等基础功能。

    程序计数器线程私有,当执行java方法时,计数器记录了正在执行的虚拟机字节码指令的地址;当执行本地(Native)方法1,计数器的值为空(Undefined)。

1.2 Java虚拟机栈

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

  • 局部变量表:存放编译期可知的各种java虚拟机基本数据类型、对象引用(直接引用是指向对象起始地址的指针,间接引用则是指向代表对象的句柄或相关位置)和returnAddress类型
  • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度(方法嵌套调用太多,如递归,导致线程内栈帧总大小超过java最大运行内存)
  • OutOfMemoryError:Java堆中没有足够内存完成实例分配,或者虚拟机栈容量无法扩展

1.3 本地方法栈

虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机使用的本地方法服务。

1.4 Java堆

在虚拟机启动时创建,被所有线程共享,几乎所有的对象实例都在堆中分配内存。

  • GC堆:Java堆是垃圾收集器管理的内存区域
  • 空间连续:Java堆可以处在物理上不连续的内存空间中,不要求文件连续存放,但对于大对象很可能要求连续的内存空间
  • 空间大小:当前主流的Java虚拟机都是按照可扩展实现的(-Xmx和-Xms)

1.5 方法区

线程共享,用于存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

  • OutOfMemoryError:Java方法区中没有足够内存完成实例分配,且虚拟机栈容量无法扩展(-XX:MaxMetaspaceSize)

  • 方法区GC:常量池中废弃的常量和不再使用的类型,回收废弃常量与回收Java堆中的对象非常类似,只要常量池中的常量没有被任何地方引用,就可以被回收;判定一个类型是否属于"不再被使用的类"的条件就比较苛刻了。需要同时满足下面三个条件:1)该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例;2)加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGI、JSP的重加载等,否则通常是很难达成的;3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

  • 运行时常量池:存放编译期生成的各种字面量与符号引用

  • 方法区和metaspace:
        对于HotSpot虚拟机,在JDK1.8之前,方法区的实现是“永久代”(Permanent Generation)。永久代与Java堆的关系非常密切,他们之间在内存上是连续的。虚拟机把GC分代收集扩展至永久代,将永久代和老年代的垃圾收集器进行了捆绑,在永久代上也使用老年代的垃圾回收算法。这样一来,HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作。使用永久代来实现方法区并不是一个好主意,因为这样更容易遇到内存溢出的问题(永久代有-XX:MaxPermSize的上限)。
        到了JDK1.8,HotSpot虚拟机完全移除了永久代,Metaspace取而代之,即Metaspace是方法区新的实现。

1.6 直接内存

Java堆外直接向系统申请的内存空间,不受Java堆大小的限制,受本机总内存(物理内存、SWAP分区或分页文件)大小及处理器寻址空间的限制。当Java虚拟机配置的内存超过物理内存限制时,可能会导致动态扩展时出现OutOfMemoryError异常。

2. HotSpot虚拟机对象

2.1 对象的创建

Jvm中对象创建流程

2.2 对象的内存布局

    在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
(1) 对象头

  • Mark Word
    存储对象自身的运行时数据如哈希码,GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,长度对应32位虚拟机的32byte和64位虚拟机的64byte。
存储内容标志位状态
对象哈希码、年代年龄01未锁定
指向锁记录的指针00轻量级锁定
指向重量级锁的指针10膨胀(重量级锁定)
空,不需要记录信息11GC标记
偏向线程ID、偏向时间戳、对象分代年龄01可偏向
  • 类型指针
    对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定该对象属于哪个类的实例。(Java数组的对象头中还必须有一块用于记录数组长度的数据)

(2)实例数据
父类和子类中的各类型字段内容,按照一定的顺序进行存储(-XX:FieldsAllocationStyle),父类数据通常在子类之前(-XX:CompactFields)。
(3)对齐填充
占位符,将对象大小对齐为8字节的整数倍,保证对象起始地址是8字节的整数倍。

2.3 对象的访问定位

引用数据访问具体对象的方式由虚拟机实现决定,主流方式有以下两种:
(1)句柄访问
Java堆中划出一片内存作为句柄池,引用数据中存储的即是对象的句柄地址,包含了对象实例数据与类型数据各自具体的地址信息。优点是当对象被移动时,引用数据本身不需要被修改。
(2)直接指针访问
访问类型数据相关信息的指针必须包含在对象的内存布局中,引用数据中存储的就直接是对象地址,节省了一次间接访问的开销。是HotSpot的主要访问方式。

2.4 OutOfMemoryError异常分析

(1)Java堆溢出
Java heap space —> Dump内存堆转储快照(-XX:+HeapDumpOnOutOfMemory -XX:HeapDumpPath=${目录}) —> Eclipse Memory Analyzer分析判断是内存泄露还是内存溢出,如果是内存泄露则需根据泄露对象的类型信息定位到对象创建的具体位置;如果是内存溢出,则通过增加虚拟机内存上限,对象优化等进行调整。
(2)虚拟机栈和本地方法栈溢出

  • 线程请求的栈深度大于虚拟机所允许的最大深度(StackOverflowError)
  • 扩展栈容量无法申请到足够的内存(OutOfMemoryError)

(3)运行时常量池和方法区溢出
(4)本机直接内存溢出


  1. 本地方法:java方法中声明的可调用的使用C/C++实现的方法,在java程序中以native关键字声明,不提供函数体,参考JNI的使用方法。 ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值