
JVM总结-00_序
1.内存划分
按线程是否共享分成两大类
线程私有
1. 程序计数器
作用:记录当前线程执行到哪一行字节码,行号指示器
特点:唯一不会OOM的区域
2. 虚拟机栈
作用:每个方法执行时,都创建一个栈帧入栈,存局部变量,方法调用信息,执行完毕后出栈
常见异常:StackOverflowError 栈溢出,递归调用过多,方法引用过长,方法过大
- 栈空间也可能OOM,线程开太多,每个线程一个栈,内存超出
3. 本地方法栈
作用:专门给native本地方法用的栈,和虚拟机栈本质一样
同样会爆栈溢出,OOM
线程共享
4. 堆(heap)--GC主战场
作用:存放所有对象实例和数组的地方,JVM最大的一块内存区域 特点:垃圾回收,容易OOM处
分代逻辑:新生代(Eden+survivor * 2,8:1:1)+ 老年代
5. 元空间(metaspace)原永久代
作用:存类的元信息,常量,即时编译后的代码等
特点:原永久代(占用jvm内存),jdk8替换为元空间,直接用本地物理内存
2.垃圾回收(GC)
如何找出哪些部分可回收
引用计数法(不用)
被引用则计数器+1,循环引用时无法回收。
可达性分析算法
定义一个GCRoot,基于GCRoot,顺着引用链,顺藤摸瓜找到全部引用,剩下的就是未引用部分,可回收
GCRoot包含哪些内容
-
虚拟机栈里引用的对象(局部变量)
-
方法区静态变量,常量引用的对象
-
本地方法栈引用的对象
引用类型
强引用
-
new关键字创建的对象,GCRoot没有引用到才回收
-
使用场景:普通new对象
软引用
-
SoftReference<>,内存不足时会GC
-
可配合引用队列释放
-
使用场景:内存敏感的缓存
弱引用
-
WeakReference<>,触发垃圾回收的时候就会GC
-
可配合引用队列释放
-
使用场景:ThreadLocal,临时性缓存
虚引用
-
PhantomReference<>,不影响对象的存活,设置它是为了在GC的时候获得系统通知
-
直接内存就是使用到了虚引用做回收通知,需要Cleaner到引用队列,调用UnSafe类,freeMemory来回收掉
-
必需配合引用队列来使用
-
使用场景:堆外内存回收通知
终结器引用
-
对象继承自Object,且没有关联其他引用,会入到Finalizer守护队列,调用finalize()方法时执行GC,
-
若调用时重新被引用,则不会GC掉,不建议使用
引用队列
// 使用:作为参数放入
ReferenceQueue<T> queue = ReferenceQueue<T>();
SoftReference<T> ref = new SoftReference(T,queue);
* 垃圾回收算法
标记-清除(CMS老年代)
-
标记存活,清除未存活对象
-
清理效率不高,容易产生碎片
标记-整理(Serial Old/Parallel Old 老年代)
-
标记存活,存活对象放到一端,清理另一端
-
内存空间连续,需要移动对象,效率低
复制(新生代)
-
内存划为两块,每次使用其中一块
-
把存活对象复制到另一块,清理原来块
-
本质是空间换效率。
分代回收
-
新生代(复制)
-
老年代(标记-清除/标记-整理)
相关VM参数
| 含义 | 参数 |
|---|---|
| 堆初始大小 | -Xms,默认是本机内存的1/64 |
| 堆最大大小 | -Xmx |
| 新生代大小 | -Xmn |
| 幸存区比例(动态) | -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy |
| 幸存区比例 | -XX:SurvivorRatio=ratio |
| 晋升阈值(多少次进入老年代) | -XX:MaxTenuringThreshold=threshold |
| 晋升详情 | -XX:+PrintTenuringDistribuiton |
| GC详情 | -XX:+PrintGCDetails -verbose:gc |
| FullGC前MinorGC | -XX:+ScavengeBeforeFullGC |
垃圾回收器
-
根节点枚举(GCRoot)
-
安全点(在执行gc时防止引用改变,跑到这个点才能执行STW(stop the world),例如方法调用,循环跳转等)
-
安全区域(用户线程,可以主动响应安全标识)
-
记忆集和卡表(记录跨代引用和跨区引用的指针集合,避免有的对象存活在老年代,还需要扫描整个老年代),卡表是记忆集的实现之一
-
写屏障(维护卡表用,避免脏数据)
按使用类型分类
串行
单线程,堆内存较小
- Serial,Serial Old
吞吐量优先
多线程,堆内存较大,尽可能让单次时间内的stw时间最短(stw次数少)
- Parallel GC ParallelOldGC
响应时间优先
多线程,堆内存较大,尽可能让每次stw时间最短(stw次数多,响应快)
- CMS ParNewGC(配合CMS老年代)
G1(jdk9默认,jdk8可开启)
ZGC(jdk11实验,jdk15+正式使用)
3. 类加载机制
java初期一次编写到处运行,先编译成class字节码文件,再让jvm来执行class字节码文件
字节码文件结构
-
魔数:标识文件类型
-
常量池:存放变量/方法,属性,类型,名称
-
访问标志:访问类型,类/接口,public/private,是否final
-
类,父类,接口索引
-
字段表属性
-
方法表属性
-
属性表属性
类加载机制
加载
找到类的二进制字节流,生成class类文件到内存
连接
-
验证
验证文件合法性
-
准备
准备类的静态变量,赋初始默认值(非程序员的赋值),static final 赋初始值,static赋0值,也就是static在类加载时初始化一次(不包括实例变量)
-
解析
类中的符号引用替换为直接引用(具体指针)
初始化【类主动使用时执行】
类加载器< cinit >()方法
执行静态语句块
类中所有赋值操作
初始化时机【就是用到这个类了】:
-
创建实例
-
访问静态变量或赋值
-
调用类静态方法
-
反射
-
初始化某个类的子类
-
jvm启动时标注,java.exec运行某个类
使用
类访问方法区中的数据结构的接口,对象在堆区
卸载
该类所有实例、类加载器、Class 对象全部无 GC Roots 引用时
类加载器
-
启动类加载器
- java.,javax. sun. 等开头,rt.jar下所有核心
-
扩展类加载器
- java.x.等开头,java.ext.dirs下的jar
-
应用程序类加载器
- 不自定义则是默认的类加载器
-
自定义类加载器
- 用户自定义类加载器,可定制
class.forName()和ClassLoader.loadClass()对比
| class.forName() | ClassLoader.loadClass() |
|---|---|
| 加载到jvm中,解析并初始化 | 加载到jvm,只有加载 连接[ 验证 准备],不执行[解析],初始化,newInstance()才执行初始化 |
| name,initialize,loader 带参数,可控制是否加载static块 |
机制:
全盘负责,负责该类下所有依赖和引用的
双亲委派/父类委托,没有的类先让父类尝试加载,父类加载不了才让子类加载
缓存机制,缓存已加载过的类
jdk9引入模块化
JMM内存模型和并发问题
java线程《-》工作内存《-》内存交互《-》主存
lock/unlock,锁定/解锁
read/load,读取/加载
use/assign,使用/赋值
store/write,存储/写入
volatile
-
保证变量在所有线程的可见性,值在修改后立马同步给主内存
-
读写前后加内存屏障,防止指令重排序,强制顺序执行
多线程的运算操作非原子性,仍然会有并发问题
指令重排
-
分配内存空间
-
初始化对象
-
对象引用指向内存空间的地址
2,3可能乱序,导致多线程拿到null
并发三要素
可见性
修改在其他线程立即可见
原子性
访问和读写操作原子性
有序性
执行顺序有序
线程安全
-
不可变
-
绝对线程安全
-
相对线程安全(大部分场景)
-
线程兼容(普通ArrayList通过加synchronzied)
-
线程对立(线程是否同步都无法线程安全)
实现线程安全
-
互斥同步(synchronzied,reentrant lock)我用的时候其他人卡着
-
非阻塞同步(CAS,compareandswap,native方法,比较交换,底层封装cpu硬件指令,原子性)
- Automic包,UnSafe下的CAS
ABA问题,A改成B再改成A,CAS检测不到,解决方案:AtomicStampedReference(引用类型)AtomicMarkableReference /版本号自增(基础类型)
-
无同步方案(栈封闭,本地线程,不共享)
其实仍然是锁相关,乐观悲观,或者干脆线程私有