JVM是java文件通过javac后编译,再经过classloader将编译后的文件以及java类库加载进内存再通过字节码解释器,即时编译器(热代码会直接编译)执行引擎交给操作系统。JVM是跨语言的平台,有100多种语言可以直接运行在JVM上,JVM是一种规范,虚拟出来的计算机,class文件包括版本号,常量池等信息。
类的初始化(加载),分为三步loading、linking、initializing(静态变量赋值为初始值),其中linking包括verification、preparation(静态变量赋默认值)、resolution等过程,类加载器有bootstrap加载核心类(底层c++实现、空值)extension加载扩展jar包,app加载classpath的内容,另外还可以自己定义类加载器,采用双亲委派机制,自底向上检查是否加载,如果加载了返回,若没有则再自上向下委派加载(因为安全)父加载器不是加载器的加载器如app的父加载器是ext,app的加载器是null(C++实现的加载)
自定义类加载器就继承classloader,然后重写findclass方法,java是懒加载
tomcat打破了双亲委派机制,每个web app可以加载同名的class(热启动、热部署)
小结:1.class load 静态代码块-默认值-初始值 2.new -申请内存 -默认值 -初始值
硬件层数据一致性问题:cacheline为基本单位,目前64bytes,为了避免伪共享,使用缓存行对齐可以提高执行效率
long类型的赋值不是原子性的,因为32操作系统每次读32位
WCBuffer,合并写(空间很少,4个字节内能合并)
volatile实现细节:可见性:缓存一致性协议
字节码层面:ACC_VOLATILE
JVM层面:对valitile修饰的变量前后加两种屏障
OS和硬件层面:windows的lock指令
synchronized实现细节:
字节码层面:monitorenter mornitorexit
JVM层面:C C++调用操作系统提供的同步机制
OS和硬件层面:X86 lock comxchg XXX
对象创建过程:
1、class loading、linking、initializing 2、申请对象内存 3、成员变量赋默认值 4、调用构造方法,成员变量顺序赋初始值,执行构造方法语句
对象在内存中的存储布局:
普通对象:1.对象头 markword 8字节 2.classpointer 指针 3.实例数据 4.对齐(对象整个为8的倍数)
数组对象:1.对象头 2.classpointer 3.数组长度 4.数组数据 5.对齐
对象头里有什么:锁标志和GC标志 GC为什么最大15:4位
对象定位:句柄池,指向一个空间,空间指向class和对象(GC效率高)直接指针:直接指向对象(取效率高,hotspot)
对象怎么分配:栈能放下就在栈,不能,若对象大 则分配到堆,不大则分配到线程本地区(占用eden,默认1%) new object大小16字节
指令集:基于栈的指令集(JVM)基于寄存器的指令集(汇编语言)
Runtime Data Area
PC(程序计数器) 存放指令位置
JVM Stack 存储栈帧 Frame:
1.Local Variable Table 局部变量表
2.Operand Stack 操作栈(对long的处理,多数虚拟机都是原子的,不用加volatile)
3.Dynamic Linking 指向Runtime Constant Pool,有就直接用,没有就动态解析
4.return address
Heap
Method Area 存储类结构
1.Perm Space(<1.8)
字符串常量位于PermSpace FGC不会清理 大小启动的时候指定,不能变
2.Meta Space(>=1.8)
字符串常量位于堆 会触发FGC清理 不设定的话,最大就是物理内存
Runtime Constant Pool
Native Method Stack
Direct Memory:JVM可以直接访问内核空间的内存(OS管理的内存)NIO,提高效率,实现zero copy
每个线程都有自己独立的PC、VMS、NMS,所有线程共享Heap、Method Area Meta Space
常用指令
init 构造方法 clinit静态方法块(没显示调用)
Store load
invoke
1.InvokStatic
2.InvokeVirtual
3.InvokeInterface
4.InvokeSpecial 可以直接定位,不需要多态的方法: private方法,构造方法
5.InvokeDynamic :JVM最难的指令 lambda表达式或者反射或者其他动态语言scala kotlin,或者CGLib ASM动态产生class会用到的指令
GC
如何定位垃圾:引用计数算法,根可达算法
常见GC算法:Mark-Sweep(标记清除)(适用于对象多,效率低)、Copying(拷贝)(适用于对象少)、Mark-Compact(标记压缩,效率低)
JVM内存分代模型(用于分代垃圾回收算法)
1.部分垃圾回收器使用的模型
除Epsilon ZGC Shenandon之外的GC都是使用逻辑分代模型
G1逻辑分代,物理不分代,其他双分代
2.新生代+老年代+永久代(1.7)/元数据区(1.8)
1.永久代 元数据-class
2.永久代必须指定大小限制,元数据可以设置,也可以不设置无上限(受于物理内存)
3.字符串常量 1.7 - 永久代,1.8堆
4.MethodArea逻辑概念 -永久代/元数据区
3.新生代 = Eden + 2个suvivor区
YGC回收之后,大多数的对象会被回收,活着的进入s0,再回收,活着的进入s1,超过15次就进入老年代 年龄1和年龄2累加总和超过50%进入老年代 1.尽量减少FGC 2. MinorGC = YGC 3.MajorGC = FG
YGC期间survivor区空间不够了 空间担保直接进入老年代
垃圾回收器:STW(stop the world) safe point
第一种组合:Serial和Serial old 单cpu效率最高 单线程 在老年代采用Mark-Sweep、Mark-Compact
第二种组合:Parrallel Scavenge Parrallel old采用Copying,多线程
第三种组合:ParNew(时间厉害):Parrallel Scavenge(吞吐量厉害)的增强和CMS配合使用
CMS:concurrent(并行) mark sweep 无法忍受STW 老年代的 过程:初始标记-STW-并发标记-STW-重新标记-并发处理
CMS的问题:
1.内存碎片化 2.浮动垃圾 发生问题后请Serial old来清理 解决方法:降低CMS触发阈值(让FGC的阈值降低,流出空间给浮动垃圾)
其他垃圾回收机制:G1、ZGC、Shenandon、Eplison 采用三色算法(自身和指向都标记过的黑色,自身标记过的黑色,都没标记的白色,问题:漏表,解决:CMS用关注引用增量,黑色重新标记为灰色 G1采用SATB 关注引用的删除,当引用消失,把这个引用推到GC的堆栈,保证还能被扫描到,避免重复扫描)RSet(其他Region中的对象到本Region的引用)
吞吐量:用户代码时间/(用户代码执行时间+垃圾回收时间)
响应时间:STW越短,响应时间越好
所谓调优就是最求吞吐量还是响应时间。看应用场景,科学计算数据挖掘就是吞吐量优先(PS+PO),网站就是响应时优先(G1)
JVM调优(重启…)
1.根据需求进行JVM规划和预调优
2.优化运行JVM运行环境
3.解决JVM运行过程中出现的各种问题(OOM)
从规划开始
-
一切调优,从业务场景出发
-
无监控,不调优(压力测试,能看结果)
-
步骤
选择垃圾回收策略
计算内存需求(经验值1.5G)
选择CPU(越高越好)
设置日志参数
观察日志情况
HotSpot参数分类
标准:- 开头,所有的HotSpot都支持
非标准:-X 开头,特定版本HotSpot支持
不稳定:-XX 下一个版本可能就没了