JVM-1

发布时间 2023-10-08 12:38:49作者: 新至所向

JVM-1

image-20231007152024072

你是否也遇到过这些问题?

  • 运行着的线上系统突然卡死,系统无法访问,甚至直接oOM !
  • 想解决线上JVM GC问题,但却无从下手。
  • 新项目上线,对各种JVM参数设置一脸茫然,直接默认吧,然后就JJ了
  • 每次面试之前都要重新背一遍JVM的一些原理概念性的东西,然而面试官却经常问你在实际项目中如何调优JVM参数,如何解决Gc、oOM等问题,
    一脸懵逼。

image-20231007105618519

如果我们把核心类库的 API 比做数学公式的话,那么Java虚拟机的知识就好比公式的推导过程。

image-20231007110602349

类似与:

Java为自动挡

c++为手动挡

image-20231007112242660

多语言混合编程

Java平台上的多语言混合编程正成为主流,通过特定领域的语言去解决特定领域的问题是当前软件开发应对日趋复杂的项目需求的一个方向。

·作用

Java虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台上的机器指令执行。每一条Java指令,Java虚拟机规范中都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放在哪里

·特点

  • 一次编译,到处运行
  • 自动内存管理
  • 自动垃圾回收功能

image-20231007114449026

JVM的整体结构

image-20231007114651600

JVM的架构模型

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

具体来说:这两种架构之间的区别:

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

 F:\科技为你\Java\中期学习1\code\middleJava\out\production\JVM\demo01\work01> cd Day010700
PS F:\科技为你\Java\中期学习1\code\middleJava\out\production\JVM\demo01\work01> javap -v Day10700.class

JVM的架构模型

总结:
由于跨平台性的设计,Java的指令都是根据栈来设计的。不同平台cPu架构不同,所以不能设计为基于寄存器的。优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。

时至今日,尽管嵌入式平台已经不是Java程序的主流运行平台了( 该是HotSpotVM的宿主环境已经不局限于嵌入式平台了),那么为什么不将架构更换为基于寄存器的架构呢?

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

9- JVM的生命周期

虚拟机的启动

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

虚拟机的执行

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

虚拟机的退出

有如下的几种情况:

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

JVM发展历程

image-20231007124853920

image-20231007125413173

HotSpot VM

  • 从服务器、桌面到移动端、嵌入式都有应用。
  • 名称中的Hotspot指的就是它的热点代码探测技术。
    • 通过计数器找到最具编译价值代码,触发即时编译或栈上替换
    • 通过编译器与解释器协同工作,在最优化的程序响应时间与最佳执行性能中取得平衡

JRockit

image-20231007130051345


image-20231007151824837

类的加载过程

image-20231007153328528

加载:

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

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

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

链接

image-20231007153807092

初始化:

image-20231007160133900

类加载器的分类

  • JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。
  • 从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。
  • 无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个,如下所示:

image-20231007161538540

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

//获取其上层:扩展类加载器
classLoader extclassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader); //sun.misc.Launcher$ExtCLassLoader@1540e19d
//获取其上层:获取不到引导类加载器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();System.out.println(bootstrapClassLoader);l/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
I

虚拟机自带的加载器

·启动类加载器(引导类加载器,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等开头的类
扩展类加载器(Extension classLoader)
  • Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
  • 派生于ClassLoader类
  • 父类加载器为启动类加载器
  • 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
应用程序类加载器(系统类加载器,AppclassLoader)
  • java语言编写,由sun.misc.Launcher$AppclassLoader实现
  • 派生于classLoader类
  • 父类加载器为扩展类加载器
  • 它负责加载环境变量classpath或系统属性java.class.path指
    定路径下的类库
  • 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
  • 通过classLoader#getSystemClassLoader ()方法可以获取到该
    类加载器
用户自定义类加载器
  • 在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。
  • 为什么要自定义类加载器?
    • 隔离加载类
    • 修改类加载的方式
    • 扩展加载源
    • 防止源码泄漏

5-双亲委派机制

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

image-20231008120942051

优势

  • 避免类的重复加载
  • 保护程序安全,防止核心API被随意篡改
    • 自定义类:java . lang.string
    • 自定义类:java.lang. shkStart

沙箱安全机制

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


对类加载器的引用

在JVM中表示两个class对象是否为同一个类存在两个必要条件:
  • 类的完整类名必须一致,包括包名。
  • 加载这个类的classLoader(指classLoader实例对象)必须相同。

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

类的主动使用和被动使用

Java程序对类的使用方式分为:主动使用和被动使用。

·主动使用,又分为七种情况:

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

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