JVM快速阅读笔记
JVM
这篇笔记写给自己在实习之前看的,有什么不对地方请广而告知,我会持续更新修改。
本片文章字数较多,适合快速浏览,有不详细的请另行百度
运行时数据区域
- 程序计数器: 指示执行某条指令
- 虚拟机栈(栈): 每个栈帧对应一个方法包括局部变量表、操作数栈、运行时的常量池、
- 本地方法栈:为使用到的本地操作系统方法服务
- java堆:线程共享的一块内存区域,对象实例和数组都在这里分配内存
- 方法区(hotspot包含运行时常量池):已经加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- *直接内存
Object o = new Object();
-
对象创建过程:
new->检查这个指令的参数是否能在常量池中定位到一个类的符号引用->检查这个符号引用代表的类是否已被加载、解析和初始化->若无则类加载,有则进行分配内存(指针碰撞/空闲列表)-> 方法执行初始化字段。
- 对象的内存布局:对象头、实例数据、对齐填充
- 对象访问
- 句柄池:两个指针,对象数据地址、对象类型地址。优点:当对象被移动时(垃圾回收时),只改变句柄中的指针,实例不变。
- 直接指针:实例存储了类型数据的地址;优点:速度快,少一次指针定位的时间;
新生代和老生代
- 新生代GC(Minor):对象大多都朝生熄灭
- 老生代GC(Major):老年代经常伴随一次MinorGC,比Minor慢10倍
OOM (OutOfMemoryError) & SOFE(StackOverFlowError)
- 内存泄漏:分配出去的内存没有回收,失去了对该区域的控制,因而造成了资源浪费,虽然有GC,但是还是会有泄漏。
- 内存溢出:程序所需要的内存超出了系统所能分配的内存的上限;
GC
-
判断对象是否可回收
-
引用技术算法为什么不好?(对象之间循环引用问题):
若被引用一次,计数器+1,引用失效-1,为0就回收。
-
跟搜索算法(GC Roots)
GCRoots可以是:虚拟机栈的引用对象、方法去的类静态属性引用的对象、方法区常量引用的对象、本地方发栈中JNI引用的对象;
判断对象到GCRoots是否可达,不可达则回收;
强引用:直接通过new来创建的对象
- 软引用:SoftReference创建的,内存不足(溢出)则回收
- 弱引用:WeakReference创建的,下一次垃圾收集就会回收
- 虚引用:PhatomReference创建的,总会回收,主要用来当回收时获取一下系统通知;
-
-
finalize()方法与GC二次标记
第一次标记筛选:没有必要执行finalize()方法:没有覆盖finalize()/已经被调用过,直接回收
自救:对象覆盖finalize(),把自己赋值给某个类变量或对象成员变量;第二次标记时他会被移除
-
回收方法区(永生代回收1.废弃常量2.无用的类)
-
无用的类:
该类所有实例已经被回收,java堆中不存在该类的实例加载该类的ClassLoader被回收了
对应的Class对象没有任何地方引用,无法反射访问该类
在大量使用反射、动态代理、CGLib等框架的场景,需要具备类卸载的功能,保证永久代不会溢出。
-
-
垃圾收集算法
- 标记-清除算法(标记,清理)
- 复制算法(分一半内存,存活的复制到另一块内存/新生代、老生代90%)
- 标记-整理算法(标记,存活对象向一端移动,再清理)
- 分代收集算法(新生代用复制,老生代用标记-清理或整理)
垃圾收集器
并行: 多条垃圾收集线程并行工作,用户线程暂停
并发: 用户线程和垃圾收集线程同时执行,垃圾收集线程运行再另一CPU上.
- Serial :单线程收集暂停其他所有线程
- ParNew :多线程收集暂停其他所有线程
- Parallel Scavenge :吞吐量优先,自适应调节策略
- Serial Old:Serial老年代版本,采用‘标记-整理’清理老年代;Parallel Old
-
CMS :以获取最短回收停顿时间为目标,采用‘标记-清除’
初始标记、并发标记、重新标记、并发清理、重置线程
缺点:采用标记-清除,
-
G1 :采用‘标记-整理’,优先回收垃圾最多的区域(已分成大小固定的块),局部看是基于复制算法实现两个Region之间
初始标记-并发标记-最终标记-筛选回收
初始标记和最终标记的区别:初始标记是单线程,最终标记是并行的
内存分配和回收策略
- 对象优先再Eden分配
- 大对象直接进入老年代:
大量连续内存空间的java对象 - 长期存活的对象将进入老年代:
每个对象都有年龄计数器,每熬过一次MinorGC兵器Survivor空间能够容纳,则年龄+1,默认超过15岁就晋升 - 动态对象年龄判定:
Survivor空间中相同年龄的所有对象大小大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代; - 空间分配担保
在发生MinorGC前,检测之前每次晋升为老生代的平均大小是否大于老年代的剩余空间,大于则进行FullGC,否则查看HandlPromotionFailure设置是否允许担保失败,允许则还是MinorGC,不允许则FullGC;
虚拟机执行子
- Class文件结构:8个字节数据流、
- 魔数:头4个字节,确定是否能被虚拟机接受
- Class版本号:魔数后的4个字节,前两个是次版本,后面是主版本
- 常量池:开头u2数据类型,从1开始计数;主要放字面量(类和接口全限定名、字段名称描述符和方法名称描述符)和符号引用;省略很多..
- 访问标志:识别类和接口的访问信息
- 类索引、父类索引和接口索引:类和父类都是一个u2类型,接口是一组u2;用来确定类的继承关系
- 字段表集合:描述接口或类中声明的变量
- 方法表集合
- 属性表集合:Code;Exceptions;Line Number Table;Local Variable Table;Source File;Constant Value;InnerClasses;Deprecated和Synthetic;
-
类加载机制
加载-验证-准备-解析-初始化-使用-卸载
连接:验证-准备-解析
-
过程
-
四种情况必须立即进行“初始化”(加载、验证、准备先开始):
- 遇到new、getstatic、putstatic或invoke static字节码指令时
- 反射调用时
- 初始化一个类,父类没初始化时
- 需要制定一个要执行的主类
-
加载:1.通过全限定名获取定义此类的二进制字节流 2.将这个字节流代表的静态存储结构转化为方法区的运行时数据结构 3. 生成代表该类的Class对象
全限定名:包名.类名
验证:文件格式、元数据、字节码(最复杂,保证安全)、符号引用;此阶段可省略
- 准备:为类变量(static修饰的)分配内存并设置变量初始值0
-
解析:将常量池内的符号引用替换为直接引用;
(符号引用就是一组符号来描述所引用的目标)直接引用就是指针、相对偏移量、一个能简介定位到目标的句柄;
-
初始化:类加载的最后一步,开始执行类中定义的Java程序代码
()方法:所有类变量的赋值动作和静态语句块的语句合并产生。必须回调用父类的();如果没有静态语句块,也没有对变量的赋值操作,那么可以没有()方法;接口不需要执行父类的(),除非父类定义的变量使用时;虚拟机回保证一个类的()方法在多线程同步;
使用
- 卸载
加载和连接阶段可交叉运行,加载未完成,连接可开始,
-
-
类加载器
- 双亲委派模型:启动类<-扩展类<-应用程序<-自定义类
-
方法重载和方法重写
- (重载)静态分派:静态类型变化,实际类型变化
- (重写)动态分派:多态性的表现
编译优化技术
- 公共子表达式消除
- 数组边界检查消除
- 方法内联
-
逃逸分析(主要):分析对象动态作用域
方法逃逸:当一个对象在方法中定义后,可能被外部方法引用,例如作为参数传递到其他方法中;
线程逃逸:赋值给类变量或可以在其他线程中访问的实例变量。
内存模型
作用:用来屏蔽掉各种硬件和操作系统的内存访问差异,实现java跨平台访问内存;
-
模型
- 主内存:存储所有变量;对应Java堆中对象实例数据部分
- 工作内存:每条线程都有自己的工作内存,保存变量的主内存副本拷贝;对应虚拟机栈中部分区域
-
volatile
- 可见性
- 禁止指令重排序
-
原子性、可见性、有序性
- 原子性:除了lock\unlock之外的6种保证原子性,
- 可见性:一个线程修改了共享变量的值,其他线程立即知道(Volatile、Synchronize、final)
- 有序性:本线程观察是有序的,观察另一个线程时无序的
线程
- 内核线程
- 用户线程
用户线程+轻量级进程混合
-
线程调度
- 协同式:线程完成后切换到其他线程
- 抢占式
-
状态转换
- 新建
- 运行
- 无期限等待
-
线程安全和锁优化
-
线程安全
- 不可变:使用final
- 绝对线程安全
- 相对线程安全:通常而言,一般不需要额外做保障措施
- 线程兼容:本身不是线程安全,但可以正确使用同步手段
线程对立:不管怎么采取同步措施,都无法并发使用
-
实现方法
-
互斥同步:使用SynChronize,在同步区前后生成monitorenter和monitorexit字节码指令;
- 在执行monitorenter时,首先尝试获取对象的锁,如果没锁,或者线程已经拥有该对象的锁,所得计数器+1,相反,在执行monitorexit时,就-1,当计数器为0,锁就释放了;
- 如果获取对象锁失败了,当前线程就会阻塞等待,直到另一个线程释放
也可以使用(重入锁)ReentrantLock来实现
- 非阻塞同步
- 无同步方案
- 可重入代码
- 线程本地存储
-
-
锁优化
- 自旋锁/自适应自旋
- 锁消除:消除不存在共享数据竞争的锁
- 锁粗化:扩大锁的范围
- 轻量级锁:无竞争时使用CAS消除同步使用的互斥量
- 偏向锁:无竞争的情况下把同步都消除掉,不做CAS
-