JVM -学习分享篇

简介: JVM学习分享

JVM

jvm有两个运行模式 server和client模式

JVM基础知识
jvm和java无关
查看  binep
javap -v  class文件路径  
jclasslib
jvm method code 指令集
https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-6.html#jvms-6.5.aconst_null
对class文件有过深入研究,了解class文件的详细信息。

class类加载过程
1.loding
类加载器   classloader
bootstrap  C++
extension   扩展jar包
app            classpath
customClassLoader  自定义
双亲委派  保证类加载安全
父加载器是parent对象的classloader
主要保证jvm类加载安全,其次直接找缓存提升效率


jdk历史上有几次破坏双亲委派机制?
https://zhuanlan.zhihu.com/p/372730404
自定义类加载器
ClassLoader -> findClass()  设计模式:模板方法    懒加载需要才加载
自定义类加载器的方式:
1继承classloader
2重写findclass()
3defineClass (byte[]-> Class clazz)  加密实现自定义class
混合执行 编译执行  解析执行
1 检测热点代码 -XX:CompileThreshold=10_000
2打破双亲委派机制  重写loadClass()
2.linking
a verification   校验是否符合JVM规范
b preparation   class静态变量赋默认值
cresolution 解析 将符号解析为指针,直接引用
3.initializing
静态变量附初始值
* JVM并没有规定什么时候加载* 但是严格规定了什么时候初始化loadClass->默认值->初始值
new ->申请内存空间->默认值->初始值
单例 DCL(双重检查) 使用volatile 与 new对象有关
JIT指令重排
0 new #2 <com/zzz/test01/Test>
3 dup
4 invokespecial #3 <com/zzz/test01/Test.<init> : ()V>
7 astore_1
8 return
4和7的顺序有可能会发生指令重排导致 未new完成导致另一个线程拿到默认值的对象

java的内存模型 jmm
1.硬件
现代CPU的数据一致性实现是通过 缓存锁(MESI等)+总线锁
1.MESI cache 缓存一致性  (缓存锁)
Modified   EXclusive Shared  Invalid
2.CacheLine 缓存行对齐概念 伪共享
多数是64字节
伪共享: Cpu一次load了多个内容(a,b) 一个cpu 修改a的时候,会使整个缓存行(64字节)失效,导致另一个cpu在缓存中的b也随之失效,导致重新load
使用缓存行对齐
2.乱序
CPU为了提高指令执行效率,会在一条指令执行过程中,去执行另一条与上条无关的指令。
wcbuff  合并写
如何保证有序  
内存屏障
load store
volatile 有序
volatile的实现
1.字节码层面
ACC_volatile
2.JVM层面
volatile内存读写都加屏障  load store
3.硬件层面
hsdis 虚拟机反汇编
Windows lock 指令
synchronized的实现
1.字节码层面
ACC_SYNCHRONIZED
monitorenter monitorexit
2.JVM层面
C/C++ 调用操作系统提供的同步机制
3.硬件层面
x86 lock comxchg
观察虚拟机配置
C:\>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=266325824 -XX:MaxHeapSize=4261213184 -XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
1.普通对象
1对象头: markWord 8
2ClassPointer 指针 :-XX:+UseCompressedClassPointers 为4字节 不开启为8字节
3实例数据
1.引用类型 -XX:+UseCompressedOops  为4字节 不开启为8字节
4padding对齐 8的倍数
2.数组对象
1对象头: markword 8
2ClassPointer 指针
3数组长度: 4字节
4数组数据
5对齐8的倍数
private static class P {                    //8    markword                    //4  class pointer  开启压缩4字节 不开8字节    int id;         //4    String name;    //4  引用类型 默认64位的   由于oops开启 占4字节    int age;        //4     byte b1;        // 1    byte b2;        // 1    Object o;       // 4    byte b3;        // 1 }
3.对象头包含信息
32bit


1没有重写过hashCode  ->31位hashCode  System.identityHashCode( )
当一个对象已经计算过identityHashCode 无法进入偏向锁状态
2为什么GC年龄默认是15?
因为分代年龄只有4位 2的4次方 范围是0-15
4.对象定位
1句柄池
2直接指针  Hotspot
Java运行时数据区和常用指令
PC 程序计数器 program  count
每一个jvm虚拟机线程都有自己的pc
JVM stacks  
每个线程一个栈 每个方法一个栈帧  frame
每一个jvm线程都有独有的jvm栈
栈帧frame  
每个方法对应一个栈帧


1.local variable table 局部变量表
2.opreand stack 操作数栈
3.dynamic Linking     常量池的符号链接
4.return address 方法的返回值地址
Heap
jvm所有的线程共享一个堆
Native method stacks  
Java本地方法 C/C++  JNI
Direct Memory 直接内存  
jdk1.4 之后 新增
直接访问内核空间  NIO zero cpoy
Method area 方法区
运行时常量池
方法区共享在所有的jvm线程中
存放Class的结构
1perm space  <1.8  字符串常量位于permSpace FGC不会清理
2meta space  >=1.8  字符串常量位于堆 会触发FGC 清理
问题:
1.为什么每个线程独有PC?
CPU切换,线程记录执行记录
看代码 读原理
// 为什么 i 输出是8?
public class T01_Test {    public static void main(String[] args) {        int i = 8;  //对应0 2 指令        i = i++;   //对应3 4 7 指令    }}
0 bipush 8   // 将8作为byte,扩展为int   压栈
2 istore_1 // 出栈 放入局部变量表index为1的地址
3 iload_1    // 把局部变量表index=1 的值  压栈
4 iinc 1 by 1  // 局部变量表 index=1 的值+1
7 istore_1  // 出栈  放入局部变量表index为1的地址
8 return
int i = 128;
bipush   1<<8   [-128,127 ]在此区间内
sipush   1<<8   不在此区间
public void m2(int k) {    int i=300;}
sipush 300
istore_2   // 非static  局部变量表index=0 存在this
public void add(int a, int b) {    int c = a + b;}
0 iload_1  //局部变量表 index=1 压栈
1 iload_2  //局部变量表 index=2 压栈
2 iadd  两数相加
3 istore_3  // 操作数栈顶的值int必须是类型,它从操作数堆栈中弹出,并将 < n > 处的局部变量的值设置为value
public static void main(String[] args) {    Hello_02 h1 = new Hello_02();    h1.m1();}public void m1() {    int i = 200;}
0 new #2 <com/zzz/test01/Hello_02>
3 dup
4 invokespecial #3 <com/zzz/test01/Hello_02.<init> : ()V>
7 astore_1
8 aload_1
9 invokevirtual #4 <com/zzz/test01/Hello_02.m1 : ()V>
12 return

aload   将局部变量表压入操作数栈
astore  它从操作数堆栈中弹出,并将 < n > 处的局部变量的值设置为value
GC

GC垃圾寻找算法、  1.引用计数     很难解决循环引用   2.根可达算法  哪些是根:     1.线程栈变量    2.静态变量    3.常量池    4.JNI指针     GC垃圾清除算法1.标记清除适用于存活对象比较多的情况下需要两次扫描 1标记2清除  容易产生碎片2.拷贝内存一份为二,将可回收与不可收回区分  适用于存活对象比较少的情况,扫描一次 没有碎片空间浪费、需要复制移动对象,调整对象引用地址。3.压缩扫描两次、需要移动对象不会产生碎片、方便对象分配 JVM分代模型G1属于逻辑分代 其他的属于逻辑+物理分代  新生代 1  拷贝算法eden  survivor(幸存者区)  survivor  8:1:1MinorGC /YGC  老年代 2  标记清除 或 压缩算法MajorGC /FullGC 一个对象从出生到消亡的过程对象产生-> 栈-> eden-> s1-> s2-> old JVM 调优 1.栈上分配 (无需调整)线程私有小对象 无逃逸 -(只在一段代码中使用)标量替换 -(使用普通类型代替) 2.线程本地分配TLAB  (无需调整)占用eden 1%默认线程独有空间小对象 -XX:-DoEscapeAnalysis   // 逃逸分析-XX:-EliminateAllocations  //标量替换-XX:-UseTLAB  // 线程本地分配 对象什么时候进入老年代-XX:MaxTenuringThreshold  与 对象头 MarkWord CG大小4位有关系CMS=6动态年龄:当 Eden+s1->cpoy->s2 时,如果超过S2的50%时,年龄最大的直接进入老年代  如何确定一个对象存放的是栈还是堆?




2.年轻代和老年代的对比  1:2   1.8 JDKjava -XX:+PrintFlagsFinal -version|grep NewRatio







分配担保YGC期间,当survivor区不够,空间担保直接进入老年代 常见的垃圾回收器1.Serial/Serial Old2.Parallel Scavenge/Parallel Old3.ParNew/CMS 演进:      JDK的诞生Serial追随,为了提高效率,诞生了PS。       为了配合CMS 诞生了PN。      CMS是JDK1.4后期引入 是里程碑式的。      CMS问题较多,目前没有JDK版本默认使用CMS。      并发垃圾回收是因为无法忍受STW      开启了并发垃圾回收STW  (stop the world)  safe point1.Serial/Old  单线程清理垃圾   清理算法:mark sweep / compact2.Parallel Scavenge/Old (PS+PO) 多线程清理垃圾  清理算法:copy / compact3.ParNew(ParallelNew)  PS的增强  清理算法:copy     能够搭配CMS 4.CMS  (concurrent mark sweep)从线程角度:CMS四个阶段1.初始标记 STW  2.并发标记3.重新标记 STW4.并发清理 CMS的问题1.内存碎片 memory fragmentation2.浮动垃圾 floating garbage 解决方案:降低触发CMS的阈值   -XX:CMSInitiatingOccupancyFraction 92%    92%的时候产生FGC,让CMS保持老年代足够的空间--    jvm调优 调优之前的概念: 1.吞吐量:用户代码时间/用户代码执行时间+垃圾回收时间 2.响应时间:STW越短,  响应时间越好 所谓调优  即追求  吞吐量/响应时间 优先 吞吐量优先:PS+PO 响应时间:G1 CMS+PreNew       调优从业务场景开始 没有业务场景的调优都是耍流氓无监控(压力测试,能看到结果)不调优步骤:1.熟悉业务场景(没有最好的垃圾回收器,只有最适合的垃圾回收器)  a.响应时间、停顿时间(CMS G1 ZGC) (需要给用户响应)  b.吞吐量=用户时间/(用户时间+GC时间)[PS]2.选择回收器组合3.计算内存需求4.选择CPU(越高越好)5.设定年代大小,升级年龄

目录
相关文章
|
6月前
|
Oracle Java 编译器
基本概念【入门、 发展简史、核心优势、各版本的含义、特性和优势、JVM、JRE 和 JDK 】(二)-全面详解(学习总结---从入门到深化)
基本概念【入门、 发展简史、核心优势、各版本的含义、特性和优势、JVM、JRE 和 JDK 】(二)-全面详解(学习总结---从入门到深化)
87 1
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
65 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
1月前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
32 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
1月前
|
Java 应用服务中间件 程序员
JVM知识体系学习八:OOM的案例(承接上篇博文,可以作为面试中的案例)
这篇文章通过多个案例深入探讨了Java虚拟机(JVM)中的内存溢出问题,涵盖了堆内存、方法区、直接内存和栈内存溢出的原因、诊断方法和解决方案,并讨论了不同JDK版本垃圾回收器的变化。
30 4
|
1月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
45 3
|
1月前
|
SQL 缓存 Java
JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
这篇文章详细介绍了JVM中类文件的初始化过程、硬件层面的数据一致性问题、缓存行和伪共享、指令乱序执行问题,以及如何通过`volatile`关键字和`synchronized`关键字来保证数据的有序性和可见性。
30 3
|
1月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
45 3
|
1月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
53 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
1月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
44 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
5月前
|
缓存 Java
《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)
《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)
50 0