JVM基础篇一

1.初识JVM

什么是JVM
JVM本质是一个运行在计算机上的程序,他的职责是运行java字节码文件。
在这里插入图片描述

JVM的功能

在这里插入图片描述

JVM功能-即时编译

  • Java语言如果不做任何优化,性能不如C、C++等语言在这里插入图片描述
  • Java需要实时解释,主要是为了支持跨平台特性。
    在这里插入图片描述
    在这里插入图片描述

2.字节码文件详情

2.1java虚拟机的组成

在这里插入图片描述

2.2字节码文件的组成

一般信息
在这里插入图片描述
常量池
在这里插入图片描述

  • 字段
    在这里插入图片描述
  • 方法
    在这里插入图片描述
  • 属性
    在这里插入图片描述
    在这里插入图片描述
字节码文件的组成部分-Magic魔数
  • 文件时无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容。
  • 软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。
  • Java字节码文件中,将文件头称为magic魔数
    在这里插入图片描述
  • 字节码文件的组成部分-祝福版本号6
    在这里插入图片描述
  • 主版本号不兼容导致的错误
    需求:
    解决以下由于主 版本号不兼容的错误
    在这里插入图片描述
    两种方案:
    1.升级JDK版本(容易引发其他的兼容问题,并且需要大量的测试)
    2.将第三方依赖的版本号降低或者更换依赖,以满足JDK版本的要求
字节码文件的组成-基础信息

在这里插入图片描述

字节码文件的组成部分-常量池
  • 字节码文件中常量池的作用:避免相同的内容重复定义,节省空间
    在这里插入图片描述
  • 常量池中的数据都有一个编号,编号从1开始。在字段或字节码指令中通过编号可以快速的找到对应的数据
  • 字节码指令中通过编号引用到常量池的过程称之为符号引用
    在这里插入图片描述
字节码文件的组成部分-方法
  • 字节码中的方法区域就是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性汇中。

在这里插入图片描述

  • 操作数栈是临时存放数据的地方,局部变量表是存放方法中的局部变量的位置.
    在这里插入图片描述
    在这里插入图片描述
字节码常用工具:javap -v命令
  • javap是JDK自带的反编译工具,可以通过控制台查看字节码文件的内容。适合在服务器上查看字节码文件内容。
  • 直接输入javap查看所有参数
  • 输入javap -v 字节码文件名称 查看具体的字节码信息。(如果jar包需要先使用jar -xvf命令解压)
字节码常用工具:jclasslib插件
  • jclasslib也有Idea插件版本,建议开发时使用Idea插件版本,可以在代码编译之后实时看到字节码文件内容
字节码常用工具:阿里arthas
  • Arthas是一款线上监控诊断产品,通过全局视角实时查看应用load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率。
    在这里插入图片描述

2.3类的生命周期

生命周期概述

在这里插入图片描述

加载阶段
  • 1、加载阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息。
    程序员可以使用Java代码拓展的不同渠道
    在这里插入图片描述
  • 2、类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到方法区中。
    在这里插入图片描述
  • 3、类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到内存的方法区中。
    生成一个InstanceKlass对象,保存类的所有信息,里面还包括实现特定功能比如多态的信息
    在这里插入图片描述
  • 4、同时,Java虚拟机还会在堆中生成一份与方法区中数据类似的java.lang.Class对象。
    作用是在Java代码中去获取类的信息以及存储静态字段的数据(JDK8之后)
    在这里插入图片描述
  • 对于开发者来说,只需要访问堆中的Class对象而不需要访问方法区中所有的信息。
    这样Java虚拟机就能很好地控制开发者访问数据的范围。
    在这里插入图片描述
连接阶段

在这里插入图片描述
验证

  • 连接阶段的第一个环节是验证,验证的主要目的是检测Java字节码文件是否遵守了《Java虚拟机规范》中的约束。这个阶段一般不需要程序要的参与.
    准备
  • 准备阶段为静态变量分配内存并设置初始值
  • 注意:本章涉及到的内存结构只讨论JDK8及之后的版本,9之前的版本后续章节详述。
    在这里插入图片描述
  • 准备阶段只会给静态变量赋初始值,而每一种基本数据类型和应用数据类型都有其初始值
    在这里插入图片描述
  • final修饰的基本数据类型的静态变量,准备阶段直接会将代码中的值进行赋值。
    在这里插入图片描述
    解析
  • 解析阶段主要是将常量池中的符号引用替换为直接引用
  • 符号引用就是在字节码文件中使用编号来访问常量池中的内容
初始化阶段
  • 初始化阶段会执行静态代码块中的代码,并为静态变量赋值。
  • 初始化阶段会执行字节码文件中clinit部分的字节码指令。
    在这里插入图片描述
    clinit方法中的执行顺序与Java中编写顺序是一致的。
  • 以下几种方式会导致类的初始化:
    1.访问一个类的静态变量或者静态方法,注意变量是final修饰的并且等号右边是常量不会触发初始化。
    2.调用Class.forName(String className)。
    3.new一个该类的对象时。
    4.执行Main方法的当前类。
    在这里插入图片描述
  • clinit指令在特定情况下不会出现,比如:如下几种情况:
    1.无静态代码块且无静态变量赋值语句。
    2.有静态变量的声明,但是没有赋值语句。
    3.静态变量的定义使用final关键字,这类变量会在准备阶段直接进行初始化。
    在这里插入图片描述
  • 直接访问父类的静态变量,不会触发子类的初始化
  • 子类的初始化client调用之前,会先调用父类的clinit初始化方法。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

2.4类加载器

  • 类加载器是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。
    类加载器只参与加载过程中的字节码获取并加载到内存这一部分.
    在这里插入图片描述
类加载器的分类

类加载器分为两类,一类是Java代码中实现的,一类是Java虚拟机底层源代码实现的。
在这里插入图片描述

  • 类加载器的设计JDK8和8之后的版本差别比较大,JDK8之前的版本中默认的类加载器有如下几种:
    在这里插入图片描述
  • 启动加载器是由Hotspot虚拟机提供的、使用C++编写的类加载器。
  • 默认加载Java安装目录/jre/lib下的类文件,比如rt.jar,tools.jar,resources.jar等。
    在这里插入图片描述
    在这里插入图片描述
    Java中的默认类加载器
  • 扩展类加载器和应用程序类加载器都是JDK中提供的、使用Java编写的类加载器。
  • 它们的源码都位于sun.misc.Launcher中,是一个静态内部类。继承自URLClassLoader。具备通过目录或者指定jar包将字节码文件加载到内存中。
    在这里插入图片描述
    扩展类加载器
  • 扩展类加载器是JDK中提供的、使用Java编写的类加载器。
  • 默认加载Java安装目录/jre/lib/ext下的类文件
    在这里插入图片描述在这里插入图片描述
双亲委派机制

由于Java虚拟机中有多个类加载器,双亲委派机制的核心是解决一个类到底由谁加载的问题。
在这里插入图片描述
双亲委派机制有什么作用?
1.保证类加载的安全性
通过双亲委派机制避免恶意代码替换JDK中的核心类库,比如java.lang.String确保核心类库的完整性和安全性。
2.避免重复加载
双亲委派机制可以避免同一个类被多次加载.

什么是双亲委派机制?
双亲委派机制指的是:当一个类加载器接收到加载类的任务时,会自低向上查找是否加载过,再由顶向下进行加载。

  • 向上查找如果已经加载过,就直接返回Class对象,加载过程结束,这样就能避免一个类重复加载.
    在这里插入图片描述
  • 如果所有的父类加载器都没加载过该类,则由当前类加载器自己尝试加载。所以看上去是自顶向下尝试加载。
    在这里插入图片描述
  • 第二次再去加载相同的类,仍然会向上进行委派,如果某个类加载器加载过就会直接返回。
    在这里插入图片描述
    双亲委派机制指的是:自低向上查找是否加载过,再由顶向下进行加载。
    向下委派加载起到了一个加载优先级的作用

问题
1.重复的类
如果一个类重复出现在三个类加载器的加载位置,应该由谁来加载?
启动类加载器加载,根据双亲委派机制,它的优先级是最高的
2.String类能覆盖吗
在自己的项目中去创建一个java.lang.String类,会被加载吗?
不能,会返回启动类加载器加载爱rt.ja包中的
String类

在Java如何使用代码的方式去主动加载一个类?
方式一:使用Class.forName方法,使用当前类的类加载器去加载指定的类
方式二:获取到类加载器,通过加载器的loadClass方法指定某个类加载器加载。
例如:
在这里插入图片描述

  • 应用程序类加载器的parent父类加载器是扩展类加载器,而扩展类的加载器的parent是空,但是在代码逻辑上,扩展类加载器依然会把启动类加载器当成父类加载器处理
  • 启动类加载器使用C++编写,没有父类加载器
    在这里插入图片描述
面试:

类的双亲委派机制是什么?
1、当一个类加载器去加载某个类的时候,会自低向上查找是否加载过,如果有加载过就直接返回,如果一直到最顶层的类加载器都没有加载,再由顶向下进行加载。
2、应用程序类加载器的父类加载器是扩展加载器,扩展类加载器的父类加载器是启动类加载器。
3、双亲委派机制的好处有两点:第一是避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。第二是避免一个类重复地被加载。

打破双亲委派机制

打破双亲委派机制的三种方式(第三种了解)
在这里插入图片描述
自定义类加载器

  • 一个Tomcat程序中是可以运行多个Web应用的,如果这两个应用中出现了相同限定名的类,比如Servlet类,Tomcat要保证这两个类都能加载并且它们应该是不同的类。

  • 如果不打破双亲委派机制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的MyServlet类就无法被加载了。
    在这里插入图片描述- Tomcat使用了自定义类加载器来实现应用之间类的隔离。
    每个应用都会有一个独立的类加载器加载对应的类。
    在这里插入图片描述

  • 先来分析ClassLoader的原理,ClassLoader中包含了4个核心方法

  • 双亲委派机制的核心代码就位于loadClass方法中。
    在这里插入图片描述
    双亲委派机制核心代码

  • 阅读双亲委派机制的核心代码,分析如何通过自定义的类加载器打破双亲委派

  • 打破双亲委派机制的核心就是将下边的一段代码重新实现
    在这里插入图片描述
    问题:自定义类加载器父类怎么是AppClassLoader呢?
    在这里插入图片描述

  • 以JDK8为例,ClassLoader类中提供了构造方法设置parent的内容:
    在这里插入图片描述

  • 这个构造方法由另一个构造方法调用,其中父类加载器由getSystemClassLoader方法设置,该方法返回的是AppClassLoader
    在这里插入图片描述
    两个自定义类加载器加载相同限定名的类,不会冲突吗?

  • 不会冲突,在同一个Java虚拟机中,只用相同类加载器+相同的类限定名会被认为是同一个类。

  • 在Arthas中使用sc -d类名的方式查看具体的情况

  • 正确的去实现一个自定义类加载器的方式是重写findClass方法,这样不会破坏双亲委派机制。
    在这里插入图片描述
    线程上下文加载器-JDBC案例

  • JDBC中使用了DriverManager来管理项目中引入不同数据库的驱动,比如mysql驱动、oracle驱动。
    在这里插入图片描述

  • DriverManager类位于rt.jar包中,由启动类加载器加载。
    在这里插入图片描述

  • 依赖中的mysql驱动对应的类,由应用程序类加载器来加载。
    在这里插入图片描述

  • DriberManager属与rt.jar是启动类加载器加载的。而用户jar包中的驱动需要由应用类加载器加载,这就违反了双亲委派机制。
    在这里插入图片描述
    问题:DriverManager怎么知道jar包中要加载的驱动在哪?
    **JDBC案例之SPI机制

  • spi(Service Provider Interface),是JDK内置的一种服务提供发现机制。

  • spi的工作原理:
    1.在ClassPath路径下的META-INF/services文**件夹中,以接口的全限定名来命名文件名,对应的文件里面写该接口的实现。
    2.使用ServiceLoader加载实现类。
    在这里插入图片描述

  • DriverManage使用SPI机制,最终加载jar包中对应的驱动类.
    在这里插入图片描述
    问题:SPI是如何获取到应用程序类加载器的?

  • SPI中使用了线程上下文中保存的类加载器进行类的加载,这个类加载器一般是应用程序类加载器。
    在这里插入图片描述
    1、启动类加载器加载DriverManager
    2、在初始化DriverManager时,通过SPI机制加载jar包中的mysql驱动。
    3、SPI中利用了线程上下文类加载器(应用程序加载器)去加载类并创建对象
    这种由启动类加载器加载的类,委派应用程序类加载器去加载类的方式,打破了双亲委派机制。
    在这里插入图片描述
    问题:JDBC案例真的打破了双亲委派机制吗?
    在这里插入图片描述
    第三种方法:OSGI模块化

  • 历史上,OSGI模块化框架。它存在同级之间的类加载器的委托加载。OSGI还使用类加载器实现了热部署的功能。

  • 热部署指的是在服务不停止的情况下,动态地更新字节码文件到内存中。

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

JDK9之后的类加载器
  • 由于JDK9引入了module概念,类加载器在设计上发生了很多变化。
    1.启动类加载器使用Java编写,位于jdk.internal.loader.ClassLoaders类中。
    Java中的BootClassLoader继承自BuiltinClassLoader实现从模块中找到要加载的字节码资源文件。
    启动类加载器依然无法通过java代码获取到,返回的仍然是null,保持了统一。
    在这里插入图片描述
    2、扩展类加载器被替换成了平台类加载器
    平台类加载器遵循模块化方式加载字节码文件,所以继承关系从URLClassLoader变成了BuiltinClassLoader,BuiltinClassLoader实现了从模块中加载字节码文件。平台类加载器的存在更多的是为了与老版本的设计方案兼容,自身没有特殊的逻辑。
    在这里插入图片描述
面试:

1.类加载器的作用是什么?
类加载器(ClassLoader)负责在类加载过程中的字节码获取并加载到内存这一部分。通过加载字节码数据放入内存转换成byte[],接下来调用虚拟机底层方法将byte[]转换成方法区和堆中的数据。
2.有几种类加载器?
①启动类加载器(Bootstrap ClassLoader)加载核心类
②扩展类加载器(Extension ClassLoader)加载扩展类
③应用程序类加载器(Application ClassLoader)加载应用classpath中的类
④自定义类加载器,重写findClass方法
JDK9及之后扩展类加载器(Extension ClassLoader)变成了平台类加载器(Platform ClassLoader).
3.什么是双亲委派机制?
每个java实现的类加载器中保存了一个成员变量叫“父”(parent)类加载器。
自底向上查找是否加载过,再由顶向下进行加载。避免了核心类被应用程序重写并覆盖的问题,提升了安全性。
在这里插入图片描述
4.怎么打破双亲委派机制?
①重写loadClass方法,不再实现双亲委派机制
②JNDI、JDBC、JCE、JAXB和JBI等框架使用了SPI机制+线程上下文类加载器
③OSGI实现了一套类加载机制,允许同级类加载器之间的相互调用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值