1.JVM整体结构图
JVM虚拟机分为: 堆,栈,程序计数器,元空间,本地方法栈,(另外包括类装载子系统,执行引擎)
其中 堆,元空间是所有线程共享,栈,本地方方法栈,程序计数器是每个线程私有的。
堆主要分为 eden区,S0区,S1区,old区,今天不介绍。今天主要介绍一下JVM栈的结构
如上图所示,栈的组成是一个一个的栈帧组成的FILO队列,每个栈帧代表一个执行方法,
主要的栈帧分成 局部变量表,操作数栈,动态链接,方法出口。
局部变量表: 主要用于存放每个方法的局部变量
操作数栈:存放临时创建的数据
动态链接: 栈要执行JVM字节码,主要是执行方法区.class加载的地址。
方法出口:方法返回地址
程序计数器主要存放当前线程执行的JVM指令的id,通过javap -c xx.class 或者javap -v xx.class命令查看指令行号
如下图中的
2.JVM内存模型
上图就是jvm对一个initFlag变量的操作,分为8个步骤,分别对应8个原子操作
JVM 8个原子操作,所有的java代码,都会转化成这8个原子操作
read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
write(写入):作用于主内存,它把store传送值放到主内存中的变量中。
lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
lock原子操作的实现
(1) MESI缓存一致性协议:一个线程对共享变量的修改,通过总线时,另一个线程通过总线嗅探机制,使自己的工作内存的对应的变量的缓存行失效,关于缓存行,可以查看这篇文章 https://my.oschina.net/manmao/blog/804161
(2) 总线加锁; 对CPU到主存的总线进行加锁,其他的CPU无法访问。
volatile 关键字底层实现
volatile修饰的变量,在对改变量进行操作时,会在指令前加 lock修饰。volatile关键字只能保证数据多线程可见性,无法保证多线程操作的线程安全。volatile适用于一写多读的场景。
synchrinized指令实现 monitorenter和monitorexit,当代码段处于加锁的区间,只能有一个线程可以执行,保证了线程的有序性和可见性。处于synchrinized修饰的代码段,变量都会里面更新到主内存和其他线程的的工作内存更新。
并发编程的三大特性:原子性,可见性,有序性。 有序性:happen-before原则,as-if-serial语义(保证单线程计算结果相同)和指令重排序
JVM 状态指令
(1)查看GC情况
* jstat -gcutil pid 1000 10
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 20.19 86.29 12.91 97.85 96.99 59701 531.379 264 16.119 547.498
CCS:压缩类空间占比
* jstat -gcnew pid 1000 10 (新生代大小)
TT:对象在新生代存活的次数
MTT:对象在新生代存活的最大次数
详情见
https://blog.csdn.net/maosijunzi/article/details/46049117
(2)jvm 线程栈情况,死锁情况
jstack -l pid > /opt/tmp/threadDump.txt
top -Hp pid 查看指定进程的线程的情况
printf "%x\n" tid 将 tid转化成 16进制展示
(3)jvm堆内存情况
是否有内存泄漏,java内存堆栈(如果内存占用过多)
jmap -histo:live pid查看堆内存中的对象数目
解析jstack 日志工具
https://fastthread.io/
关注点
内存占用超过 75% 单核 systemLoad (均值>0.8,峰值>1.2)
Full GC 单次(平均> 0.5s)
磁盘持续减少的幅度 > %1
流量(包括带宽,TPS等)显著增加
----------------JVM GC 日志----------------------
# -XX:+PrintGCDetails
33.125: [GC [DefNew:3324k->152k(3712k),0.0025925secs]3324k->152k(11904k),0.0031680secs]100.667: [Full GC [Tenured:0k->210k(10240k),0.0149142secs]4603k->210k(19456k), [Perm:2999k->2999k(21248k)],0.0150007secs]
[Times: user=0.01sys=0.00real=0.02secs]
首先是最前面的33.125和100.667,代表的是GC发生的时间,这个数字的含义是从Java JVM启动以来经过的秒数。然后是日志开头的“[GC” 和“[Full GC”说明了这次垃圾收集的类型。接下来的“[DefNew”、"[Tenured"、“[Perm”表示GC发生的区域,而后面的"3324k->152k(3712k)"表示的是“GC前该区域已使用量->GC后该区域已使用量(该区域总量)”。而在后面的"3324k->152k(11904k)"表示的是“GC前Java堆已使用量->GC后Java堆的使用量(Java堆的总量)”。后面的0.0025925 secs等表示的是该区域GC的时间。而[Times: user=0.01 sys=0.00 real=0.02 secs]
*CMS收集
UseConcMarkSweepGC使用ParNew + CMS + Serial Old 的GC组合,Serial Old GC作为CMS GC出现Concurrent Mode Failure后的备用GC
UseParallelGC JVM运行在Server模式下的默认值,使用Parallel Scavenge GC + Serial Old 的GC组合
UseParallelOldGC 使用Parallel Scavenge GC + Parallel Old GC的GC组合
UseG1GC使用G1 GC来进行垃圾收集