JVM 运行在操作系统上,与硬件没有直接的交互,是可运行 Java 代码的虚拟机
一、内存
- 程序计数器 (线程私有)
- 当前线程所执行的字节码的行号指示器,通过改变这个计数器的值来选取下一条需要执行的字节码指令
- 每条线程都有一个独立的程序计数器,互不影响、独立存储,所以线程私有
- 唯一一个没有规定任何OOM情况的区域
- 虚拟机栈 (线程私有)
- 生命周期与线程相同
- 每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧,用于存储 局部变量表、操作数栈、动态连接、方法出口 等信息
- 局部变量表放的是编译器可知的各种基本数据类型、对象引用类型
- 局部变量表存储空间以局部变量槽来表示,64位的Long和Double类型占两个变量槽
- 每个方法都被调用,直到执行完毕的过程,对应着一个栈帧在虚拟机栈中从入栈到出栈道过程
- 线程申请的栈深度大于虚拟机所允许的深度:StackOverflowError异常
- 允许动态扩展下,扩展到无法再扩展:OutOfMemoryError异常
- 本地方法栈(线程私有)
- 和虚拟机栈非常相似,虚拟机栈为虚拟机执行Java方法服务,本地方法栈是为虚拟机使用到本地(Native)方法服务
- HotSpot虚拟机直接就把本地方法栈和虚拟机栈合二为一
- 堆(线程共享)
- 虚拟机启动时创建,用于存放对象实例,所有的对象实例以及数组都应当在堆上分配 Java 堆是垃圾收集器管理的内存区域
- Java堆可以实现固定大小,也可以扩展,主流的Java虚拟机都是按照可扩展来实现(参数-Xmx和-Xms设定)
- 方法区(线程共享)
- 存储已被虚拟机加载的类型信息、常量、静态变量、即使编译器编译后的代码缓存等数据
- 运行时常量池
- Class文件中有类的版本、字段、方法、接口等描述信息,还有一项常量池表
- 用于存放编译器生成的各种字面量与符号引用,在类加载后存放到方法区的运行时常量池
- 具备动态性
- 常用String.intern()方法放入运行时常量池
二、垃圾回收
- Java 堆从 GC 角度还可以细分为 新生代(Eden、From Servivor、To Servivor) 和老年代
- 新生代:存放新 new 的对象,一般占据 1/3 堆内存,频繁创建对象就会频繁触发 MinorGC 进行垃圾回收
- Eden:新对象出生地(如果很大的话,会直接分配到老年代),当 Eden 区内存不够的时候就会触发 MinorGC
- From Survivor:上一次 GC 幸存者
- To Survivor:保留了一次 MinorGC 过程中的幸存者
- 老年代:主要存放应用程序中生命周期长的内存对象
- 空间不够会触发 MajorGC,采用标记清除算法,老年代都不够的时候就会抛出 OOM 异常
- 新生代:存放新 new 的对象,一般占据 1/3 堆内存,频繁创建对象就会频繁触发 MinorGC 进行垃圾回收
- MinorGC:采用复制算法(复制-清空-互换)
- Eden、From Servivor 复制到 To Servivor,年龄 +1
- 把前两区存活的对象复制到后者,如果有达到老年标准的或后者不够位置了,对象就会到老年代去,同时对象年龄 +1
- 清空 Eden、From Servivor
- To Servivor 和 From Servivor 互换
- 原 To Servivor 成为下一次 GC 时的 From Servivor 区
- Eden、From Servivor 复制到 To Servivor,年龄 +1
- Major GC:采用标记清除算法
- 扫描老年代,标记出存活的对象,然后回收没有标记的对象
- Major GC 耗时比较长,因为要扫描再回收,还会产生内存碎片
- 永久代
- 主要存放 Class 和 Meta 信息,Class 被加载的时候被放入永久区域,不会被 GC 清理,会随着加载的 Class 增多而膨胀,最后抛出 OOM
- Java 8 与元数据
- Java 8 中永久代已经被移除,元数据区取代了该功能,区别在于元空间不在虚拟机中,而是本地内存,由系统实际可用空间控制
三、并发锁机制
- 锁
- 乐观锁:CAS
- 悲观锁:synchronized、vector、hashtable
- 自旋锁:CAS
- 可重入锁:synchronized、ReentrantLock、Lock
- 读写锁:ReentrantReadWriteLock、CopyOnWriteArrayList、CopyOnWriteArraySet
- 公平锁:ReentrantLock(true)
- 非公平锁:synchronized、ReentrantLock(false)
- 共享锁:ReentrantReadWriteLock 中读锁
- 独占锁:synchronized、vector、hashtable、ReentrantReadWriteLock 中写锁
- 重量级锁:synchronized
- 轻量级锁:锁优化技术
- 偏向锁:锁优化技术
- 分段锁:concurrentHashMap
- 互斥锁:synchronized
- 同步锁:synchronized
- 死锁:相互请求对方资源
- 锁粗化:锁优化技术
- 锁消除:锁优化技术
- CAS 机制
- 说明:无锁、自旋锁、乐观锁、轻量级锁,compareAndSet、compareAndSwap
- 问题
- 原子性问题:lock comxchgq 缓存行锁/总线锁
- ABA 问题:可以添加一个版本号解决 AtomicStampedReference
- 轻量级锁一定比重量级锁性能高吗?
- 锁机制
- 说明:互斥锁、悲观锁、同步锁、重量级锁
- 线程阻塞、上下文切换、操作系统线程调度
- 说明:互斥锁、悲观锁、同步锁、重量级锁
- static 下 sync 锁的是 class 对象,非 static 锁的是实例
- 对象 OOP 组成
- 对象头:MarkWord(Epoch、TreadID、age、偏向状态 0或1、锁状态标志)、MetaData、数组长度(数组才有)
- 实例数据
- 对齐填充位
- 锁膨胀升级过程
- 无状态
- 默认延时 4s 开启偏向锁,可以通过 -XX:BiasedLockingStartupDelay=0 取消,如果不要偏向锁 -XX:-UserBiasedLocking = false
- 偏向锁启用一个线程加锁,将线程 id 写入对象头 MarkWord,如果未启用直接升级轻量级锁
- 偏向锁
- 多个线程加锁升级为 CAS 轻度竞争
- 调用 wait 方法 升级重量级锁(这个是重量级锁 monitor 锁才有的实现)
- 轻量级锁
- CAS 自旋不成功/锁膨胀 升级为重度竞争 自适应自旋(自旋 N 次之后拿不到锁就升级)
- 重量级锁
- 无状态
- LongAdder 分段 CAS 优化机制
- 多个线程对同一个变量进行操作的时候,使用其他同类型变量进行接收,最后统一处理