• 《码出高效:Java开发手册》 四-走进JVM


    前言

    JVM是java中底层的知识,这里的内容比较复杂,对于一些软件编程,会经常使用,但很多业务其实碰不到这里的知识,下图为目录
    在这里插入图片描述

    介绍

    JVM,java虚拟机,它的前身是99年的hotspot java虚拟机,之后被oracle收购后,形成了现在的OpenJDK使用的主流JVM
    一些商业公司都有自己的定制版本,比如阿里有AJDK

    字节码

    之前讲01是最底层的信号,而再向上就是机器码,不同的操作系统,硬件都会对应不同种类的机器码,这就需要针对多种平台编写不同代码,而jvm可以做到在不同平台都字节码来运行,字节码bytecode大小为一个字节(8位),可以存储256种不同的指令,java有200个左右的指令

    • 主要的字节码指令
      1.加载和存储指令
      将局部变量加载到操作栈中:如ILOAD,ALOAD等
      将操作栈顶存储到局部变量表:ISTORE,ASTORE等
      常量加载到操作栈顶,使用频率高:ICONST,BIPUSH,SIPUSH,LDC等
      2.运算指令
      3.类型转换指令
      4.对象创建与访问指令
      5.操作栈管理指令
      6.方法调用与返回指令
      7.同步指令
      -编写好的java文件是源代码,需要转换为字节码才能给机器执行,转化流程如下
      在这里插入图片描述

    字节码需要通过类加载过程加载到JVM环境后,才可以执行
    执行方法有三种:
    解释执行
    JIT编译执行
    JIT与解释执行混合(主流JVM默认执行方式)

    • JIT作用是将字节码动态编译成可以直接发给处理器指令执行的机器码,简要流程如下
    • 在这里插入图片描述
      书中讲了一个实例:机器在刚启动时,负载比较低,之后会慢慢升高,有程序员在发布时直接分为两批发布,导致前一半机器宕机,说明了JVM刚启动时,JIT动态编译和热点代码统计还没开始,此故障说明了JIT的存在

    类加载过程

    任何程序都需要加载到内存才能与CPU进行交流,字节码.class文件同样需要加载到内存中,才可以实例化类

    • Java的类加载器主要有三个流程,Load,Link,Init
    • Load,加载类文件的二进制流,转化为特定数据结构,校验各种参数后,创建对应类的java.lang.Class实例
    • Link,包括准备,验证,解析三步,这里的验证相比之前更加详细,对类型,变量进行检查,准备就是分配内存,布局内存结构
    • Init,执行构造器clinit方法,如果有其他类的静态方法参与,就解析另外一个类
    • 在这里插入图片描述
      这里还讲了class是Class(注意大小写)的对象,也就是类是Class的抽象
      这里不太明白,只留下大概代码,以后去理解
      在这里插入图片描述
      在这里插入图片描述
      执行结果是
      在这里插入图片描述
      这个示例说明了类的一些加载特性,类加载器把类的实现和定义解耦了,同时可以用2的方法获取注解,方法等
      类加载器的结构,分Bootstrap,platform ClassLoader(平台类加载器),Application ClassLoader的应用类加载器。用户也可以自定义类加载器,查看本地类加载器方式如下:
      在这里插入图片描述
      JDK8下的输出
      在这里插入图片描述
      下图显示的是类加载器间的关系
      在这里插入图片描述
    • 低层次的类加载器在加载类之前会向上逐级询问,这个类是否以加载
      高层次类加载器会检查是否已经加载此类,以及是是否可以加载此类
      通常流程是:向上问是否已加载此类,如果没有,之后向下问是否可加载,如果不能,就让当前类加载器去加载这个类,当然实际类库比图中要多
    • Bootstrap加载的路径可以追加,不建议修改或者删除原有路径
    • 自定义类加载器的情况
      1.隔离加载类:在某些框架中进行中间件与应用模块隔离,比如需要确保应用依赖的jar包不会影响中间件运行时使用的jar包
      2.修改类加载方式,Boostrap外的加载并非一定要引入
      3.扩展加载源,比如从数据库,网络甚至机顶盒进行加载
      4.防止源代码泄露
    • 实现自定义类加载器的步骤:继承ClassLoader ,重写findClass()方法,调用defineClass() 方法。
    • 如下为一个简单的类加载器
    • 在这里插入图片描述

    内存布局

    在这里插入图片描述
    上图即为经典JVM布局

    • 堆:堆区是OOM故障多发地,大量创建对象,容易消耗完堆内存,可以调整大小,-xms,-xmx,一般保持一样大小,在线上环境可以避免GC后调整堆大小时带来额外压力
      堆里对象的晋升流程如图在这里插入图片描述
    • 给JVM 设置运行参数-XX:+HeapDumpOnOutOfMemoryError ,可以让JVM在OOM异常时能输出堆内信息
      • 元空间
        元空间的前身是永久代,由于很难调优,经常出现致命错误,后来用元空间替代后,转为再本地内存中分配
        3.JVM Stack(虚拟机栈)
        栈就是先进后出,像弹夹一样,它是线程私有的,用于进行方法调用
        栈帧:就是栈顶的,只有这里的帧才是有效的,也就是当前栈帧,它是方法运行的基本结构,执行引擎运行时,只能操作当前栈帧
        StackOverflowError表示请求的栈溢出,导致内存耗尽,通常出现在递归方法中
    • 栈帧包括了局部变量表,操作栈,动态连接,方法返回地址等
    • 局部变量表:存放方法参数和局部变量
    • 操作栈:初始状态为空的桶式结构栈,里面会压入弹出一些指令,与局部变量表进行交互
      以一个常见的面试题:i++和++i为例子,在字节码层面是这样的
      在这里插入图片描述
      左边:局部变量表中取出一个数,压入栈顶,下一步实现+1操作,对栈顶元素无影响,接下来把栈顶元素赋值给a
      右边:先做完+1操作后,压入栈顶,之istore_2后存进去的就是i+1的值
      这里i++不是原子操作,即使用volatile修饰,多个线程下也会产生数据覆盖问题
    • 动态连接:每个栈帧中有一个对当前方法的应用,就是动态连接
    • 方法返回地址:方法执行时有两种退出情况:正常退出和异常退出
      4.本地方法栈
      本地方法栈是线程私有的,虚拟机栈主JVM内部,本地方法栈则主JVM外部
      5 .程序计数器
      这里的计数器指的是CPU的寄存器,这里主要是保证指令之间连贯执行,多线程之间互不影响
      从公有私有角度可以为JVM分为以下
      在这里插入图片描述

    对象实例化

    从Object ref = new Object()分析,查看字节码如下
    在这里插入图片描述

    • 从字节码角度看待对象创建过程
      new:无CLass对象则进行类加载,之后分配堆内存,零值初始化,将指向实例变量的引用变量压入虚拟机栈顶
      Dup:在栈顶复制该引用变量,如果 方法有参数,需要把参数压入操作栈,两个引用变量的目的不同,压至底下的用于赋值,栈顶的作为句柄调用相关方法
      INVOKESPECIAL:调用对象实例方法,通过栈顶调用init方法,clinti是类初始化方法,inti是对象初始化方法
    • 从执行步骤分析对象创建过程
      确定类元信息是否存在,这里找不到class文件就会抛出ClassNotFound异常
      分配对象内存,计算对象占用空间,在堆中划分内存,这里有同步操作,一般是CAS失败重试或者区域加锁等
      设定默认值
      设置对象头,包括哈希码,GC,锁信息,类元信息
      执行init方法,初始化成员变量,执行实例化代码块,调用类的构造方法,引用变量接收对象首地址

    垃圾回收

    垃圾回收,简称GC,判断内存不足且虚拟机空闲时,就会清除不再使用的对象,自动释放内存

    • 判断逻辑:GC Roots,对象与其roots之间没有引用关系(包括间接),就可以被回收
    • 回收方法:标记清除:直接清除不用的对象,会产生大量内存碎片
      标记整理:将可以存活对象整理到内存一端,把边界之外的都清除掉
      复制:MArk-coy,分两块空间,回收时把可存活对象放到另一块上,之后清除原对象
      新生代使用复制,老生代标记整理
    • 垃圾回收器:有数十种,常用的有serial,CMS,G1

    在这里插入图片描述

    G1采用标记清除,可以配置-XX :+UseCMSCompactAtFul!Collection参数,强制full gc后对老年代进行压缩,也就是空间碎片整理,可以配置XX : +CMSFul!GCsBeforeCompaction=n 参数,防止频繁整理引发STW(暂停整个应用程序执行)
    在这里插入图片描述

  • 相关阅读:
    java计算机毕业设计基于springboot+vue+elementUI的家具销售电商平台(前后端分离)
    佳能相机拍出来的dat文件怎么修复为正常视频
    【01】Java代码如何运行
    开源数据库MySQL 8.0 OCP认证精讲视频、环境和题库 之二
    令无数站长闻风丧胆的 DDoS 攻击到底是什么
    解决远程连接 docker中mysql 失败
    HPA (Horizontal Pod Autoscaler) In K8s
    php文件操作
    Docker的3主3从redis集群配置(扩容和缩容配置)
    oracle触发器的自治事务
  • 原文地址:https://blog.csdn.net/qq_39167720/article/details/128084565