JDK:Java程序设计语言、Java虚拟机、Java类库
JRE:Java程序运行的标准环境
HotSpot:Sun/OracleJDK和OpenJDK中默认的Java虚拟机
目录标题
一、运行时数据区域
1.1 线程私有区
- 程序计数器
- 当前线程所执行的字节码的行号指示器
- 通过改变这个计数器的值来选取下一条需要执行的字节码指令
- 每条线程都有一个独立的程序计数器,互不影响、独立存储,所以线程私有
- 唯一一个没有规定任何OOM情况的区域
- 虚拟机栈
- 生命周期与线程相同
- 每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧,用于存 储局部变量表、操作数栈、动态连接、方法出口等信息
- 局部变量表放的是编译器可知的各种基本数据类型、对象引用类型
- 局部变量表存储空间以局部变量槽来表示,64位的Long和Double类型占两个变量槽
- 每个方法都被调用,直到执行完毕的过程,对应着一个栈帧在虚拟机栈中从入栈到出栈道过程
- 异常
- 线程申请的栈深度大于虚拟机所允许的深度:StackOverflowError异常
- 允许动态扩展下,扩展到无法再扩展:OutOfMemoryError异常
- 本地方法栈
- 和虚拟机栈非常相似,虚拟机栈为虚拟机执行Java方法服务,本地方法栈是为虚拟机使用到本地(Native)方法服务
- HotSpot虚拟机直接就把本地方法栈和虚拟机栈合二为一
1.2 线程共享区
- 堆
- 虚拟机启动时创建,用于存放对象实例,所有的对象实例以及数组都应当在堆上分配
- Java堆是垃圾收集器管理的内存区域
- Java堆可以实现固定大小,也可以扩展,主流的Java虚拟机都是按照可扩展来实现(参数-Xmx和-Xms设定)
- 方法区
- 存储已被虚拟机加载的类型信息、常量、静态变量、即使编译器编译后的代码缓存等数据
- 运行时常量池
- Class文件中有类的版本、字段、方法、接口等描述信息,还有一项常量池表
- 用于存放编译器生成的各种字面量与符号引用,在类加载后存放到方法区的运行时常量池
- 具备动态性
- 常用String.intern()方法放入运行时常量池
1.3 直接内存
- 就是Java堆外内存,直接内存不在Java堆内,Java堆内存往外写需要拷贝到native堆
- 虚拟内存
- 内存用完了,划分一部分硬盘来充当内存
二、HotSpot虚拟机对象探秘
2.1 对象的创建
- 虚拟机遇到new指令时,首先会检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化,如果没有,就必须执行类加载过程
- 检查通过后,为新生对象分配内存,把一块确定大小的内存块从Java堆中划分出来
- 指针碰撞:被使用过的内存和空闲的内存分别放在两边,中间放指针作为分界点,分配内存就是把指针向空闲方向挪动一段距离
- 空闲列表:已使用的和空闲的交错在一起,就需要维护空闲列表,分配的时候从列表找出一块足够大的空间划分给对象
- 并发情况下
- 采用CAS配上失败重试保证更新操作原子性
- 本地线程分配缓存:每个线程都在Java堆中预先分配一小块内存
- 接着执行()方法,就是构造方法,按照程序员意愿对对象进行初始化
2.2 对象内存布局
- 对象头
- 用于存储对象自身的运行时数据
- 如:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
- 用于存储对象自身的运行时数据
- 类型指针
- Java虚拟机通过这个指针来确定该对象是那个类的实例
- 如果对象是个Java数组,对象头还必须有一块用于记录数组长度的数据
- Java虚拟机通过这个指针来确定该对象是那个类的实例
- 实例数据
- 定义的各种类型的字段内容,不管是从父类继承下来,还是子类中定义的字段都必须记录起来
- 相同宽度的字段总是被分配到一起存放
- 定义的各种类型的字段内容,不管是从父类继承下来,还是子类中定义的字段都必须记录起来
- 对齐填充
- 对象起始地址必须是8字节的整数倍
2.3 对象的访问定位
- Java程序通过栈上的reference数据来操作堆上的具体对象
- 访问方式
- 句柄访问
- 在Java堆中划分出一块内存用来做句柄池,reference存储的就是对象句柄地址
- 直接指针访问
- reference存储的直接就是对象地址
- 句柄访问
- 各自优势:句柄访问在对象移动时不需要改变reference本身,直接指针访问速度快、节省一次指针定位的时间开销
- HotSpot主要使用直接指针访问,从整体软件开发来看,句柄访问也很常见
三、OOM 异常
3.1 Java堆溢出
- 堆的最小值:-Xms
- 堆的最大值:-Xmx
- 参数设置一样可以避免自动扩展
- 内存泄漏:已动态分配的堆内存由于某种原因程序未释放或无法释放,造- 成系统内存的浪费,导致程序减慢、系统崩溃
- 具体情况
- 静态集合类:长生命周期的对象持有短生命周期对象的引用,导致不能被回收
- 各种连接:数据库连接没有close,网络连接和IO连接等
- 变量不合理作用域:变量作用域大于使用范围、没有及时设置为null
- 内部类持有外部类:外部类对象返回内部类实例对象,内部类长期被引用,外部累不能被垃圾回收
- 改变哈希值、过期引用、缓存泄漏、监听器和回调
- 避免
- 少使用枚举、尽量使用静态内部类而不是内部类、尽量使用轻量级的数据结构、谨慎使用static关键字、谨慎使用单例模式
- 内存溢出:内存不够,超出规定大小
- 修改JVM启动参数,直接增加虚拟机内存
3.2 虚拟机栈和本地方法栈溢出
- 栈容量使用-Xss参数设定
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,或者新的栈帧内存无法分配的时候,将抛出StackOverflowError异常
- 如果虚拟机的栈内存运行动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出OutOfMemoryError异常
- Windows 单个进程最大内存限制为2GB
3.3 方法区和运行时常量池溢出
- intern()方法会将首次遇到的字符串实例复制到方法区的字符串常量池中存储
- -XX:MaxMetaspaceSize: 设置元空间最大值,默认-1(不限制)
- -XX:MetaspaceSize: 指定元空间的初始空间大小
- -XX:MinMetaspaceFreeRatio: 垃圾收集之后控制最小的元空间剩余容量的百分比
3.4 本机直接内存溢出
- 直接内存的容量大小参数
- -XX:MaxDirectMemorySize
- 不指定默认与Java堆最大值 (-Xmx指定) 一致