JVM知识点整理(整理中)

news2024/11/15 12:50:37

JVM知识点整理

  • 1、JVM与java体系结构
    • 1.1、java的体系结构
    • 1.2、JVM
      • 1.2.1、从跨平台的语言到跨语言的平台
      • 1.2.2、常用的JVM实现
      • 1.2.3、JVM的位置
      • 1.2.4、JDK、JER、JDK
      • 1.2.5、JVM的整体结构
      • 1.2.6、java代码的执行流程
      • 1.2.7、JVM的代码模型
      • 1.2.8、JVM的生命周期
  • 2、类加载子系统
    • 2.1、内存结构
      • 2.1.1、方法的实现
        • 2.1.1.1、注意
      • 2.1.2、实例2
    • 2.2、类的加载器
      • 2.2.1、类的加载过程
      • 2.2.2、Loading
      • 2.2.3、Linking
        • 2.2.3.1、初始化过程
        • 2.2.3.2、new对象的过程
        • 2.2.3.3、单例模式:双重检查
        • 2.2.3.4、为什么要加volatile关键词?
      • 2.2.4、Initializing
      • 2.2.5、类加载器的分类
        • 2.2.5.1、类加载器之间的关系
        • 2.2.5.2、Bootstrap ClassLoader
        • 2.2.5.3、Extension ClassLoader
        • 2.2.5.4、AppClassLoader
        • 2.2.5.5、自定义类加载器
          • 2.2.5.5.1、什么要自定义加载器?
          • 2.2.5.5.2、自定义加载的步骤
          • 2.2.5.5.3、自定义类加载器加载自加密的class(扩展)
          • 2.2.5.5.4、自定义父加载类
          • 2.2.5.5.5、自定义热部署(Tomcat热部署的简单实现)
      • 2.2.6、ClassLoader
          • 获取classloader的途径
      • 2.2.7、双亲委派
        • 2.2.7.1、机制
          • 2.2.7.1.1、工作原理
          • 2.2.7.1.2、父加载类
          • 2.2.7.1.3、为什么要使用双亲委派?
          • 2.2.7.1.4、沙箱安全机制
          • 2.2.7.1.5、如何打破双亲委派机制?
        • 2.2.7.2、编译器(扩展)
          • 2.2.7.2.1、混合模式
          • 2.2.7.2.2、热点代码编译
            • 2.2.7.2.2.1、检测热点代码
          • 2.2.7.2.3、编译模式的选择
        • 2.2.7.3、懒加载(扩展)
      • 2.2.8、类的主动使用和被动加载
        • 2.2.8.1、判断两个class对象是否为同一个类的条件
        • 2.2.8.2、对类加载器的引用——动态链接
        • 2.2.8.3、主动使用/被动使用
  • 3、字节码与类的加载器
    • 3.1、Class文件结构
      • 3.1.1、概述
        • 3.1.1.1、字节码文件的跨平台性
        • 3.1.1.2、java的前端编译器
        • 3.1.1.3、透过字节码指令看代码细节
          • 3.1.1.3.1、实例
      • 3.1.2、Class文件:虚拟机的基石
        • 3.1.2.1、Class文件本质
        • 3.1.2.2、Class文件格式
        • 3.1.2.3、Class文件的数据类型
      • 3.1.3、Class文件结构
        • 3.1.3.1、实例

1、JVM与java体系结构

1.1、java的体系结构

在这里插入图片描述

1.2、JVM

虚拟机就是一台虚拟的计算机。他就是一款软件,用来执行一系列虚拟计算机指令。大体上虚拟可以分为系统虚拟机程序虚拟机吞吐量优先

系统虚拟机:Visual Box、VMWare,完全是对物理计算机的仿真

程序虚拟机:JVM,专门为执行单个计算机程序而设计

1.2.1、从跨平台的语言到跨语言的平台

  • java是一个跨平台的语言

在这里插入图片描述

  • jvm是一个跨语言的平台
    • java虚拟机平台上运行非java语言编写的程序
    • 只要能编译成class文件,就可以在虚拟机上运行
    • jvm和java是没有关系的,之和class文件有关系
  • jvm是一种规范
    • https://docs.oracle.com/javase/specs/index.html
  • jvm是一个虚构出来的计算机
    • 字节码指令集
    • 内存管理:栈、堆、方法区等
  • java虚拟机就是二进制字节码的运行环境

java不是最强大的语言,但是JVM是最强大的虚拟机

各种语言之间的交互不存在任何困难,就像使用自己语言的原生API一样方便,因为他们始终都运行在一个虚拟机上

1.2.2、常用的JVM实现

  • Sun Classic VM
    • 是世界上第一款商用的java虚拟机
    • 在内部只提供了解释器,没有JIT(即时编译器)
  • Exact VM
    • 准确式的内存管理:可以知道内存中某个位置的数据具体是什么类型
    • 具备现代高性能虚拟机的雏形:热点探测、编译器与解释器混合工作模式
    • 最终被Hstspot虚拟机替代
  • Jrocket
    • 专注于服务器端应用
    • 不太注重程序启动速度,因此内部不包含解析器,全部代码都是靠及时编译器编译后执行
  • J9
    • 广泛用于IBM的各种java产品中
  • Azul VM
    • 与特定硬件平台绑定、软硬件配合的专有虚拟机
    • 每个Auzl VM实例都可以管理至少数十个CPU和数百个GB内存的硬件资源,并提供在巨大内存范围内实现可控的GC时间的垃圾收集器、转悠硬件有花的线程调优等优秀特性
  • Liquid VM
    • Liquid VM不需要操作系统的支持,或者说让它自己本身实现了一个专用操作系统的必要功能,如线程调度、文件系统、网络支持等。
  • Taobao VM
    • 基于openJDK开发的自己的定制版本的AlibabaJDK
    • 深度定制且开源的高性能服务器
    • 即将生命周期较长的java对象从heap中移到heap之外,并且GC不能管理GCIH内部的java对象,以此达到降低GC的回收频率和提高GC的回收效率的目的
    • GCIH中给的对象还能够在多个java虚拟机进程中实现共享
    • 硬件严重依赖Intel的cpu,损失了兼容性,但是提高了性能(凡是和操作系统、硬件耦合高的,性能都强)
  • Graal VM
    • 最有可能取代Hotspot的虚拟机
    • 跨语言全栈虚拟机,可以作为“任何语言”的运行平台使用

在这里插入图片描述

  • 常用的是Hotspot
    • Hotspot占有绝对的市场地位
    • 大多数默认都是Hotspot,相关机制也主要指Hotspot的GC机制。(J9、Jrockit都没有方法区的概念)
    • 名称中的Hotspot指的是他的热点代码探测技术
      • 通过计数器找到最具编译价值代码,触发即时编译器或栈上替换
      • 通过编译器与解释器协同工作,在最优先的程序响应时间与最佳执行性能中取得平衡
      • 解释器:最优先的响应时间——速度快(假设去旅游,不管什么方法,即刻出发,只走路)
      • 编译器:最佳的执行性能——方法(假设去旅游,只考虑方法,只坐公交车,不走路)

在这里插入图片描述

1.2.3、JVM的位置

JVM试运行在操作系统之上的,他与硬件没有直接的交互
在这里插入图片描述

1.2.4、JDK、JER、JDK

在这里插入图片描述

  • JDK:(Java Development Kit)java开发工具包(开发所用的包)
  • JRE:(Java Runtime Envirment)java运行环境(核心类库,Object、String……)
  • JVM:(Java Virtual Machine)java虚拟机

1.2.5、JVM的整体结构

在这里插入图片描述

多个线程共享方法区和堆

栈、本地方法栈、程序计数器是每个线程独有一份的

1.2.6、java代码的执行流程

在这里插入图片描述

xx.java文件通过javac编译得到xx.class文件。当执行java命令的时候,.class文件会被ClassLoader(类加载器)装载到JVM中。装载后,通过调用字节码解释器、JIT即时编译器对代码进行解释和编译,编译过后用执行引擎进行执行,与OS硬件交互。

所用到的类库,会在class文件被加载到 JVM的同时,也被装载到类加载器中。

常用的代码会在第一次编译后被即JIT做成本地编译,不会一次次的去进行解释和即时编译。

如果每一句代码都要进行解释和编译,java就无法做到跨平台了

1.2.7、JVM的代码模型

java编译器输入的指令流基本上是一种基于栈的指令集框架,另外一种指令集架构则是基于寄存器的指令集架构

  • 基于栈式架构的特点(八位为一个单位)
    • 设计和实现更简单,适用于资源受限的系统;
    • 避开了寄存器的分配难题:使用零地址指令方式分配。
    • 指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。指令集更小,编译器容易实现。
    • 不需要硬件支持,可移植性更好,更好实现跨平台
  • 基于寄存器架构的特点(十六位为一个单位)
    • 典型的应用是x86的二进制指令集:比如传统的PC以及Android的Davlik虛拟机。
    • 指令集架构则完全依赖硬件,可移植性差
    • 性能优秀和执行更高效;
    • 花费更少的指令去完成一项操作。
    • 在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主。

由于跨平台性的设计,Java的指令都是根据栈来设计的。

栈:跨平台性、指令集小、指令多:执行性能比寄存器差

1.2.8、JVM的生命周期

  • 虚拟机的启动

    • Java虚拟机的启动是通过引导类加载器(bootstrap class loader) 创建一个初始类(initial class) 来完成的,这个类是由虚拟机的具体实现指定的。
  • 虚拟机的执行

    • 一个运行中的java虚拟机有着一个清晰的任务:执行java程序
    • 程序开始执行时他才运行,程序结束时他就停止
    • 执行一个所谓的java程序的时候,真真正正执行的是一个叫做java虚拟机的进程
  • 虚拟机的退出

    • 有如下几种情况:
      • 程序正常执行结束
      • 程序在执行过程中遇到了异常或错误而异常结束
      • 由于操作系统出现错误而导致java虚拟机进程终止
      • 某线程调用Runtime类或System类的exit方法,或Runtime类的halt方法,并且java安全管理器也允许这次exit或halt操作
      • 除此之外,JNI规范描述了用JNI Invocation API来加载或卸载java虚拟机时,java虚拟机的退出情况

2、类加载子系统

2.1、内存结构

在这里插入图片描述

当自己手写一个java虚拟机的话,主要考虑哪些结构呢?

类加载器和执行引擎

2.1.1、方法的实现

1.首先利用获取到的16进制码去汇编表中查找对应数字所代表的含义

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

查找此汇编语言的含义
在这里插入图片描述

2.查询到是哪条指令后,再将这条指令翻译过来

3.查询下一个b7–>invokespecial,重复上述操作

读到2a的时候,this压栈,之后读取下一条指令,直到b1

2.1.1.1、注意

2a是开始,b1是结束

在这里插入图片描述

这五个字节是文件的构造方法的具体实现

2a是aload-0,表示把this压栈,表示整个语句的开始

然后再编译b7……

01表示常量池里的第一项,java.lang.Object

b1表return,是整个语句的结束

2.1.2、实例2

代码1:

在这里插入图片描述

实现过程:

在这里插入图片描述

先加载父类的构造方法,之后再将自己的成员变量初始化

0:把this压栈

3:第二次压栈,把变量压入栈,后赋值

2.2、类的加载器

在这里插入图片描述

Loading:将二进制文件,加载到内存

Linking-verification:判断是否符合class文件的标准 cafe babe

Linking-perparation:给class静态变量赋默认值

Linking-resolution:class文件用到的符号引用,转换为直接能访问到的内容(内存地址)

Initializing:静态变量赋值初始值

2.2.1、类的加载过程

在这里插入图片描述

2.2.2、Loading

在这里插入图片描述

1.通过一个类的全限定名获取定义此类的二进制字节流

2.将这个字节流所代表的静态存储结构转化为元空间(方法区)的运行时数据结构

3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

2.2.3、Linking

在这里插入图片描述

2.2.3.1、初始化过程

  • Linking——Verification
    • 验证文件是否符合JVM规定
  • Linking——Preparation
    • 为静态成员变量分配内存并且设置该类变量的默认初始值
    • 这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显示初始化
    • 这里不会为实例变量分配初始化,静态变量会分配在方法区中,而实例变量是会随着变量一起分配到java堆中
  • Linking——Resolution
    • 将类、方法、属性等符号引用解析为直接引用
    • 常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
    • 解析操作往往会伴随着JVM在执行完初始化之后再执行
    • 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等……
  • Initializing
    • 调用类初始化代码,给静态成员变量赋初始值

实例1:

在这里插入图片描述

运行结果:3

运行过程:调用T.count时,首先将T.class通过app加载到内存,再经过Verification进行校验,之后再通过Preparation对T里面的静态成员变量赋默认值,此时count是空0,t是空值,再进行Resolution,之后再通过Initializing对静态成员变量赋初始值,此时count是2,之后t会被赋值new T(),对count++,因此count变为3。

实例2:

在这里插入图片描述

运行结果:2

运行过程:调用T.count时,首先将T.class通过app加载到内存,再经过Verification进行校验,之后再通过Preparation对T里面的静态成员变量赋默认值,此时t是空值,,count是0,再进行Resolution,之后再通过Initializing对静态成员变量赋初始值,此时t会被赋值new T(),对count++,此时count是1,之后count又被赋值为2,因此count为2。

2.2.3.2、new对象的过程

第一步:给new的对象申请内存,内存申请完毕后,先给成员变量赋默认值

第二步:调用构造方法,给成员变量赋初始值

静态变量是在类加载的初始化时分为两步赋值,对象中的成员变量的赋值是在类被创建的时候被分为两步

load:默认值—初始值

new:申请空间—默认值—初始值

2.2.3.3、单例模式:双重检查

在这里插入图片描述

两次检查 INSTANCE == null ,但在执行INSTANCE = new Mgr06()时,在初始化阶段将里面的成员变量赋值为0时,另一个线程来执行此方法,此时 INSTANCE != null ,并被返回,但是返回的成员变量均为初始值。

解决方法:加volatile关键词(指令重排

在这里插入图片描述

2.2.3.4、为什么要加volatile关键词?

在这里插入图片描述

二进制码:

在这里插入图片描述

1:创建内存

3:调用构造方法,给成员变量赋初始值

4:将引用值赋值给 “t”

指令重排发生时,可能先发生4再发生3,此时赋的值为默认值

2.2.4、Initializing

在这里插入图片描述

在这里插入图片描述

init:构造方法

main:main方法

clinit:类构造器方法

类的构造器(init)

  • 如果不手动的添加,都会默认的添加一个空参的构造器

类的构造器方法(clinit)

  • 初始化阶段就是执行类构造器方法<clinit> ()的过程。
  • 此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。没有静态代码块就没有上述过程。
  • 构造器方法中指令按语句在源文件中出现的顺序执行。
  • JVM会保证子类的clinit执行前,父类的clinit已经执行完毕。
  • 虚拟机必须保证一个类的clinit方法在多线程下被同步加锁。
  • <clinit> ()不同于类的构造器。(关联:构造器是虚拟机视角下的<init>())
public class ClassInitTest {
    private static int num = 1;
    private static int number;

    public ClassInitTest() {
    }
    
    public static void main(String[] args) {
        System.out.println(num);
        System.out.println(number);//如果没有声明全局变量,会报错,非法的前项引用
    }
    
    static {
        num = 2;
        number = 20;
        System.out.println(num);
        number = 10;
    }

}

在这里插入图片描述

  • 若该类具有父类,JVM会保证子类的()执行前,父类的 ()已经执行完毕。
public class ClinitTest1 {
    static class Father{
        public static int A = 1;
        static{
            A = 2;
        }
    }

    static class Son extends Father{
        public static int B = A;
    }

    public static void main(String[] args) {
        //加载Father类,其次加载Son类。
        System.out.println(Son.B);//2
    }
}

在这里插入图片描述

  • 虚拟机必须保证一个类的() 方法在多线程下被同步加锁。(static代码块只能被执行一次)
public class DeadThreadTest {
    public static void main(String[] args) {
        Runnable r = () -> {
            System.out.println(Thread.currentThread().getName() + "开始");
            DeadThread dead = new DeadThread();
            System.out.println(Thread.currentThread().getName() + "结束");
        };

        Thread t1 = new Thread(r,"线程1");
        Thread t2 = new Thread(r,"线程2");

        t1.start();
        t2.start();
    }
}

class DeadThread{
    static{
        if(true){
            System.out.println(Thread.currentThread().getName() + "初始化当前类");
            while(true){

            }
        }
    }
}

在这里插入图片描述

2.2.5、类加载器的分类

JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)自定义类加载器(User-Defind ClassLoader)

从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。

无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个,如下所示:

在这里插入图片描述

这里的四者之间的关系是包含关系,不是上下层关系,也不是父子类的继承关系。

public class ClassLoaderTest {
    public static void main(String[] args) {

        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //获取其上层:扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d

        //获取其上层:获取不到引导类加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//null

        //对于用户自定义类来说:默认使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null
        
    }
}

2.2.5.1、类加载器之间的关系

在这里插入图片描述

在创建的时候,会同时生成底层二进制码文件和一个class对象,当程序运行时,会通过这个class对象调用和访问底层的二进制码。过程是先自下而上(上图里的加载器)的查找,再自上而下的查找,当返回为null时,一定是Bootstrap加载器加载的

2.2.5.2、Bootstrap ClassLoader

  • 启动类加载器/引导类加载器

  • 这个类加载使用C/C++语言实现的,嵌套在JVM内部。

  • 它用来加载Java的核心库(/…/JAVA HOME/jre/lib/rt.jar/resources. jar或sun.boot.class.path路径下的内容) ,用于提供JVM自身需要的类

  • 并不继承自java. lang.ClassLoader,没有父加载器。

  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器。

  • 出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类。

2.2.5.3、Extension ClassLoader

  • 扩展类加载器
  • Java语言编写,由sun.misc. Launcher$ExtClassLoader实现。
  • 派生于ClassLoader类
  • 父类加载器为启动类加载器
  • 从java.ext. dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。

2.2.5.4、AppClassLoader

  • 应用程序类加载器/系统类加载器
  • java语言编写,由sun . misc. LauncherSAppClassLoader实现
  • 派生于ClassLoader类
  • 父类加载器为扩展类加载器
  • 它负责加载环境变量classpath或系统属性java.class.path 指定路径下的类库
  • 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
  • 通过ClassLoader #getSystemClassLoader ()方法可以获取到该类加载器

在这里插入图片描述

运行结果:

被bootstrap加载的jar包

在这里插入图片描述

被extension加载的内容

在这里插入图片描述

被app加载的内容
在这里插入图片描述

2.2.5.5、自定义类加载器

在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。

2.2.5.5.1、什么要自定义加载器?
  • 隔离加载类
  • 修改类的加载方法
  • 扩展加载源
  • 防止源码泄露
2.2.5.5.2、自定义加载的步骤

1.继承ClassLoader

在这里插入图片描述

2.重写模板方法findClass

负责将底层二进制编码转换为class对象的方法叫做defineClass,底层必须有这个方法

在这里插入图片描述

3.调用defineClass(将二进制码文件转换为class类对象的方法)

在这里插入图片描述

测试类

在这里插入图片描述

执行过程:

先去app找,找不到再去ext找,找不到再去bootstrap找,找不到ext再找,找不到APP再找,找不到自定义找,在c://text目录下找到了class对象

2.2.5.5.3、自定义类加载器加载自加密的class(扩展)

简单加密:二进制文件经过两次异或还是原值,在异或前再加入一个seed的二进制代码片段

自己编译的时候,先异或,再减去seed二进制代码片段

防止反编译

防止篡改

2.2.5.5.4、自定义父加载类

在这里插入图片描述

2.2.5.5.5、自定义热部署(Tomcat热部署的简单实现)

重写loadClass之前:

方法类:
在这里插入图片描述

运行结果为true,说明第一次编译后,第二次编译时通过双亲委派访问到了第一次编译的结果,因此值相同。

重写loadClass之后:
在这里插入图片描述

在这里插入图片描述

重写的方法中,第一步就是先查找需要编译的class文件,如果找到了就直接编译,如果没有找到,就交给父加载类编译。排除了原有的判断是否加载过的这一步骤,而是直接进行编译

方法类:

在这里插入图片描述

运行结果为false,说明第一次编译后,第二次编译时直接重新编译了底层的class文件。

2.2.6、ClassLoader

ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader (不包括启动类加载器)

方法名称描述
getParent()返回该类加载器的超类加载器
loadClass(String name)加载名称为name的类,返回结果为java.lang.Class类的实例
findClass(String name)查找名称为name的类,返回结果为java.lang.Class类的实例
findLoadClass(String name)查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例
defineClass(String name.byte[] b,int off.int len)把字节数组b中的内容转换为一个java类,返回结果为java.lang.Class类的实例
resolveClass(Class<?> c)链接一个指定的java类
获取classloader的途径
  • 方式一:获取当前类的ClassLoader
    clazz. getClassLoader ()

  • 方式二:获取当前线程上下文的ClassLoader
    Thread. currentThread() .getContextClassLoader ()

  • 方式三:获取系统的ClassLoader
    ClassLoader.getSystemClassLoader ()

  • 方式四:获取调用者的ClassLoader
    DriverManager . getCallerClassLoader ()

2.2.7、双亲委派

2.2.7.1、机制

Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。

2.2.7.1.1、工作原理
  1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器执行;
  2. 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达项层的启动类加载器;
  3. 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

在这里插入图片描述

是一个孩子向父亲的方向,然后父亲向孩子方向的双亲委派过程

2.2.7.1.2、父加载类

父加载类不是“类加载器的加载器”,也不是“类加载器的父类加载器”

在这里插入图片描述

2.2.7.1.3、为什么要使用双亲委派?
  • 保护程序安全,防止核心API被随意篡改
    • 主要原因是为了安全,假设当有人自拟了一个java.lang.String类,直接上传到类库中将原有的类库覆盖,之后将自己的代码上传到互联网上,当用户设置账号密码时,就会用到这个类,万一这个类中有一个功能可以将该用户的所有账户信息发送给开发者,那么个人信息将暴露。而双亲委派就不会出现此类情况,当排查到bootstarp时,发现已经执行过了,就不会覆盖掉原有的类。
  • 避免类的重复加载
    • 次要原因可以避免资源的浪费,在双亲委派的过程中,如果发现已经加载过了就不会再次进行加载。
2.2.7.1.4、沙箱安全机制

自定义String类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java\lang\String.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的String类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。

2.2.7.1.5、如何打破双亲委派机制?
  1. 重写loadClass()
  2. 何时打破过?
    1. JDK1.2.之前,自定义ClassLoader都必须重写loadClass()
    2. ThreadContextClassLoader可以实现基本类调用实现类代码,通过thread.setContextClassLoader指定
    3. 热启动、热部署
      1. osgi tomcat都有自己的模块指定classloader(可以加载同一类库的不同版本)

2.2.7.2、编译器(扩展)

2.2.7.2.1、混合模式

解释器 —— bytecode intepreter

JIT ——Just In-Time compiler(把java代码编译成本地代码执行)

  • 混合模式
    • ​ 混合使用解释器+热点代码编译
    • ​ 起始阶段采用解释执行
    • ​ 热点代码检测
      • 多次被调用的方法(方法计数器:监测方法执行频率)
      • 多次被调用的循环(循环计数器:检测循环执行频率)
      • 进行编译
2.2.7.2.2、热点代码编译

当一段代码被大量的重复使用时,解释器不停地解释相同的代码,有损效率,为了提升效率,热点代码编译将这段代码编译为本地代码,当下次再需要这段代码时,就不用再执行解释器解释了,直接执行本地代码,这样就提高了编译效率

2.2.7.2.2.1、检测热点代码

-XX:CompileThreshold = 10000

2.2.7.2.3、编译模式的选择
  • -Xmixed默认为混合模式 开始解释执行,启动速度较快对热点代码实行检测和编译
  • -Xint 使用解释模式,启动很快执行稍慢
  • -Xcomp使用纯编译模式,执行很快,启动很慢(指需要大量类进行编译时)

在这里插入图片描述

2.2.7.3、懒加载(扩展)

什么时候需要什么时候加载

  • 严格讲应该叫lazy lnitializing
  • JVM规范并没有规定何时加载
  • 但是严格规定了什么时候必须初始化
    • new getstatic putstatic invokestatic指令,访问final变量除外
    • java.lang.reflect对类进行反射调用时
    • 初始化子类的时候,父类首先初始化
    • 虚拟机启动时,被执行的主类必须初始化
    • 动态语言支持java.lang.invoke.MethodHandle解析的结果为REF getstatic REF putstatic REF invokestatic的方法句柄时,该类必须初始化访问内部类时,需要外部类/内部类的名字

在这里插入图片描述

2.2.8、类的主动使用和被动加载

2.2.8.1、判断两个class对象是否为同一个类的条件

在JVM中表示两个class对象是否为同一个类存在两个必要条件:

  • 类的完整类名必须一致,包括包名。
  • 加载这个类的ClassLoader (指ClassLoader实例对象)必须相同。

换句话说,在JVM中,即使这两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的。

2.2.8.2、对类加载器的引用——动态链接

JVM必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会**将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。**当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的。

2.2.8.3、主动使用/被动使用

  • 主动使用,又分为七种情况:
    • 创建类的实例
    • 访问某个类或接口的静态变量,或者对该静态变量赋值
    • 调用类的静态方法
    • 反射(比如: Class . forName (“com. atguigu . Test”) )
    • 初始化一个类的子类
    • Java虚拟机启动时被标明为启动类的类
    • JDK 7开始提供的动态语言支持:
    • java. lang. invoke . MethodHandle实例的解析结果REF getStatic、REF putStatic、 REF invokeStatic句柄对应的类没有初始化,则初始化

除了以上七种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化。

3、字节码与类的加载器

3.1、Class文件结构

3.1.1、概述

3.1.1.1、字节码文件的跨平台性

  1. java语言的跨平台性

    • java源代码编译成字节码文件后,如果在不同的平台上运行时,不用再此编译。
    • 这点不再那么吸引人了,大多数其他语言也可以做到
    • 跨平台已经快成为一门语言必选的特性
  2. java虚拟机,跨语言的平台

    • java虚拟机不和包括java在内的任何语言绑定,他只和Class文件这种特定的二进制文件格式所关联
    • 所有的字节码文件必须遵守java虚拟机规范
  3. 想要让规格程序正确地运行在JVM中,java虚拟机就必须要被编译为符合JVM规范的字节码

    • 前端编译器的主要任务就是负责将符合java语法规范的java代码转换为符合JVM规范的字节码文件
    • javac是一种能够将java原码编译为字节码的前端编译器
    • Javac编译器在将Java源码编译为一个有效的字节码文件过程中经历了4个步骤,分别是词法解析、语法解析、语义解析以及生成字节码。
      在这里插入图片描述
  4. Oracle的JDK软件包括两部分内容:

    • 一部分是将Java源代码编译成Java虚拟机的指令集的编译器;
    • 另一部分是用于实现Java虚拟机的运行时环境。

3.1.1.2、java的前端编译器

在这里插入图片描述

Java源代码的编译结果是字节码,那么肯定需要有种编译器能够将Java源码编译为字节码,承担这个重要责任的就是配置在path环境变量中的javac编译器

  • javac是一种能够将Java源码编译为字节码的前端编译器
  • Eclipse中内置的ECJ(Eclipse Compiler for Java)编译器是一种增量式编译器

前端编译器并不会直接涉及编译优化等方面的技术,而是将这些具体优化细节移交给HotSpot的JIT编译器负责。

3.1.1.3、透过字节码指令看代码细节

  • 类文件结构有几个部分?

    • 魔数
    • 版本号
    • 常量池
    • 访问表示
    • 类索引、父类索引、接口索引
    • 字段表
    • 方法表
    • 属性表
  • 知道字节码吗?字节码都有哪些?Integerx=5;inty=5;比较x==y都经过哪些步骤?

    • 通过Jclasslib观察字节码文件

    • public class IntegerTest {
          public static void main(String[] args) {
      
              Integer x = 5;
              int y = 4;
              System.out.println(x == y);
      
          }
      }
      

在这里插入图片描述

3.1.1.3.1、实例
/*
成员变量(非静态的)的赋值过程: 
	① 默认初始化
    ② 显式初始化 /代码块中初始化 
    ③ 构造器中初始化
    ④ 有了对象之后,可以“对象.属性”或"对象.方法"的方式对成员变量进行赋值。
 */
class Father {
    int x = 10;

    public Father() {
        this.print();
        x = 20;
    }
    
    public void print() {
        System.out.println("Father.x = " + x);
    }

}

class Son extends Father {
    int x = 30;

    //    float x = 30.1F;
    public Son() {
        this.print();
        x = 40;
    }
    
    public void print() {
        System.out.println("Son.x = " + x);
    }

}

public class SonTest {
    public static void main(String[] args) {
        Father f = new Son();
        System.out.println(f.x);
    }
}

运行结果

在这里插入图片描述

字节码解析
在这里插入图片描述

3.1.2、Class文件:虚拟机的基石

  • 字节码文件
    • 源代码经过编译器去编译后便会生成一个字节码文件,他的内容就是JVM指令,而不是想C/C++一样生成机器码。
  • 字节码指令
    • Java虚拟机的指令是由一个字节码长度、代表着某种特定操作含义的操作码以及跟随其后的零至多个代表此操作所需参数的操作数所构成。部分指令并不包含操作数,只有操作码
    • 操作数+操作码
  • 解读方式
    • NotePade++和插件HEX-Editor/Binary Viewer
    • javap指令
    • JClasslib

3.1.2.1、Class文件本质

一组以8位字节为基础单位的二进制流

3.1.2.2、Class文件格式

  • 字节码文件中的字节顺序、数量都是被严格限定的,不能随意修改
  • 采用一种类似于C语言的方式进行数据存储,这种结构中只有两种数据类型:无符号数
  • 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别表示一个字节、两个字节、四个字节和八个字节的无符号数,可以用来描述数字索引引用数量值或者按照UTF-8编码构成字符串值
  • 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“info"结尾。表用于描述有层次关系的复合结构的数据,整个Class 文件本质上就是一张表。 由于表没有固定长度,所以通常会在其前面加上个数说明

3.1.2.3、Class文件的数据类型

数据类型定义说明
无符号数无符号数可以用来描述数字、索引引用、数量值或按照utf-8编码构成的字符串值。其中无符号数属于基本的数据类型。 以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节
表是由多个无符号数或其他表构成的复合数据结构。所有的表都以“_info”结尾。 由于表没有固定长度,所以通常会在其前面加上个数说明。

3.1.3、Class文件结构

Class文件的结构并不是一成不变的,随着Java虚拟机的不断发展,总是不可避免地会对Class文件结构做出一些调整,但是其基本结构和框架是非常稳定的。

结构如下:

类型名称说明长度数量
u4magic魔数,识别Class文件格式4个字节1
u2minor_version副版本号(小版本)2个字节1
u2major_version主版本号(大版本)2个字节1
u2constant_pool_count常量池计数器2个字节1
cp_infoconstant_pool常量池表n个字节constant_pool_count-1
u2access_flags访问标识2个字节1
u2this_class类索引2个字节1
u2super_class父类索引2个字节1
u2interfaces_count接口计数器2个字节1
u2interfaces接口索引集合2个字节interfaces_count
u2fields_count字段计数器2个字节1
field_infofields字段表n个字节fields_count
u2methods_count方法计数器2个字节1
method_infomethods方法表n个字节methods_count
u2attributes_count属性计数器2个字节1
attribute_infoattributes属性表n个字节attributes_count

3.1.3.1、实例

public class Demo {
    private int num = 1;

    public int add(){
        num = num + 2;
        return num;
    }

}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/173985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ARM NandFlash 介绍

一、NandFlash 的接口 1、Nand 的型号与命名 (1) Nand 的型号命名都有含义&#xff0c;就拿 K9F2G08 来示例分析一下&#xff1a;K9F 表示是三星公司的 NandFlash 系列。2G 表示 Nand 的大小是 2Gbit&#xff08;256MB&#xff09;。08 表示 Nand 是 8 位的&#xff08; 8 位…

员工入职管理系统|员工管理系统|基于SpringBoot+Vue的企业新员工入职系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

SICTF2023 WP

前言 新年前的最后一场比赛&#xff0c;感谢shenghuo2师傅提供的misc和密码的wp&#xff0c;把misc和密码ak了&#xff0c;太强了 web 兔年大吉 源码 <?php highlight_file(__FILE__); error_reporting(0);class Happy{private $cmd;private $content;public function _…

Registration Center

CAP●一致性(Consistency)&#xff1a;所有节点在同一时间具有相同的数据&#xff1b;●可用性(Availability) &#xff1a;保证每个请求不管成功或者失败都有响应&#xff1b;某个系统的某个节点挂了&#xff0c;但是并不影响系统的接受或者发出请求。●分隔容忍(Partition to…

python循环语句

Python循环语句 文章目录Python循环语句一、实验目的二、实验原理三、实验环境四、实验内容五、实验步骤1.While循环结构2.While无限循环3.For循环语法4.break语句和continue语句一、实验目的 掌握循环结构的语法 二、实验原理 Python中的循环语句有 for 和 while。 Python…

AcWing蓝桥杯AB组辅导课07、贪心

文章目录前言一、贪心模板题例题1&#xff1a;AcWing 104. 货仓选址&#xff08;贪心&#xff0c;简单&#xff0c;算法竞赛进阶指南&#xff09;分析题解&#xff1a;贪心思路例题例题1&#xff1a;AcWing 1055. 股票买卖 II&#xff08;贪心、状态机&#xff0c;简单&#xf…

[ESP][驱动]GT911 ESP系列驱动

GT911ForESP GT911在ESP系列上的驱动&#xff0c;基于IDF5.0&#xff0c;ESP32S3编写 本库使用面向对象思想编写&#xff0c;可创建多设备多实例 Github&#xff0c;Gitee同步更新&#xff0c;Gitee仅作为下载仓库&#xff0c;提交Issue和Pull request请到Github Github: h…

具体芯片的I2C_Adapter驱动分析

具体芯片的I2C_Adapter驱动分析 文章目录具体芯片的I2C_Adapter驱动分析参考资料&#xff1a;一、 I2C控制器内部结构1.1 通用的简化结构1.2 IMX6ULL的I2C控制器内部结构二、 I2C控制器操作方法三、 分析代码3.1 设备树3.2 驱动程序分析致谢参考资料&#xff1a; Linux内核真正…

03_筛选标记2.0版和3.0版FIND及ColorIndex

文章目录2.0版工作簿筛选标记筛选sheet标记取消筛选标记3.0版ColorIndex 下标代码特别鸣谢,大佬的分享FIND方法的使用2.0版 工作簿筛选标记 Option Explicit Sub 自动筛选()Dim Town As StringDim wsh As WorksheetCall 初始化 初始化表格状态Town InputBox("请输入街…

SLAM笔记——turtlebot传感器ekf实验实验

这里写目录标题实验内容实验准备msg数据类型给uwb和odom增加噪声robot_pose_ekf发布路径实验结果实验内容 本实验将在gazebo仿真环境中使用ekf进行传感器数据融合。本文使用turtlebot3进行实验&#xff0c;turtlebot本身会发布odom和imu。imu的误差可以在urdf文件中进行调整&a…

追梦之旅【数据结构篇】——对数据结构的认知 + 初识时间复杂度和空间复杂度~

详解C语言函数模块知识(下篇&#xff09;&#x1f60e;前言&#x1f64c;浅谈数据结构~&#x1f64c;1、什么是数据结构&#xff1f;(ˇˍˇ) 想&#xff5e;2、什么是算法&#xff1f;ˇˍˇ) 想&#xff5e;3、数据结构和算法的重要性&#x1f60a;4、如何才能学好数据结构呢…

初识 NodeJS(基于 Chrome V8 引擎的 JavaScript 运行时环境)

初识 NodeJS&#xff08;基于 Chrome V8 引擎的 JavaScript 运行时环境&#xff09;参考描述NodeJSNodeJS 可以做什么&#xff1f;特点用武之地获取检测运行JavaScript 运行时环境JavsScript 引擎浏览器中的 JavaScript 运行时环境Chrome 浏览器运行时环境NodeJS 中的 JavaScri…

【着色器实现海面效果_菲尼尔/Unlit/Transparent】

1.水体颜色 2.反射,水面波纹流动 3.折射、水底、水底透明度和折射 4.焦散,在水底接近岸边的水域 5.岸边泡沫,水花接近岸边的泡沫 6.水体运动,顶点动画 用灯光模式是Light Model :Unilt Render Type:Transparent 获取水面深度 利用这个节点,从深度图获取世界空间的位…

如何做流程图?这几个实用的制作流程图方法分享给你

说到流程图的制作&#xff0c;相信大家都并不陌生&#xff0c;在日常的工作和学习中&#xff0c;我们都会根据需求接触到各种各样的流程图&#xff0c;有时还要自己动手绘制流程图并使用&#xff0c;但你是否会因为不会绘制流程图而感到苦恼呢&#xff1f;没关系&#xff0c;今…

vue中利用ref实现更灵活的子向父传值

目录前言一&#xff0c;基础代码二&#xff0c;层次递进的讲解用法2.1 给子组件设置ref2.2 自定义事件2.3 给子组件设置一个自定义事件三&#xff0c;灵活性四&#xff0c;注意后记前言 目前我们熟知的子向父传值有两种方式&#xff1a; 一种是在父组件中定义函数&#xff0c;…

【AI】Windows配置GPU Cuda驱动和Pytorch框架

目录 1、Cuda驱动安装 1.1 查看显卡硬件 1.2 查看cuda版本 2、Annaconda python环境准备 2.1 创建pytorch_gpu 2.2 查看python版本 3、Pytorch和torchVsion软件安装 4、验证测试 在进行AI项目开发的时候&#xff0c;经常要在GPU环境中运行代码&#xff0c;对于没有配置…

动手深度学习-pytorch线性代数

标量简单操作长度向量简单操作长度其他操作矩阵简单操作乘法&#xff08;矩阵*向量&#xff09;乘法&#xff08;矩阵*矩阵&#xff09;范数取决于如何衡量b和c的长度常见范数矩阵范数&#xff1a;最小的满足的上面公式的值Frobenius范数特殊矩阵对称和反对称正定正交矩阵置换矩…

Solidity 中的数学(第 4 部分:复利)

本文是关于在 Solidity 中进行数学运算的系列文章中的第四篇。这次的主题是&#xff1a;复利。 介绍 在我们之前的文章中&#xff0c;我们讨论了百分比以及它们是如何在 Solidity 中计算的。在金融数学中&#xff0c;百分比通常与贷款和存款支付的利息有关。在每个时间段结束时…

深度学习入门基础CNN系列——批归一化(Batch Normalization)和丢弃法(dropout)

想要入门深度学习的小伙伴们&#xff0c;可以了解下本博主的其它基础内容&#xff1a; &#x1f3e0;我的个人主页 &#x1f680;深度学习入门基础CNN系列——卷积计算 &#x1f31f;深度学习入门基础CNN系列——填充&#xff08;padding&#xff09;与步幅&#xff08;stride&…

CSS 搜索框

CSS 搜索框 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>搜索框</title><style type"text/css">* {margin: 0;padding: 0;}.search-container {margin: 50px;display: flex;width: 500px;height:…