# JVM内存分区
# PC寄存器
# 栈
# 栈帧
# 局部变量表
# 操作数栈
# 本地方法栈
# 堆
# 堆空间
# 代码优化策略
# 序列化与深拷贝
# TLAB
# 方法区
# 字符串常量池(全局串池)
JDK1.6中串池存在于方法区(永久代)中,只有当进行fullGC时才进行垃圾回收;JDK1.7中串池脱离方法区,存在于堆中,只要进行minorGC时就进行垃圾回收
类加载解析阶段,在堆中生成字符串对象实例,并将该字符串对象实例的引用值存于串池(JDK1.7后位于堆)中
HotSpot VM中的具体实现为StringTable
(串池map),其中存储驻留字符串引用,在每个HotSpot VM实例只有一份,被所有的类共享
String a = "a";
String b = "b";
String ab1 = "ab";
String ab2 = a + b; // ab2 != ab1
String ab3 = "a" + "b"; // ab3 == ab1
String abi = ab2.intern(); // abi != ab2且abi == ab1 == ab3(JDK1.7)
字面值常量(a、b、ab1)
若在串池中未找到目标字符串对象,则创建对应的字符串对象存储于堆中,将其引用放入串池(整个过程为懒加载)
字符串拼接表达式(ab2、ab3)
- 若表达式中包含变量,创建
StringBuilder
对象,分别拼接表达式变量,最后将toString()
结果(新字符串)保存于堆中,并将其引用赋值给ab2而不放入串池 - 若表达式中均为常量,则在编译期进行字符串拼接,并执行字面值常量初始化流程
- 若表达式中包含变量,创建
intern()
- JDK1.7:尝试将调用该方法的字符串对象引用放入串池,并返回串池中该对象的引用
- 若串池中不存在该对象引用,则返回的对象引用
==
当前对象引用 - 若串池中已存在该对象引用,则无需将当前字符串关联串池,返回的对象引用
!=
当前对象引用
- 若串池中不存在该对象引用,则返回的对象引用
- JDK1.6:尝试将调用该方法的字符串对象的副本引用放入串池,并返回串池中该对象引用,因此返回的对象引用
!=
当前对象引用
- JDK1.7:尝试将调用该方法的字符串对象引用放入串池,并返回串池中该对象的引用
# 串池调优
# class文件常量池
类加载阶段,编译器生成的各种字面量于符号引用存储于class文件常量池(表)中。其中在类加载解析阶段,运行时常量池中符号引用被替换为直接引用,此时可能需要查询串池,以保证串池中的字符串引用值与class文件常量池中的字符串引用值一致
# 运行时常量池
类加载阶段结束后,class文件常量池中的内容转移至运行时常量池中
JDK1.6中运行时常量池存在于**方法区(永久代)中,JDK1.8中运行时常量池存在于元空间(直接内存)**中
# 垃圾回收
# 基本概念
# 根对象与可达性分析
# 根对象:
- 强引用
- 软引用
- 弱引用
- 虚引用
- 终结器引用
# GC算法
标记-清除
- 标记可达对象
- 清除不可达对象
# 缺陷
- STW较长,性能较差
- 碎片化严重
标记-整理
复制
# 缺陷
- 存货对象较多时影响性能(故不适用于老年代)
- 只使用一半空间进行对象填充,内存使用率较低
分代收集(新生代使用复制算法,老年代使用标记-清除/标记-整理算法)
# GC类型
MinorGC
新生代满时触发
以下对象将提前晋升至老年代:
- 长期存活的对象
- Survivor区无法容纳的大对象
- Survivor区可容纳,但复制过程过于消耗性能的大对象
FullGC
老年代满时触发
OOM:某线程发生OOM将清空其占用的堆内存,而不影响其他线程
# 垃圾回收器
# 串行(不推荐使用)
-XX:+UseSerialGC
:Serial New(新生代,复制) + Serial Old(老年代,标记-清除)
# 并行(JDK6、7、8默认)
-XX:+UseParallelGC
/-XX:+UseParallelOldGC
:Parallel Scavenge(新生代+老年代,复制)
吞吐量优先,GC线程对CPU占用高
-XX:GCTimeRatio=GC时间占总吞吐时间百分比
(默认值99%)
-XX:MaxGCPauseMillis=GC最大STW时间
# CMS(并发,JDK9废弃)
-XX:+UseConcMarkSweepGC
:CMS(老年代,标记-清除;ParNew Old为备用)+ -XX:+UseParNewGC
:ParNew New(【并行】新生代,复制)
- 初始标记(STW)
- 并发标记
- 预清理
- 重新标记(STW)
- 并发清理
- 并发重置
响应时间优先,GC线程对CPU占用低
若并发GC失败,则CMS退化为SerialOld
-XX:ParallelGCThreads=并行线程(工作线程)数
-XX:ConcGCThreads=并发线程(GC线程)数
(t2 = t1 / 4)
-XX:CMSInitiatingOccupancyFraction
当老年代浮动垃圾占比达到该值时,开始执行FullGC
-XX:+CMSScavengeBeforeRemark
执行FullGC前先使用ParNewGC对新生代进行清理,减少老年代对新生代的引用,降低重新标记阶段的资源开销
-XX:UseCMSCompactAtFullCollection
+-XX:CMSFullGCsBeforeCompaction
在执行FullGC之后进行老年代内存压缩,降低内存碎片出现频率
# G1(JDK9默认)
-XX:+UseG1GC
:G1(区域间复制 + 标记-整理)
# 回收原理
# 堆内存分区
在进行区域间复制的过程中,同时实现了标记-整理功能,降低内存碎片化概率
- Humongous区(巨型对象区)
# 回收流程
# STW
-XX:MaxGCPauseMillis
默认STW时间为200ms
# Young Collection
# 跨代引用、
Remembered Set
与卡表# Mixed Collection
触发时机控制:
-XX:InitiatingHeapOccupancyPercent
(默认值45%)- 初始标记(STW)
- 根区域扫描
- 并发标记(STW)
- 最终标记(STW)
- 拷贝存活(Evacuation(STW):老年代垃圾进行选择性回收,优先回收巨型对象)
#
pre-write barrier
与satb_mark_queue
# GC性能衡量
# 衡量指标
# 吞吐量
$\Huge{\frac{GC耗时}{应用程序耗时 + GC耗时}}$
# 停顿时间
# GC频率
# 衡量工具
**top
**查看进程的内存使用情况
# jstack
jstack [PID]
查看具体线程的堆栈与状态信息
# jstat
jstat -class [PID]
/jstat -compiler [PID]
查看类加载/类编译的数量
jstat -gc [PID] [打印间隔/ms] [打印次数]
查看堆中各个分区的内存占用情况及GC情况
# jmap
jmap -heap [PID]
查看堆中各个分区的内存占用情况,以及所用垃圾收集器的设置类型
jmap -histo[:live] [PID] | more
查看堆内存中的对象数目、大小统计直方图
jmap -dump:format=b,file=./heap.hprof [PID]
输出堆dump文件结合jhat/MAT进行分析
-XX:HeapDumpOnOutOfMemoryError
在发生 OOM 时输出堆dump文件
# jhat
jhat -port [PORT] ./heap.hprof
# jinfo
jinfo -flags [PID]
# ==调优==
# GC调优
-XX:+PrintGC 输出 GC 日志
-XX:+PrintGCDetails 输出 GC 的详细日志
-XX:+PrintGCTimeStamps 输出 GC 的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出 GC 的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行 GC 的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径
可使用GC Viewer/GC Easy等工具对输出的GC日志文件进行分析,根据分析结果进行内存与GC调优
# 堆
无用对象的回收
-XX:MaxTenuringThreshold
新生代对象晋升年龄阈值(最大值15)
-XX:PretenureSizeThreshold
新生代对象直接晋升大小阈值
# 降低Minor GC频率
通过进一步增加新生代内存以降低Minor GC频率
若新生代对象存活时间普遍较长,则不可贸然增加新生代内存,否则反而会增加Minor GC时间
# 降低Full GC频率
若新生代Surviver区内存紧张,则对象晋升年龄/大小可能降低(
-XX:+UseAdaptiveSizePolicy
),增大老年代Full GC概率故可通过适当增大堆内存空间,提高新生代对象晋升年龄与大小阈值进行缓解
# 方法区
无用常量与类的回收
# 内存调优
-Xms
与-Xmx
建议配置相同的值,使得在GC后无需重新分隔计算堆区的大小-Xmn
新生代内存分配一般为堆内存空间的25%~50%(Sun推荐配置为整个堆内存大小的3/8)- 在JDK1.7中,新生代与老年代的默认内存占比为1:2,可通过
–XX:NewRatio
进行显式配置;年轻代中Eden区与To Survivor区、From Survivor区的比例为8:1:1,可通过-XX:SurvivorRatio
进行显式配置 - 在JDK1.8中,默认开启
-XX:+UseAdaptiveSizePolicy
配置项,将对堆中各个区域的空间大小(不再遵循以上默认值)以及进入老年代的晋升年龄进行动态调整- 若新生代(Eden区)对象创建较多,可考虑将该配置项关闭并显式配置
–XX:NewRatio
及-XX:SurvivorRatio
,固定Eden区空间占用比例
- 若新生代(Eden区)对象创建较多,可考虑将该配置项关闭并显式配置
# 类加载
将类Class文件中的二进制数据读入内存,并放入运行时数据区的方法区内,然后在内存(HotSpot实现为方法区而非堆)中创建对应的Class对象
# 加载
# 连接
# 初始化
为类静态变量赋初值(按照初始化语句的声明顺序执行)
# 类实例化
Java编译器为编译的每一个类都生成一个实例初始化方法(
<init>
方法)(对静态实例生成<clinit>
静态初始化方法)
# 类初始化的时机
# 主动使用
- 启动类
- 创建类的实例或子类实例(子类进行初始化,前提是其所有父类都已经完成初始化过程)(这一结论不适用于接口)
- 访问或修改类的静态成员变量,访问类的静态成员方法(严格区分类静态变量的定义场所,若访问子类继承而来的父类静态变量,则父类被初始化,而子类不会被初始化)
- 通过反射访问类
# 被动使用
创建该类型的数组或集合对象(JVM在运行时动态创建数组类型:前缀带有[L
表示一维数组,带有[[L
表示二维数组)
类加载器在类可能被主动使用之前将会对其进行预先加载,如果字节码文件缺失,则只有当该类被首次主动使用时,类加载器才会报告LinkageError
错误
# 类加载器
# JVM自带的类加载器
- Bootstrap类加载器
- 扩展类加载器
- 系统(应用)类加载器
# 用户自定义的类加载器
java.lang.ClassLoader
的子类
# 双亲委派
# 字节码
# 字节码结构
常量池
方法表集合
# 解释与编译
默认mixed模式
-Xint
-Xcomp
前端编译
运行时编译
# 即时(JIT)编译
# JIT类型与分层编译
- C1
- C2
# JIT触发时机
- 方法调用计数器
- 回边计数器
- 栈上替换(OSR)编译
# JIT常用优化策略
方法内联
JIT是否进行内联优化,与方法体大小及方法调用频率相关
在编程实践中,避免在一个方法中写大量代码,优先考虑将代码分散至多个小方法体中
逃逸分析
- 栈上分配(未实现❓)
- 锁消除
- 标量替换
# AOT编译器
# Graal编译器
# 助记符
#
ldc
将
int
/float
/String
类型的常量值从常量池推送至()栈顶#
bipush
将
short
/char
/int
等单字节(-128~127)常量值从常量池推送至()栈顶#
sipush
将短整型(-32768~32767)常量值从常量池推送至()栈顶
#
iconst_[m1-5]
将[m1-5]从常量池推送至()栈顶
#
anewarray
创建一个引用类型(类、接口、数组)的数组,并压入栈顶