JVM体系大全
2020-04-22 10:26:17 0 举报
AI智能生成
马老师JVM的VIP课程总结,更新中
作者其他创作
大纲/内容
1、虚拟机基础概念(来源:马士兵课程)
java -version :可以看到JVM的具体实现HotSpot,解释编译混合执行mixed mode(解释器 + 热点代码编译)
-Xint 使用解释模式,启动很快,执行稍慢
-Xcomp 使用纯编译模式,执行很快,启动很慢
检测热点代码: -XX:CompileThreshold = 10000
java -version :可以看到JVM的具体实现HotSpot,解释编译混合执行mixed mode(解释器 + 热点代码编译)
-Xint 使用解释模式,启动很快,执行稍慢
-Xcomp 使用纯编译模式,执行很快,启动很慢
检测热点代码: -XX:CompileThreshold = 10000
2、class文件结构
是什么:二进制字节流
是什么:二进制字节流
可以通过jdk自带的命令:javap -v 目标class文件的绝对路径,进行查看,或者通过IDEA插件:jclasslib Bytecode viewer
Magic Number
Minor Version
Major Version
constant_pool_count
constant_pool
1、CONSTANT_Utf8_info
tag:1 --占用空间一个字节
length:UTF-8字符串占用的字节数
bytes:长度为length的字符串
3、CONSTANT_Integer_info
tag:3
bytes:4个字节,Big-Endian(高位在前)存储的int值
4、CONSTANT_Float_info
tag:4
4个字节,Big-Endian的float值
5、CONSTANT_Long_info
tag:5
8个字节,Big-Endian的long值
6、CONSTANT_Double_info
tag:6
8个字节,Big-Endian的double值
7、CONSTANT_Class_info
tag:7
index:2字节,指向类的全限定名项的索引
8、CONSTANT_String_info
tag:8
2字节,指向字符串字面量的索引
9、CONSTANT_Fieldref_info
tag:9
index:2字节,指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项
index:2字节,指向字段描述符CONSTANT_NameAndType的索引项
10、CONSTANT_Methodref_info
tag:10
index:2字节,指向声明方法的类或者接口描述符CONSTANT_Class_info的索引项
index:2字节,指向字段描述符CONSTANT_NameAndType的索引项
11、CONSTANT_InterfaceMethodref_info
tag:11
index:2字节,指向声明方法的类或者接口描述符CONSTANT_Class_info的索引项
index:2字节,指向字段描述符CONSTANT_NameAndType的索引项
12、CONSTANT_NameAndType_info
tag:12
index:2字节,指向该字段或方法名称常量项的索引
index:2字节,指向该字段或方法描述符常量项的索引
15、CONSTANT_MethodHandle_info
tag:15
refence_kind:1字节,1-9之间的一个值,决定了方法句柄的类型,方法句柄类型的值表示方法句柄的字节码行为
refence_index:2字节,对常量池的有效索引
16、CONSTANT_MethodType_info
tag:16
descriptor_index:2字节,指向Utf8_info结构表示的方法描述符
18、CONSTANT_invokeDynamic_info
tag:18
bootstrap_method_attr_index:2字节,当前Class文件中引导方法表的bootstrap_method[]数组的有效索引
name_and_type_index:2字节,指向NameAndType_info表示的方法名和方法描述符
access_flags
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否为final
ACC_SUPER 0x0020 该标志必须为真,JDK1.0.2之后编译出来的内容必须为真,指明invokespectial指令使用新语义
ACC_INTERFACE 0x0200 是否接口
ACC_ABSTRACT 0x0400 接口或抽象类
ACC_SYNTHETIC 0x1000 编译器自动生成,非用户代码产生,http://www.jianshu.com/p/d571300810b3
ACC_ANNOTATION 0x2000
ACC_ENUM 0x4000
this_class
super_class
interfaces_count
interfaces
fields_count
fields
access_flags 2字节
ACC_PUBLIC -0x0001
ACC_PRIVATE - 0x0002
ACC_PROTECTED -0x0004
ACC_STATIC -0x0008
ACC_FINAL -0x0010
ACC_VOLATILE -0x0040
ACC_TRANSIENT -0x0080
ACC_FINAL -0x0010
ACC_ENUM -0x4000
name_index u2
descriptor_index u2
B - byte
C - char
D - double
F - float
I - int
J - long
S - short
Z - boolean
V - void
L - Object 例如:Lcom/botech/dispacher/Test
数组[
一维数组[B [Ljava/lang/Stirng
多维数组[[C [[[Ljava/lang/String
attributes_count
attributes
methods_count
methods
access_flags
ACC_PUBLIC -0x0001
ACC_PRIVATE - 0x0002
ACC_PROTECTED -0x0004
ACC_STATIC -0x0008
ACC_FINAL -0x0010
ACC_SUNCHORNIZED - 0x0020
ACC_BRIDGE - 0x0040 编译器产生的桥接方法 https://blog.csdn.net/mhmyqn/article/details/47342577
ACC_VARARGS -0x0080
ACC_NATIVE - 0x0100
ACC_ABSTRACT -0x0400
ACC_STRICTFP -0x0800 https://zhidao.baidu.com/question/451207689.html
ACC_SYNTHETIC -0x1000
name_index u2
descriptor_index u2
先参数列表(放在小括号内部),后返回值
void m() -> ()v
String toString() -> ()Ljava/lang/String;
long pos(int[] arr1,int arr2,long length) -> ([iiJ)J
attributes_count
attributes
attributes_count - u2
attributes
既有预定义属性,也可以自定义,java虚拟机自动忽略不认识的属性
Code - 方法表 - 该方法编译成的字节码指令
u2 attribute_name_index
u4 attribute_length
u2 max_stack
u2 max_locals
u4 code_length
code
u2 exception_table_length
exception_table
u2 attribute_count
attributes
ConstantVlue:字段表,final关键字定义的常量值
Deprecated:类、方法表、字段表
Exceptions:方法表
EnclosingMethod:类文件,局部类或匿名类的外部封装方法
InnerClasses:类文件,内部类列表
LineNumberTable:Code属性,java源码的行号与字节码指令的对应关系
LocalVariableTable:Code属性,方法局部变量表
SourceFile:类文件,源文件名
自我总结:class文件中除了上面这些固定位置信息后,其他的具体代码实际都对应着java虚拟机规范中的指令集,也就是java汇编码,例如:aload_0对应的十六进制是0x26,然后在class搜26就能找到具体方法中的对应。
这个感觉有点像将所有的信息都放入常量池中,然后根据不同的命令对常量池中的对象进行调用、操作等
这个感觉有点像将所有的信息都放入常量池中,然后根据不同的命令对常量池中的对象进行调用、操作等
https://mp.weixin.qq.com/s/Zfv9SjQeAtjnT4xVwTsa2g
java内存模型八大原子操作
lock:锁定
作用于主内存,标识变量为线程独占
unlock:解锁
作用于主内存,解锁线程独占变量
read:读取
作用于主内存,读取内容到工作内存
load:载入
作用于工作内存,read后的值放入线程本地变量副本
use:使用
作用于工作内存,传值给执行引擎
assign:赋值
作用于工作内存,执行引擎结果赋值给线程本地变量
store:存储
作用于工作内存,存储到主内存给write备用
write:写入
作用于主内存,写变量值
3、内存加载过程
Loading(加载)
双亲委派机制,主要是考虑安全性,从上到下进行类加载
自顶而下:进行实际查找和加载 child方向
自下而上:检查该类是否已经加载 partent方向
自顶而下:进行实际查找和加载 child方向
自下而上:检查该类是否已经加载 partent方向
主要就是出于对安全的考虑
如何打破双亲委派机制?
1、JDK1.2之前,自定义ClassLoader都必须重写loadClass()
2、ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
3、热启动,热部署:osgi tomcat都有自己的模板指定classloader(可以加载同一类库的不同版本)
1、Bootstrap加载器
加载路径:sun.boot.class.path
2、Extension加载器
加载路径:java.ext.dirs
3、App加载器
加载路径:java.class.path
4、CustomClassLoader加载器
实现方式
继承ClassLoader
重写模板方法findClass
重写的findClass中,返回defineClass(四个参数)
使用场景
1、Spring中对class会进行动态代理(新的class)
2、tomcat加载自己的jar包
3、热部署,针对改动的文件手动load
Linking
Verification(校验)
校验class文件符不符合标准,例如:是不是以cafe babe开头的
Preparation(准备)
给静态成员变量赋默认值
Resolution(解析)
将类、方法、属性等符号引用解析为直接引用
常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
Initializing(初始化)
调用类初始化代码,给静态成员变量赋初始值
4、运行时内存结构
1、前置知识铺垫
计算机构成
2、数据一致性问题
1、缓存行cacheLine的存在,大多数为64字节
2、现代CPU的数据一致性 = 缓存锁(MESI缓存一致性协议) + 总线锁
3、位于同一缓存行的两个不同数据,被两个相同CPU锁定,产生互相影响的伪共享问题
3、乱序问题
1、问题现象:CPU中PC的指令会有多条,对于没有依赖关系的指令会发生乱系执行
2、合并写技术:WCBuffer,高于L1,容量非常小,4个字节(知识点比较偏),直接写到L2缓存
3、有序性保证
CPU级别内存屏障
IntelCPU
IntelCPU
sfence:写操作前后串行
lfence:读操作前后串行
mfence:读写操作前后串行
汇编指令:lock
JVM级别内存屏障
LoadLoad:读操作前后串行
StoreStore:写操作前后串行
LoadStore:先读再写
StoreLoad:先写再读
JVM内存屏障不是仅依赖硬件级别的CPU屏障,也可以通过ock指令实现
具体实现:Volatile关键字
字节码层面:ACC_VOLATILE
JVM层面:通过内存屏障实现
OS及硬件层面:lock指令实现
synchronized实现细节
字节码层面:ACC_SYNCHRONIZED
JVM层面:monitorenter、monitorexit指令
C C++调用了操作系统提供的同步机制
C C++调用了操作系统提供的同步机制
OS及硬件层面:X86 : lock cmpxchg / xxx
4、对象相关问题
1、创建过程
1、class loading
2、class linking(verification preparation resolution)
3、class initializing
4、申请对象内存
5、成员变量赋默认值
6、调用构造方法<init>
1、成员变量顺序赋初始值
2、执行构造方法语句
2、内存布局
1、对象头:MarkWord 8个字节
对象头空间分布
引出一个问题:为什么GC年龄默认为15?,因为4个字节代表最大值就是15
2、ClassPoint指针: -XX:UseCompressedClassPointers为4个字节,不开启为8个字节
2.1、此为数组专用,存储数组长度,4字节
3、实例数据(数组数据): 《-XX:UseCompressedOops为4字节,不开启为8个字节》针对引用类型
Ordinary Object Pointers
Ordinary Object Pointers
4、Padding对齐,8的倍数
5、运行时数据区(来源:图灵学院诸葛老师)
Heap(堆):线程共享
Stack(栈):线程独有
局部变量表
操作数栈
比较典型的问题就是:i=i++ 其实i不变
动态链接
将符号引用解析为直接引用的过程
方法出口
Native Method Stacks(本地方法栈):线程独有
Method Area(方法区):线程共享
具体实现A:Perm Space 永久区 (<1.8)
一旦确定无法改变大小,可能出现溢出现象
具体实现B:Meta Space 元空间(>=1.8)
受限于物理内存,比上面更大更灵活
包含常量池
存储Class的信息、方法编译完的信息、字节码、类静态变量等
其中字符串常量在1.7是在这的,1.8之后在【堆】里了
Program Counter(程序技术器):线程独有
5、JVM常用指令
命令行参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
指定垃圾回收器
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
-XX:+UseParNewGC = ParNew + SerialOld
-XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old(1.8默认)
-XX:+UseG1GC = G1
设定日志参数
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
或者每天产生一个日志文件
6、GC垃圾回收
概览图(来源:图灵学院诸葛老师)
何为垃圾:没有任何引用指向,如何找到?
引用计数法
不可能解决的问题:循环引用
根可达算法
根对象
线程栈变量
静态变量
常量池
JNI指针
GC的常用算法
Mark- Sweep(标记清除)
优势:算法性对简单,存活对象比较多的情况下效率较高
劣势:两遍扫描,效率偏低。内存空间不连续容易产生碎片
Coping(拷贝)
优势:适用于存活对象较少的情况,只扫描一次,效率提高没有碎片
劣势:空间浪费,移动赋值对象,需要调整对象引用
Mark - Compact(标记压缩)
优势:不会产生碎片,方便对象分配,不会产生内存减半
劣势:扫描两次,需要移动对象,效率偏低
分代年龄
年轻代(1/3)
MinorGC/YGC:年轻代空间耗尽触发
-Xmn 新生代大小
老年代(2/3)
MajorGC/FullGC:老年代空间耗尽触发
-Xms:初始堆大小 -Xmx:最大堆大小(放在这里有点不合适,知道就行吧)
两者比例可以通过: –XX:NewRatio 来指定
对象何时进入老年代?
参数:XX:MaxTenuringThreshold 指定次数,默认15
垃圾回收15次的进入老年代
动态年龄:s1入s2超过5成,把年龄最大的放入老年代
分配担保:YGC期间,survivor区空间不够了,空间担保直接进入老年代
提升效率的分配细节
栈上分配(无需调整)
线程私有小对象
无逃逸
支持标量替换
线程本地分配TLAB(无需调整)
占用eden,默认1%
多线程的时候不用竞争eden就可以申请空间,提高效率
小对象
VM options:-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB
去掉逃逸分析、去掉标量替换、去掉TLAB,效率会减半
去掉逃逸分析、去掉标量替换、去掉TLAB,效率会减半
对象分配过程图
常见的垃圾回收器
概览图:目前还不存在不stop-the-world的垃圾回收器,ZGC可以做到1-2ms以内
年轻代
Serial
单线程串行回收、safe point、stop-the-world
Parallel Scavenge(默认)
多线程并行回收、stop-the-world
同下比较:吞吐量优先
ParNew
多线程并行回收、stop-the-world
同上比较:响应时间优先
老年代
Serial Old
标记压缩算法
Parallel Old(默认)
整理、压缩算法
CMS JDK1.4版本之后
(concurrent mark sweep)
(concurrent mark sweep)
开启了并发回收的先河
回收阶段
初始标记(STW)
只标记root
并发标记
一个GC过程,8成的时间都是浪费在这里
在程序运行的同时,进行标记
重新标记(STW)
由于上面的运行的同时进行标记,达不到全部标记,所以要重新标记
并发清理
在清理的过程中还会产生新的垃圾:浮动垃圾
个人理解:这个有点类似于迭代开发
CMS的弊端
内存碎片化
CMS不适用于大内存
碎片化会导致新生代对象无法进入老年代,会降级为Serial Old来进行标记压缩
浮动垃圾
同上,也会切换至Serial Old
-XX:CMSInitiatingOccupancyFraction92% 阈值可以调低,留出浮动垃圾的空间
垃圾回收器与内存大小的关系
Serial 几十M
PS 上百M—几个G
CMS 20G
G1 上百G
ZGC 4T-16T
7、调优相关
概念区分
内存泄漏 memory leak
内存溢出 out of memory
例子用到的命令
java -XX:+PrintCommandLineFlags HelloGC
java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC
PrintGCDetails PrintGCTimeStamps PrintGCCauses
PrintGCDetails PrintGCTimeStamps PrintGCCauses
java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC
java -XX:+PrintFlagsInitial 默认参数值
java -XX:+PrintFlagsFinal 最终参数值
java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数
java -XX:+PrintFlagsFinal -version |grep GC:查询GC相关的命令
GC日志详解
调优前的基础概念
吞吐量:用户代码时间 /(用户代码执行时间 + 垃圾回收时间)
响应时间:STW越短,响应时间越好
所谓调优,首先确定,追求啥?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量...
什么是调优?
1、根据需求进行JVM规划和预调优
2、优化JVM运行环境(慢、卡顿)
3、解决JVM运行过程中出现的各种问题(OOM)
系统CPU经常100%,如何调优?
分析:一定有线程在占用系统资源(jps查看java进程、jinfo 进程ID)
找出那个进程CPU高(top)
查看对象数量:jmap -histo 进程ID | head -20、
jmap -dump:format=b,file=xxx pid(线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合))
jmap -dump:format=b,file=xxx pid(线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合))
在项目启动的时候加入参数:-XX:+HeapDumpOnOutOfMemoryError
很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
在线定位:Arthas
该进程哪个线程CPU高(top-Hp 进程ID)
导出该线程的堆栈(jstack -l 线程ID转换成16进制、jstack 进程ID查看下面所有线程的状态)
查找哪个方法(栈帧)消耗时间(jstack)
系统内存飙高,如何查找问题?
导出堆内存(jmap)
分析(jhat jvisualvm mat jprofiler)
如何监控:jstat jvisualvm jprofiler arthas top...
工具
jconsole
jvisualvm(JDK自带)
jprofiler(收费)
arthas
jvm(jinfo)
查看详细情况
thread(jstack)
查看有多少个线程
dashboard(top)
观察系统情况
heapdump(jmap -dump)
导出堆文件
jhat 文件名.hprof(JDK自带)、MAT、jvisualvm
对堆文件进行分析
jad 类的路径(反编译)
动态代理生成类的问题定位
第三方的类(观察代码)
版本问题(确定自己最新提交的版本是不是被使用)
redefine(热替换)
目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性
0 条评论
下一页