1.JVM概述
虚拟机:VM, Virtual Machine
- 逻辑上,一台虚拟的计算机
- 实际上,一个软件,能够执行一系列虚拟的计算指令
- 系统虚拟机
- 对物理计算机的仿真
- 如VMWare,Oracle VirtualBox等
- 软件虚拟机
- 专门为单个计算程序而设计
- 如JVM等
2.JVM内存分类
- 线程私有内存
- 程序计数器(Program Counter Register)
- Java虚拟机栈(JVM Stack)
- 本地方法栈(Native Method Stack)
- 多线程共享内存
- 堆(Heap)
- 方法区(Method Area)
- 运行时常量池
程序计数器
- Program Counter Register, 一块小内存,每个线程都有
- PC存储当前方法
- 线程正在执行的方法称为该线程的当前方法
- 当前方法为本地(native)方法时, pc值未定义(undefined)
- 方法为非本地方法时,pc包含了当前正在执行指令的地址
- 不会引发OutOfMemoryError异常
JVM 栈(JVM Stack,Java 栈)
- 线程有自己独立的Java虚拟机栈,线程私有
- -Xss设置每个线程堆栈大小
- Java方法的执行基于栈
- 每个方法从调用到完成对应一个栈帧在栈中入栈、出栈的过程
- 栈帧存储局部变量表、操作数栈等
- 局部变量表存放方法中存在“栈”里面的东西
- 每个方法从调用到完成对应一个栈帧在栈中入栈、出栈的过程
- 引发的异常
- 栈的深度超过虚拟机规定深度,StackOverflowError异常
- 无法扩展内存,OutOfMemoryError异常
本地方法栈(Native Method Stacks)
- 存储native方法的执行信息,线程私有
- VM规范没有对本地方法栈做明显规定
- 引发的异常
- 栈的深度超过虚拟机规定深度,StackOverflowError异常
- 无法扩展内存,OutOfMemoryError异常
堆(Heap)
- 虚拟机启动时创建,所有线程共享,占地最大
- 对象实例和数组都是在堆上分配内存
- 垃圾回收的主要区域
- 设置大小
- -Xms 初始堆值,-Xmx最大堆值
- 引发的异常
- 无法满足内存分配要求,OutOfMemoryError异常
方法区(Method Area)
- 存储JVM已经加载类的结构,所有线程共享
- 运行时常量池、类信息、常量、静态变量等
- JVM启动时创建,逻辑上属于堆(Heap)的一部分
- 很少做垃圾回收
- 引发的异常
- 无法满足内存分配要求, OutOfMemoryError异常
运行时常量池(Run-Time Constant Pool)
- Class文件中常量池的运行时表示
- 属于方法区的一部分
- 动态性
- Java语言并不要求常量一定只有在编译期产生
- 比如String.intern方法
- 引发的异常
- 无法满足内存分配要求,OutOfMemoryError异常
名称 | 线程私有/共享 | 功能 | 大小 | 异常 |
---|---|---|---|---|
程序计数器 | 私有 | 保存当前线程执行方法 | 通常固定大小 | 不会 |
JVM栈 | 私有 | 方法的栈帧 | -Xss StackOverflowError | OutOfMemoryError |
本地方法栈 | 私有 | 存储native方法信息 | 通常固定大小 | StackOverflowErro、OutOfMemoryError |
堆 | 共享 | 存储对象和数组 | -Xms | 初始堆值 |
方法区 | 共享 | 存储类结构/常量/静态变量 | -XX参数设置 | OutOfMemoryError |
运行时常量池 | 共享 | 常量池运行时表示 | 从属于方法区 | OutOfMemoryError |
3.JVM内存参数
- 堆(Heap)
- 共享,内存大户,存储所有的对象和数组
- -Xms 初始堆值,-Xmx最大堆值
- JVM 栈(JVM Stack)
- 线程私有,存储类中每个方法的内容
- -Xss 最大栈值
- 方法区 (Method Area)
- 存储类信息、常量池等
- 1.7及以前,永久区(Perm),-XX:PermSize, -XX:MaxPermSize
- 1.8及以后,元数据区,-XX:MetaspaceSize, -XX:MaxMetaspaceSize
4.Java对象引用
Java对象的生命周期
- 对象通过构造函数创建,但是没有析构函数回收内存
- 对象存活在离它最近的一对大括号中
内存回收API
Object的finalize方法,垃圾收集器在回收对象时调用,有且仅被调
用一次System的gc方法,运行垃圾收集器
基于对象引用判定无用对象
- 零引用,互引用等
对象引用链
- 通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的
GC Roots对象包括
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中引用的对象
强引用
- 例如Object obj = new Object(); Object obj2 = obj;
- 只要强引用还存在,对象就不会被回收,哪怕发生OOM异常
软引用
- 描述有用但并非必需的对象
- 在系统将要发生内存溢出异常之前,会把这些对象列为可回收
- JDK提供了SoftReference类来实现软引用
1
SoftReference<StringBuilder> s2 = new SoftReference<StringBuilder>(s1);
弱引用
- 描述非必需对象,比软引用强度更弱些
- 被弱引用关联的对象只能生存到下一次垃圾收集发生之前
- JDK提供了WeakReference类来实现弱引用
1
SoftReference<StringBuilder> s2 = new SoftReference<StringBuilder>(s1);
虚引用
- 最弱的引用关系,JDK提供PhantomReference实现虚引用
- 为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知,用于对象回收跟踪
1
PhantomReference<StringBuilder> s2 = new PhantomReference<StringBuilder>(s1);
引用类型 | 强引用 | 软引用 | 弱引用 | 虚引用 |
---|---|---|---|---|
类型 | 正常赋值 | SoftReference | WeakReference | PhantomReference |
回收时间 | 不回收 | 内存紧张时回收 | GC就回收 | 随时可能被回收 |
5.垃圾收集算法
引用计数法
- 一种古老的算法
- 每个对象都有一个引用计数器
- 有引用,计数器加一,当引用失效,计数器减一
- 计数器为0的对象,将被回收
- 优点
- 简单,效率高
- 缺点
- 无法识别对象之间相互循环引用
标记-清除
- 标记阶段:标记出所有需要回收的对象
- 回收阶段:统一回收所有被标记的对象
- 优点
- 简单
- 缺点
- 效率不高
- 内存碎片
复制算法
- 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块
- 当这一块的内存用完了,就将还存活着的对象复制到另外一块上面
- 然后再把已使用过的内存空间一次清理掉
- 优点
- 简单、高效
- 缺点
- 可用内存减少
- 对象存活率高时复制操作较多
标记-整理
- 标记阶段:与“标记-清除”算法一样
- 整理阶段:让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
- 优点
- 避免碎片产生
- 无需两块相同内存
- 缺点
- 计算代价大,标记清除+碎片整理s –更新引用地址
分代收集
- Java对象的生命周期不同,有长有短
- 根据对象存活周期,将内存划分新生代和老年代
- 新生代(Young Generation)
- 主要存放短暂生命周期的对象
- 新创建的对象都先放入新生代,大部分新建对象在第一次gc时被回收
- 老年代(Tenured Generation)
- 一个对象经过几次gc仍存活,则放入老年代
- 这些对象可以活很长时间,或者伴随程序一生,需要常驻内存的,可以减少回收次数
分代收集
- 针对各个年代特点采用合适的收集算法
- 新生代 复制算法
- 老年代 标记清除或标记整理
6.JVM堆内存参数设置
- -Xms初始堆大小
- -Xmx最大堆大小
- -Xmn新生代大小
- -XX:SurvivorRatio设置eden区/from(to)的比例
- -XX:NewRatio设置老年代/新生代比例
- -XX:+PrintGC/-XX:+PrintGCDetails 打印GC的过程信息
HotSpot现有垃圾收集器(JDK 13)
- 串行收集器(Serial Collector)
- 并行收集器(Parallel Collector)
- CMS收集器(Concurrent Mark Sweep Collector)
- G1收集器(Garbage-First Collector)
- Z收集器(Z Garbage Collector)