详解JAVA类加载机制

1.一段简单的代码

首先来一段代码,这个是单例模式,可能有的人不知道什么是单例模式,我就简单说一下

单例模式是指一个类有且只有一种对象实例。这里用的是饿汉式,还有懒汉式,双检锁等等。。。。

写这个是为了给大家看一个现象

class SingleTon{
  public static int count1;
  public static int count2=0;
  private static SingleTon instance=new SingleTon();
  public SingleTon(){
    count1++;
    count2++;
  }

  public static SingleTon getInstance() {
    return instance;
  }
}
public class JVMTest {
  public static void main(String[] args) {
    SingleTon.getInstance();
    System.out.println(SingleTon.count1);
    System.out.println(SingleTon.count2);
  }
}

执行结果:

同样的代码我把private static SingleTon instance=new SingleTon()移到上面

我们再看执行结果:

可以发现执行结果发生了变化

这个先放在这,等了解了类加载机制过程,后面再说

2.什么是类加载机制

  概念:Java中的类加载机制指虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。

  大白话:其实就是把字节码文件放在虚拟机里面去

  与那些在编译时需要进行连接工作的语言不同,在Java语言中,类型的加载、连接、初始化都是在程序运行期间完成的,这种策略虽然会令类加载时稍微多一些性能的开销,但是会为Java应用程序提供高度的灵活性。

  类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用、卸载七个阶段。验证、准备、解析被称为连接

  注意:加载、验证、准备、初始化、使用、卸载这几个顺序是确定的,但是解析不一定,它在某些情况下可以在初始化阶段后再开始,主要是为了支持Java语言运行时绑定(只做了解即可)

  类加载机制包括前面五个阶段。

3.类加载时机

  什么情况下虚拟机需要开始加载一个类呢?虚拟机规范中并没有对此进行强制约束,这点可以交给虚拟机的具体实现来自由把握。

4.类初始化时机

  那么,什么情况下虚拟机需要开始初始化一个类呢?这在虚拟机规范中是有严格规定的,虚拟机规范指明 有且只有 五种情况必须立即对类进行初始化(而这一过程自然发生在加载、验证、准备之后):

  遇到new、getstatic、putstatic或invokestatic这四条字节码指令(注意,newarray指令触发的只是数组类型本身的初始化,而不会导致其相关类型的初始化(比如,new String[]只会直接触发String[]的初始化,也就是触发对类java.lang.String的初始化,而直接不会触发String类的初始化)时,如果类没有进行过初始化,则需要先对其进行初始化。

生成这四条指令的最常见的Java代码场景是:

1  使用new关键字实例化对象的时候;
2  读取或设置一个类的静态字段(被final修饰,已在编译器把结果放入常量池的静态字段除外)的时候;
3  调用一个类的静态方法的时候。
  2) 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

  3) 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

  4) 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

  5) 当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始

化,则需要先出触发其初始化。

  注意,对于这五种会触发类进行初始化的场景,虚拟机规范中使用了一个很强烈的限定语:“有且只有”,这五种场景中的行为称为对一个类进行 主动引用。除此之外,所有引用类的方式,都不会触发初始化,称为 被动引用。

  特别需要指出的是,类的实例化与类的初始化是两个完全不同的概念:

    类的实例化是指创建一个类的实例(对象)的过程;

5.类加载过程

加载:

加载“是”类加机制”的第一个过程,在加载阶段,虚拟机主要完成三件事:

(1)通过一个类的全限定名(可以理解为绝对路径)来获取其定义的二进制字节流

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

(3)在堆中生成一个代表这个类的Class对象,作为方法区中这些数据的访问入口。

相对于类加载的其他阶段而言,加载阶段是可控性最强的阶段,因为程序员可以使用系统的类加载器加载,还可以使用自己的类加载器加载。我们在最后一部分会详细介绍这个类加载器

验证

验证的主要作用就是确保被加载的类的正确性。也是连接阶段的第一步。说白了也就是我们加载好的.class文件不能对我们的虚拟机有危害,所以先检测验证一下。他主要是完成四个阶段的验证:

(1)文件格式的验证:验证.class文件字节流是否符合class文件的格式的规范,并且能够被当前版本的虚拟机处理。这里面主要对魔数、主版本号、常量池等等的校验(魔数、主版本号都是.class文件里面包含的数据信息、在这里可以不用理解)。

(2)元数据验证:主要是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求,比如说验证这个类是不是有父类,类中的字段方法是不是和父类冲突等等。

(3)字节码验证:这是整个验证过程最复杂的阶段,主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在元数据验证阶段对数据类型做出验证后,这个阶段主要对类的方法做出分析,保证类的方法在运行时不会做出威海虚拟机安全的事。

(4)符号引用验证:它是验证的最后一个阶段,发生在虚拟机将符号引用转化为直接引用的时候。主要是对类自身以外的信息进行校验。目的是确保解析动作能够完成。

对整个类加载机制而言,验证阶段是一个很重要但是非必需的阶段,如果我们的代码能够确保没有问题,那么我们就没有必要去验证,毕竟验证需要花费一定的的时间。当然我们可以使用-Xverfity:none来关闭大部分的验证。

准备:

为类变量分配内存,并将其初始化为默认值。(此时为默认值,在初始化的时候才会给变量赋值)即在方法区中分配这些变量所使用的内存空间。例如

public static int value = 123;

此时在准备阶段过后的初始值为0而不是123;

特例:

public static final int value = 123;

此时value的值在准备阶段过后就是123。

解析:

解析阶段将类中符号引用转换为直接引用。符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。

初始化:

初始化阶段是执行类构造器<client>方法的过程。<client>方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证<client>方法执行之前,父类的<client>方法已经执行完毕。如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成<client>()方法。

java中,对于初始化阶段,有且只有以下五种情况才会对要求类立刻“初始化”(加载,验证,准备,自然需要在此之前开始):

1  使用new关键字实例化对象、访问或者设置一个类的静态字段(被final修饰、编译器优化时已经放入常量池的例外)、调用类方法,都会初始化该静态字段或者静态方法所在的类。

2  初始化类的时候,如果其父类没有被初始化过,则要先触发其父类初始化。

3  使用java.lang.reflect包的方法进行反射调用的时候,如果类没有被初始化,则要先初始化。

4  虚拟机启动时,用户会先初始化要执行的主类(含有main)

5  jdk 1.7后,如果java.lang.invoke.MethodHandle的实例最后对应的解析结果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄,并且这个方法所在类没有初始化,则先初始化。

JVM初始化步骤

1、假如这个类还没有被加载和连接,则程序先加载并连接该类

2、假如该类的直接父类还没有被初始化,则先初始化其直接父类

3、假如类中有初始化语句,则系统依次执行这些初始化语句

6.分析刚开始的问题

第一种情况:

连接阶段:为静态变量赋值初始值,SingleTon=null,count1=0,count2=0

初始化阶段:从上到下执行赋值操作和静态代码块

  count1=0,count2=0创建对象后对两个值进行递增结果count1=1,count2=1

第二种情况:

连接阶段:为静态变量赋值初始值,SingleTon=null,count1=0,count2=0

初始化阶段:从上到下执行赋值操作和静态代码块

  先创建对象后对两个值进行递增count1=1,count2=1

  再赋值count1没变,count2=0

6.类加载器分类

· 启动类加载器(Bootstrap ClassLoader):

启动类加载器负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的类。

· 扩展类加载器(ExtClassLoader):

扩展类加载器负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类)。

· 应用类加载器(AppClassLoader):

应用类加载器负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器。

类加载器的职责:

· 全盘负责:

当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。

· 父类委托:

类加载机制会先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。

父类委托机制是为了防止内存中出现多份同样的字节码,保证java程序安全稳定运行。

· 缓存机制:

缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效。

7.双亲委派模型

类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的;除了启动类加载器,每个类都有其父类加载器(父子关系由组合(不是继承)来实现);

所谓双亲委派是指每次收到类加载请求时,先将请求委派给父类加载器完成(所有加载请求最终会委派到顶层的Bootstrap ClassLoader加载器中),如果父类加载器无法完成这个加载(该加载器的

搜索范围中没有找到对应的类),子类尝试自己加载。

双亲委派好处

· 避免同一个类被多次加载;

· 每个加载器只能加载自己范围内的类;

双亲委派是可以违背的么?

这个是可以的只需要我们去自定义类加载器重写ClassLoader中的loadclass方法

以上就是详解JAVA类加载机制的详细内容,更多关于JAVA类加载机制的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java类加载器和类加载机制实例分析

    本文实例讲述了Java类加载器和类加载机制.分享给大家供大家参考,具体如下: 一 点睛 1 类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象. 2 当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构: Bootstrap ClassLoader:根类加载器. Extension ClassLoader:扩展类加载器. System ClassLoader:系统类加载器. 3 JVM的类加载机制主要有如下三种

  • Java 类加载机制详细介绍

    一.类加载器 类加载器(ClassLoader),顾名思义,即加载类的东西.在我们使用一个类之前,JVM需要先将该类的字节码文件(.class文件)从磁盘.网络或其他来源加载到内存中,并对字节码进行解析生成对应的Class对象,这就是类加载器的功能.我们可以利用类加载器,实现类的动态加载. 二.类的加载机制 在Java中,采用双亲委派机制来实现类的加载.那什么是双亲委派机制?在Java Doc中有这样一段描述: The ClassLoader class uses a delegation mo

  • Java运行时环境之ClassLoader类加载机制详解

    背景:听说ClassLoader类加载机制是进入BAT的必经之路. ClassLoader总述: 普通的Java开发其实用到ClassLoader的地方并不多,但是理解透彻ClassLoader类的加载机制,无论是对我们编写更高效的代码还是进BAT都大有裨益:而从"黄埔军校"出来的我对ClassLoader的理解都是借鉴了很多书籍和博客,站在了各大博主的肩膀上,感谢你们!上菜,Classloader最主要的作用就是将Java字节码文件(后缀为.class)加载到JVM中,JVM在启动时

  • 通过面试题解析 Java 类加载机制

    在许多Java面试中,我们经常会看到关于Java类加载机制的考察,例如下面这道题: class Grandpa { static { System.out.println("爷爷在静态代码块"); } } class Father extends Grandpa { static { System.out.println("爸爸在静态代码块"); } public static int factor = 25; public Father() { System.ou

  • java 类加载机制和反射详解及实例代码

    一.Java类加载机制 1.概述 Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能. 虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 2.工作机制 类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示

  • Java类加载机制实现步骤解析

    一.类的加载过程 JVM将类的加载分为3个步骤: 1.装载(Load) 2.链接(Link) 3.初始化(Initialize) 其中 链接(Link)又分3个步骤,如下图所示: 1) 装载:查找并加载类的二进制数据(查找和导入Class文件) 加载是类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情: 1.通过一个类的全限定名来获取其定义的二进制字节流. 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构. 3.在Java堆中生成一个代表这个类的java.lang.C

  • 初探Java类加载机制

    一.在jdk1.2以后,类加载是通过委托来完成的,这意味着如果 ClassLoader 不能找到类,它会请求父代 ClassLoader 来执行此项任务,所有 ClassLoaders 的根是系统 ClassLoader,它会以缺省方式装入类 -- 即,从本地文件系统.今天我们就来探讨一下在jvm中这些机制是怎样运行的.让我们假设有一个class字节码文件(比如Hello.class文件),那么在应用程序中,他是如何被加载进来,并形成一个类对象的呢?我们这篇文章的目的就是为了解释这个问题. 在j

  • 详解JAVA类加载机制(推荐)

    JAVA源码编译由三个过程组成: 1.源码编译机制. 2.类加载机制 3.类执行机制 我们这里主要介绍编译和类加载这两种机制. 一.源码编译 代码编译由JAVA源码编译器来完成.主要是将源码编译成字节码文件(class文件).字节码文件格式主要分为两部分:常量池和方法字节码. 二.类加载 类的生命周期是从被加载到虚拟机内存中开始,到卸载出内存结束.过程共有七个阶段,其中到初始化之前的都是属于类加载的部分 加载----验证----准备----解析-----初始化----使用-----卸载 系统可能

  • 详解JAVA类加载机制

    1.一段简单的代码 首先来一段代码,这个是单例模式,可能有的人不知道什么是单例模式,我就简单说一下 单例模式是指一个类有且只有一种对象实例.这里用的是饿汉式,还有懒汉式,双检锁等等.... 写这个是为了给大家看一个现象 class SingleTon{ public static int count1; public static int count2=0; private static SingleTon instance=new SingleTon(); public SingleTon()

  • 详解Java类加载器与双亲委派机制

    目录 引子 了解.class文件 类加载的过程 类加载器 与 双亲委派机制 ClassLoader 自定义类加载器 编写一个自定义的类加载器 为什么我们这边要打破双亲委派机制 自定义类加载器时,如何打破双亲委派机制 SPI机制 与 线程上下文类加载器 JDBC Tomcat SpringBoot Starter 尾语 引子 大家想必都有过平时开发springboot 项目的时候稍微改动一点代码,就得重启,就很烦 网上一般介绍 2种方式 spring-boot-devtools,或者通过JRebe

  • 详解JAVA 反射机制

    什么是反射? 反射机制是在程序运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法: 对于任意一个对象,都能够调用它的任意一个方法和属性: 这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制. 反射的作用 1.可以实现简单的反编译,获取类中的属性和方法等基本信息,.class->java 2.通过反射机制获取类的属性.方法等 在使用eclipse时,通过对象引用.的方式,eclipse就会将这个对象中的所有属性和方法展示出来,这个就是利用的反射机制.其实反射应用最多的

  • 详解Java序列化机制

    概况 在程序中为了能直接以 Java 对象的形式进行保存,然后再重新得到该 Java 对象,这就需要序列化能力.序列化其实可以看成是一种机制,按照一定的格式将 Java 对象的某状态转成介质可接受的形式,以方便存储或传输.其实想想就大致清楚基本流程,序列化时将 Java 对象相关的类信息.属性及属性值等等保存起来,反序列化时再根据这些信息构建出 Java 对象.而过程可能涉及到其他对象的引用,所以这里引用的对象的相关信息也要参与序列化. Java 中进行序列化操作需要实现 Serializabl

  • 图文详解java反射机制及常用应用场景

    目录 一.什么是java反射? 二.HelloWorld 三.类加载与反射关系 四.操作反射的java类 4.1.获取Class对象的三种方法 4.2.获取Class类对象的基本信息 4.3.获得Class对象的成员变量 4.4.获取Class对象的方法 4.5.方法的调用 4.6.创建类的对象(实例化对象) 五.反射的常用场景 5.1.通过配置信息调用类的方法 5.2.结合注解实现特殊功能 5.3.按需加载jar包或class 六.反射的优缺点 一.什么是java反射? 在java的面向对象编

  • 详解JAVA SPI机制和使用方法

    JAVA SPI 简介 SPI 是 Java 提供的一种服务加载方式,全名为 Service Provider Interface.根据 Java 的 SPI 规范,我们可以定义一个服务接口,具体的实现由对应的实现者去提供,即服务提供者.然后在使用的时候再根据 SPI 的规范去获取对应的服务提供者的服务实现.通过 SPI 服务加载机制进行服务的注册和发现,可以有效的避免在代码中将具体的服务提供者写死.从而可以基于接口编程,实现模块间的解耦. SPI 机制的约定 1 在 META-INF/serv

  • 详解JVM类加载机制及类缓存问题的处理方法

    前言 大家应该都知道,当一个Java项目启动的时候,JVM会找到main方法,根据对象之间的调用来对class文件和所引用的jar包中的class文件进行加载(其步骤分为加载.验证.准备.解析.初始化.使用和卸载),方法区中开辟内存来存储类的运行时数据结构(包括静态变量.静态方法.常量池.类结构等),同时在堆中生成相应的Class对象指向方法区中对应的类运行时数据结构. 用最简单的一句话来概括,类加载的过程就是JVM根据所需的class文件的路径,通过IO流的方式来读取class字节码文件,并通

  • 详解Java的类加载机制及热部署的原理

    一.什么是类加载 类的加载指的是将类的.class文件的二进制数据读入到内存中,将其放在运行数据区的方法去,然后再堆区创建一个java.lang.Class对象,用来封装类在方法区的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区的数据结构,并且向Java程序员提供了访问方法区的数据结构的接口. 类加载器并不需要等到某个类被"首次主动使用"时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.cla

  • 一文详解Java中的类加载机制

    目录 一.前言 二.类加载的时机 2.1 类加载过程 2.2 什么时候类初始化 2.3 被动引用不会初始化 三.类加载的过程 3.1 加载 3.2 验证 3.3 准备 3.4 解析 3.5 初始化 四.父类和子类初始化过程中的执行顺序 五.类加载器 5.1 类与类加载器 5.2 双亲委派模型 5.3 破坏双亲委派模型 六.Java模块化系统 一.前言 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最 终形成可以被虚拟机直接使用的Java类型,这个过程

随机推荐