重点知识学习(4.1)--[JVM概述,类加载机制]

Posted 小智RE0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重点知识学习(4.1)--[JVM概述,类加载机制]相关的知识,希望对你有一定的参考价值。

一:JVM(Java虚拟机)概述

前几天安装的VMware工具,它就是虚拟机

  • 所谓虚拟机(Virtual Machine),虚拟的计算机。作为一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机
  • VMware 就属于系统虚拟机,它是对物理计算机的仿真,提供可运行完整操作系统的软件平台。程序虚拟机典型的代表就是 java 虚拟机了,为执行某个单个计算机程序而设计。
  • 在 java 虚拟机中执行的指令称为 java 字节码指令。
  • Java 虚拟机是执行 java 字节码文件的虚拟计算机,具有独立的运行机制。
  • Java 技术核心就是 java 虚拟机,所有 java 程序都运行在 java 虚拟机内部

JVM作用

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

JAVA虚拟机的特点
(1) 一次编译到处运行,可移植性高;
(2)作为跨语言平台,不仅可执行 java 字节码文件,还可执行其他语言编译后的字节码文件;
(3) 自动内存管理
(4) 自动垃圾回收功能

位置

运行在操作系统之上的,它与硬件没有直接的交互。

分类

  • 类加载器(ClassLoader)
  • 运行时数据区(Runtime Data Area)
  • 执行引擎(Execution Engine)
  • 本地库接口(Native Interface)

简略流程

一次编译,到处运行

通常所说的 JVM 组成指的是运行时数据区(Runtime DataArea),因为通常需要程序员调试分析的区域就是“运行时数据区”,或者“运行时数据区”里面的 Heap(堆)模块。

程序在执行之前先要把 java 代码转换成字节码(class 文件),jvm 把字节码通过一定的方式 类加载器(ClassLoader) 把文件加载到内存中的运行时数据区(Runtime Data Area) ,而字节码文件是 jvm 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine) 将字节码翻译成底层系统指令再交由CPU 去执行,而这个过程中需要调用其他语言的接口 本地库接口(NativeInterface) 来实现整个程序的功能.

  • JVM 主要任务就是负责将字节码装载到其内部,解释/编译为对应平台上的机器指令执行。JVM 使用类加载器(Class Loader)装载 class 文件。

  • 类加载完成后,会进行字节码校验,字节码校验通过之后 JVM 解释器会把字节码翻译成机器码交由操作系统执行。

  • 但不是所有的代码都是解释执行,JVM 对此作了优化,比如 HotSpot 虚拟机,自身提供JIT(Just In Time)编译器.


JVM的架构模型

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

区别:

  • 基于栈的指令集架构:
    • 无需硬件支持,可移植性好,能实现跨平台.
    • 设计和实现更简单,适用于资源受限的系统.
    • 使用零地址指令方式分配,其执行过程依赖于操作栈,指令集更小,编译器容易实现.
  • 基于寄存器式的指令集架构:
    • 指令完全依赖于硬件,可移植性差.
    • 性能优秀,执行更高效.
    • 完成操作时使用的指令更少

使用 javap -v class文件可以将 class 文件反编译为指令集. 所以由于跨平台的设计,Java 指令集都是根据栈来设计的,不同 CPU 架构不同, 所以不能设计为基于寄存器的. 优点是跨平台,指令集小,编译器容易实现. 缺点是性能下降,实现同样功能需要更多的指令.


二:JVM类加载[classLoader]

类加载子系统

首先看看之前的流程图中的类加载子系统;分为三个流程;
加载阶段:里面分类了不同的加载器
链接阶段:验证,准备,解析
初始化阶段:

作用:

  • 类加载器子系统负责从文件系统或者网络中加载 class 文件。

  • 主要负责加载类, 有执行引擎执行,存放在方法区(元空间)

  • class文件存在于硬盘上,可理解为模板,而最终这个模板在执行的时会被加载到 JVM 里,根据此模板实例化出 n 个一模一样的实例

  • class 文件 加载到 JVM 中,被称为 DNA 元数据模板,放在方法区中.

加载过程之–>加载阶段

  • 根据类的地址,从硬盘上读取类的信息,

  • 将信息读入到方法区,生成Class类的对象

加载过程之–>链接阶段

  • (1) 验证: 验证字节码文件格式是否是当前虚拟机所支持的文件格式,语法格式;
    • 验证文件格式是否一致: class 文件在文件开头有特定的文件标识(字节码文件都以 CA FE BA BE 标识开头);主,次版本号是否在当前 java 虚拟机接收范围内.
    • 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合java 语言规范的要求,例如这个类是否有父类;是否继承浏览不允许被继承的类(final 修饰的类)…
      找到之前使用过的文件

命令运行;可输出

但是,若是在编译的Hello.class文件中放置一些其他信息;

这时就会被验证出错误

  • (2) 准备: 为静态成员分配默认值(int 默认值0) ;
  • 注意static final修饰的 静态常量在编译期间赋值

比如看这个案例

/**
 * @author by 信计1801 李智青 学号:1809064012
 */
public class Demo 

    //准备阶段时,为静态的变量进行初始化赋值; 默认值为0;初始化阶段时才赋值
    static int number = 10;
    //在编译期间直接赋值; 静态常量;
    static  final  int BAN = 100;

    //初始化触发;
    static 
        System.out.println("这是一行输出");
    

静态常量在编译期间之间赋值;这个类并没有被加载

但是要是访问静态变量的话;

  • (3) 解析: 将字节码中符号引用 替换 成 直接引用; 类加载到内存后把符号的引用地址 换成 内存的地址引用
    符号引用: Class 文件的逻辑符号,直接引用指向的方法区中某一个地址​

加载过程之–>初始化阶段

类在什么情况下会初始化:

  • (1)创建类的实例,也就是 new 一个对象

  • (2)访问某个类或接口的静态变量,或者对该静态变量赋值

  • (3)调用类的静态方法 ,静态域

  • (4)反射(例如Class.forName(“”))

  • (5)初始化一个类的子类时;(就会首先初始化子类的父类)

初始化顺序
顺序是:父类 static –> 子类 static –> 父类构造方法- -> 子类构造方法

  • 先初始化静态的,多个静态的按照从上向下的顺序执行,

  • 如果类有父类,则先初始化父类的静态,然后是子类.

  • 如果是创建对象,先调用父类的构造方法,然后是子类自己的构造方法


类加载器

按照JVM 分类 :类加载器可以分为两种

  • 引导类加载器(启动类加载器 Bootstrap ClassLoader).
  • 其他所有类加载器,由 java 语言实现,独立存在于虚拟机外部,继承自抽象类java.lang.ClassLoader.

java 开发分类:

(1)引导类加载器(启动类加载器 BootStrap ClassLoader)

  • 使用 C/C++语言实现,嵌套在 JVM 内部;用来加载 java 核心类库;负责加载扩展类加载器和应用类加载器;且指定父类加载器;
  • 它不继承自 java.lang.ClassLoader; 且没有父加载器.
  • 引用类加载器只加载存放在<JAVA_HOME>\\lib目录,或者被-Xbootclasspath 参数锁指定的路径中存储放的类;

(2)扩展类加载器(Extension ClassLoader)

  • Java语言编写;程序中默认的类加载器; sun.misc.Launcher$ExtClassLoader实现;继承自ClassLoader 类;

  • java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 系统安装目录的jre/lib/ext 子目录(扩展目录)下加载类库.当我们创建的 jar 放在此目录下,也会自动由扩展类加载器加载.

(3)应用程序类加载器(系统类加载器 Application ClassLoader)

  • sun.misc.Launcher$AppClassLoader 实现; Java语言编写,继承自ClassLoader类;

  • 用于加载用户类路径(classpath)上所有的类( 加载自定义的类)

ClassLoader 类,作为抽象类,其后所有的类加载器都继承自 ClassLoader(注意:不包括启动类加载器

案例

/**
 * @author by 信计1801 李智青 学号:1809064012
 */
public class ClassLoaderTest 
    public static void main(String[] args) 
        //应用程序类加载器
        ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(sysClassLoader);
        //sun.misc.Launcher$AppClassLoader@18b4aac2

        //获取 扩展类加载器;
        ClassLoader p1 = sysClassLoader.getParent();
        System.out.println(p1);
        //sun.misc.Launcher$ExtClassLoader@1b6d3586

        //继续向上获取;
        ClassLoader p2 = p1.getParent();
        System.out.println(p2);
        //null

        //我这里自定义的类,用的是应用程序类加载器;
        ClassLoader myLoader =ClassLoaderTest.class.getClassLoader();
        System.out.println(myLoader);
        //sun.misc.Launcher$AppClassLoader@18b4aac2

        //那么系统类库的类加载器就是 启动类加载器;
        ClassLoader l = Integer.class.getClassLoader();
        System.out.println(l);
        //null;
    


双亲委派机制浅入

  • 类加载时按需加载,使用时才会加载;当需要时才会将它的 class 文件加载到内存中生成class 对象

  • 类加载时,加载器都会将类交给父级类加载器加载.

  • 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,若它的父类还有父加载器;就一级一级的委派.

  • 若最终找到的类加载器无法完成任务;那么就在它向子类加载器中查找;最后如果还找不到,就抛出异常ClassNotFoundException

它的优点

  • 安全,可避免用户自己编写的类动态替换 Java 的核心类,比如 java.lang.String
  • 避免全限定命名的类重复加载(使用了 findLoadClass()判断当前类是否已加载)

例如,自定义String类

package java.lang;
/**
 * @author by 信计1801 李智青 学号:1809064012
 */
public class String 

    public String() 
        System.out.println("自定义的String类型成功");
    

我在其他位置测试调用创建对象;
这里实际加载的是系统的String类;

类的主/被动使用

主动使用和被动使用的区别在于类是否会被初始化.

主动

  • 通过new关键字被导致类的初始化;
  • 在当前类中执行main 函数
  • 访问类的静态变量,包括读取/更新;以及访问类的静态方法;
  • 对类进行反射操作
  • 初始化子类会导致父类的的初始化

被动

  • 引用该类的静态常量,不会导致初始化;
    • 注意这里的常量是指已经指定字面量的常量,
    • 对于需要计算才能得出结果的常量或者说在构造方法中赋值的常量就会导致初始化;
  • 构造类的数组时不会导致该类的初始化

以上是关于重点知识学习(4.1)--[JVM概述,类加载机制]的主要内容,如果未能解决你的问题,请参考以下文章

小白学习JVM--类加载和双亲委派机制

95%的技术面试必考的JVM知识点都在这,另附加分思路!

还没搞懂JVM吗?95%的技术面试必问知识点都在这,还怕面不过?

JVM类生命周期概述:加载时机与加载过程

重点知识学习(4.3)--[JVM的执行引擎,垃圾回收概述]

Java面试问题笔记——JVM