JVM是什么?类加载器是什么?

  •    JVM是虚拟机(Java Virtual Machine),JVM是运行字节码的。JVM有针对不同系统的特定实现(WindowsLinuxmacOS),目的是使用相同的字节码,它们都会给出相同的结果。
  •  类加载器(ClassLoader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载程序中的类型(类、接口)到JVM的内存空间去,并赋予唯一的名字。每一个JVM都有一个执行引擎(Execution engine)负责执行被加载类中包含的指令。

 

 JVM有两种类加载器,分别是启动类加载器,其他类加载器,也可以理解为是用户自定义加载器。

 


 

  小知识:1.JVM内存区域划分有什么?

       --粗略地来分,JVM的内部体系主要由三部分组成,分别是类装载器(ClassLoader)子系统、运行时数据区、执行引擎(Execution engine)。

 

       2.执行引擎?

       --执行引擎或者在执行字节码,或者在执行本地方法。【执行引擎必须把字节码转换成可以直接被JVM执行的语言】

        执行引擎主要是做解释、即时编译、自适应优化。

解释是通过解释器[芯片级直接执行其中解释是属于第一代JVM], 执行方式:一条一条地读取,解释并且执行字节码指令,很快能解释完,速度慢。
即时编译是通过即时(Just-In-Time)编译器[属于第二代JVM],   执行方式:是弥补解释器速度慢的缺点,按照解释执行的方式来执行,然后在合适的时候,把整段字节码编译成本地代码。直接通过本地代码去执行比一条条执行快的多。本地代 码执行快的原因是本地代码保存在缓存里。
 自适应优化(目前Sun的HotpotJVM采用这种技术),是前面两代的结合方式。 执行方式:开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对经常调用的方法启东一个后台线程,将其编译为本地代码,并进行仔细优化。

 

       3.运行时数据区主要包括什么?

       --主要包括:方法区、堆、Java栈(虚拟机栈)、PC寄存器、本地方法栈。

方法区:当JVM的类加载器加载.class文件,并进行解析,把解析的类型信息放入方法区。
堆:存放所有程序在运行时创建的对象
JVM栈:是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(JAVA八种基本类型)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。
本地方法栈:存储本地方法调用的状态     


方法区&堆 是由所有线程共享

  Java栈和PC寄存器 是由线程独享

   本地栈 是存储本地方法调用的状态

       JVM运行时的数据区对开发重要的几个东西:

        (1)方法区(Method Area):在Sun JDK中这块区域对应的为持久代,PermanetGeneration。<--存放所加载的   类的信息(名称、修饰符等)、类中的静态变量、final类型的常量、Field信息、方法信息;如Class对象中的getName、isInterface等方法获取信息时,这些数据都是来源于方法区,同时方法区也是全局共享的, 在一定条件下也会被GC,如果使用大小超出其允许范围,就会抛出OutOfMemory错误信息。

 

       (2)堆(Heap):存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。 <--Heap是所有线程共享的,因此在其上进行对象内存分配均需要进行加锁,正式因为如此,导致了new对象的开销比较大。为了提升效率,JVM在给线程的对象分配内存时会尽量在TLAB(在所创建的线程分配一块独立的空间-Thread Local Allocation Buffer)上分配,但对象过大则仍采用Heap。TLAB仅仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来分更加高效。

 

        (3)Java栈(JavaStack) : 虚拟机只会直接对JavaStack执行两种操作--以帧为单位的压栈或出栈。<-- 每个帧代表一个方法,Java方法有两种返回方式,return 和 抛出异常,两种方式都会导致该方法对应的帧出栈和释放内存。ps:  帧的组成?  --局部变量区、操作数栈、帧数据区。

 

        (4)PC寄存器(ProgramCounter):每一个线程都有自己的PC寄存器,在该线程启动时创建的。PC寄存器的内容总是指向下一个将被执行指令的地址,这里的地址可以是一个本地指针,也可以是方法区中相对应于该方法起始指令的偏移量。<--若Thread执行Java方法,则PC保存下一条执行指令的地址。若Thread执行native方法,则PC的值为undefined。

 

        (5)本地方法栈(Nativemethodstack):保存native方法进入区域的地址。<-- 依赖于本地方法的实现,如某个JVM实现的本地方法接口使用C连接模型,则本地方法栈就是C栈,可以说某线程在调用本地方法时,就进去了一个不受JVM限制的区域,也就是JVM可以利用本地方法来动态扩展本身。

 

一、类加载机制

       当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过 加载、链接、初始化 三个步骤俩对该类进行初始化。

            

       1.加载

    (1)加载是什么?--加载指的是将类的 class文件 读入到内存中,并为之创建一个 java.lang.class 对象,简而言之,当程序中使用任何类时,系统都会为之建立一个 java.lang.class 对象。

    (2)类的加载由谁来完成?--类的加载是由类加载器(ClassLoader)完成的,这些类加载器通常由JVM提供,称为系统加载器。当热也可以通过用户自定义类加载器。

    (3)使用不同类加载器加载不同来源的二进制数据时,通常有几种来源?(本地文件系统、JAR包、网络、Java源文件

       --从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。

       --从JAR包加载class文件,这种方式也是很常见的,jdbc编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。

       --通过网络加载class文件。

       --把一个Java源文件动态编译,并执行加载。

        类加载器通常无需等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

 

      2.链接

       1)链接负责什么?--当类被加载后,系统为之生成一个对应的Class对象,接着将会进入链接阶段,链接阶段 负责把类的二进制数据合并到JRE中

       (2)链接的阶段可以分为哪些?(3个阶段,分别是验证、准备、解析

           --验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。目的:确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。

             

文件格式验证 主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理
元数据验证 对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范
字节码验证 最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要是针对元数据验证后对方法体的验证,保证类方法在与运行时不会有危害出现。
符号引用验证 主要是针对符号引用转换为直接引用的时候,针对确定访问类型等涉及到引用的情况,保证其一定会被访问到,不会出现无法访问的问题。

           --准备:类准备阶段 负责为类的 静态变量 分配内存,并 设置 默认初始值,这些变量所使用的内存都将在方法区汇总进行分配

           --解析:JVM将常量池内的符号引用替换成直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这7中符号引用进行。

             ps:符号引用?-- 符号引用是以一组符号来描述所引用的目标,符号可以是任何字面形式的字面量,只要不会出现冲突,能够定位到就行。布局和内存无关。

                 直接引用?--是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。

 

      3.初始化

         初始化是为 类的静态变量 赋予 正确的初始值,到了初始化阶段,才真正开始执行类中定义的java程序代码(字节码),直接点说:初始化阶段是执行类构造器<clinit>()方法的过程

          ps: <client>()方法是什么? -- 是由编译器自动收集类中所有类变量的赋值动作和静态语句块static{}中的语句合并产生的, 编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量可以在前面的静态语句块中赋值,但是不能访问。 

               虚拟机会保证一个类的类构造器<clinit>()在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的类构造器<clinit>(),其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。在同一个类加载器下,一个类型只会被初始化一次。

 

             类加载机制类别:全盘负责、双亲委派、缓存机制

             

全盘负责   当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
双亲委派 先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时,子类才擦夯实从自己的类路径中加载该类。
缓存机制 将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区转给你搜寻该Class,只有当缓存区不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。

 

二、类加载时机

         创建类的实例-->访问某个类或接口的静态变量-->调用类的静态方法-->反射-->初始化一个类的子类-->JVM启动时标明的启动类

         1.创建类的实例,也就是我们所说的new一个对象

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

         3.调用类的静态方法

         4.反射(Class.forName("com.lyj.load"))

         5.初始化一个类的子类(会首先初始化子类的父类)

         6.JVM启动时,指定一个要执行的主类(包含main()方法的那个类)标明的启动类,即文件名和类名相同的那个类

         

        ps:Java对象创建/实例化时机与过程

        (类实例化过程)父类静态成员和静态初始化块-->子类静态成员和静态初始化块-->父类实例成员和实例化初始块-->父类构造方法-->子类实例成员和实力初始化块-->子类构造方法

        1.使用new关键字创建对象

        2.使用Class类的newInstance方法(反射机制)

        3.使用Constructor类的newInstance方法(反射机制)

        4.使用Clone方法创建对象

        5.使用(反)序列化机制创建对象

 

 

 

 Thanks . 

参考:https://blog.csdn.net/qq_41701956/article/details/80020103

           https://blog.csdn.net/xiaoliuliu2050/article/details/53023752

             https://www.cnblogs.com/wxd0108/p/6681618.html