概述java虚拟机中类的加载器及类加载过程

1. 类加载子系统

1.1 概述

类加载子系统负责从文件系统或者网络中加载Class文件,Class文件在文件开头有特定的文件标识

  • ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine 决定
  • 加载的类信息存放于一块成为 :方法区的内存空间,除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)

字节码中的常量池加载到 方法区 -----> 运行时常量池信息

1.2 类的加载器

  • Class file(字节码文件) 存在与本地磁盘上(硬盘),字节码文件在执行的时候是需要被加载到JVM的方法区中,再根据方法区中的这个类对象实例化出n个一模一样的实例
  • Class file 加载到JVM中,被称为DNA元数据模板,放在方法区。
  • 在Class文件 --> JVM -->最终成为元数据模板,此过程就要一个运输工具(类加载器 Class Loader),扮演一个快递员的角色。

2.类的加载过程

2.1 类的加载过程简图


2.2 加载阶段:Loading

  • 通过一个类的全限定名获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

小补充:加载字节码文件(.class)的方式

  • 本地系统直接加载
  • 网络获取:Web Applet
  • jar、war包
  • 动态代理:运行时计算生成…

2.3 链接阶段:Linking

验证(Verify)

  • 目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全
  • 主要包括四种验证:文件格式验证、元数据验证、字节码验证、符号引用验证

准备(Prepare)

  • 为类变量分配内存并且设置该类变量的默认初始值,即“零值”

(在准备阶段 a = 0,到下一个阶段(初始化阶段)a = 1)
(不同的数据类型的变量默认值不一样,如 int =0 ,引用类型 = null)

  • 这里不包含用final 修饰的static,因为final 在编译的时候就会分配了,准备阶段会显示初始化(final修饰的变量是:常量)
  • 这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中

解析(Resolve)

  • 将常量池内的符号引用转换为直接引用的过程
  • 事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行
  • 符号引用:一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《Java 虚拟机规范》的Class 文件格式中
  • 直接引用:直接指向目标的指针、相对偏移量或者一个间接定位到目标的句柄
  • 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。

2.4 初始化阶段:initialization

  • 初始化阶段就是执行类构造器方法 clinit()的过程,此方法不需要定义,是javac 编译器自动收集类中的所有了变量的赋值动作和静态代码块中的语句合并而来

例子1:

public class Test {
    private static int a=2;
    private static int b=20;

    public static void main(String[] args) {
        System.out.println(a);
        System.out.println(b);
    }
}

例子2:

public class Test {
    public static void main(String[] args) {
        System.out.println("测试一下");
    }
}

  • 构造器方法中指令按语句在源文件中出现的顺序执行
  • clinit() 方法不同于类的构造器。(关联:构造器是虚拟机视角下的 init())
  • 若该类具有父类,JVM会保证子类的 clinit () 执行前,父类的 clinit () 已经执行完毕
  • 虚拟机必须保证一个类的 clinit () 方法在多线程下被同步加锁,虚拟机只会调用一次 clinit 方法,保证类只被加载一次

3.几种类的加载器

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

代码:

public class Test {

    public static void main(String[] args) {
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 :应用类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@34a245ab :扩展类加载器
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);//null :引导类加载器:非Java语言实现

        ClassLoader classLoader = Test.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

         ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null
    }
}

3.1 引导类加载器:

  • BootstrapClassLoader 这个类加载器使用 C/C++语言实现,嵌套在JVM内部
  • 它用来加载Java 的核心类库(JAVA_HOME/jre/lib/rt.jar、resources.jar 或 sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
  • 并不继承自 java.lang.ClassLoader ,没有父类加载器
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
  • 出于安全考虑,Bootstrap 启动类加载器只加载包名为 java 、javax、sun等开头的类

3.2 扩展类加载器:ExtensionClassLoader

  • Java 语言编写,派生于ClassLoader类
  • 父类加载器为:启动类加载器
  • 从 java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库,如果用户创建的jar包放在此目录下,也会自动由扩展类加载器进行加载

3.3 应用程序类加载器:AppClassLoader

  • java语言编写,派生于 ClassLoader
  • 父类加载器为 ExtensionClassLoader
  • 负责加载环境变量classpath或者系统属性 java.class.path 指定路径下的类库
  • 该类加载器是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
  • 通过ClassLoader # getSystemClassLoader()方法可以获取到该类加载器

3.4 用户自定义类加载器

  • 在Java 的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要的时候,我们还可以自定义类加载器,来定制类的加载方式

为什么要用自定义类加载器呢?

  • 隔离加载类
  • 修改类加载的方式
  • 扩展加载源
  • 防止源码泄漏

用户自定义类加载器实现步骤

  • 通过继承抽象类 java.lang.ClassLoader 类的方式,实现自己的类加载器,以满足一些特殊的需求
  • JDK1.2之前,总会去继承ClassLoader类并重写loadClass()方法,从而实现自定义的类加载器,但是在JDK1.2之后,已不再建议去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在 findClass()方法中
  • 如果对于类加载器没有太过于复杂的需求,可以通过直接继承 URLClassLoader类,这样就可以避免自己去编写findClass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁

继承体系

获取 ClassLoader

4.双亲委派机制

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

  • 如果一个类加载器收到了类的加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行
  • 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归请求最终将到达顶层的启动类加载器
  • 如果类加载器可以完成类加载任务,就成功返回,如果父类加载器无法完成此加载任务,子类加载器才会尝试去加载,这就是双亲委派模式

优势

  • 避免类的重复加载
  • 保护程序安全,防止核心API被随意篡改:自定义:java.lang.String …
  • 不要乱取包名

5.其他

在JVM中表示两个class对象是否为同一个类存在两个必要条件

  • 类的全限定类名必须一致,包括包名
  • 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同
  • 即使两个类来源于同一个文件,但是类加载器不一样,那这两个类对象也是不相等的

到此这篇关于概述java虚拟机中类的加载器及类加载过程的文章就介绍到这了,更多相关Jvm中类的加载器及类加载过程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java虚拟机内存溢出与内存泄漏

    一.基本概念 内存溢出:简单地说内存溢出就是指程序运行过程中申请的内存大于系统能够提供的内存,导致无法申请到足够的内存,于是就发生了内存溢出. 内存泄漏:内存泄漏指程序运行过程中分配内存给临时变量,用完之后却没有被GC回收,始终占用着内存,既不能被使用也不能分配给其他程序,于是就发生了内存泄漏. 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory: 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间

  • Java虚拟机内存区域划分详解

    在谈 JVM 内存区域划分之前,我们先来看一下 Java 程序的具体执行过程,我画了一幅图. Java 源代码文件经过编译器编译后生成字节码文件,然后交给 JVM 的类加载器,加载完毕后,交给执行引擎执行.在整个执行的过程中,JVM 会用一块空间来存储程序执行期间需要用到的数据,这块空间一般被称为运行时数据区,也就是常说的 JVM 内存. 所以,当我们在谈 JVM 内存区域划分的时候,其实谈的就是这块空间--运行时数据区. 大家应该对官方出品的<Java 虚拟机规范>有所了解吧?了解这个规范可

  • 深入了解Java虚拟机栈以及内存模型

    1.结合字节码指令理解Java虚拟机栈和栈帧 栈帧:每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间. 每个栈帧中包括局部变量表(Local Variables).操作数栈(Operand Stack).指向运行时常量池的引用(A reference to the run-time constant pool).方法返回地址(Return Address)和附加信息. 局部变量表:方法中定义的局部变量以及方法的参数存放在这张表中,局部变量表中的变量不可直接使用,如需要使用的话,必须通过

  • 一篇文章总结Java虚拟机内存区域模型

    首先我们来看一下Java运行时的数据区域,Java虚拟机在执行Java程序的过程中会把它所管理的内存划分成若干个不同的数据区域,这些区域都有各自的用途,各自的创建和销毁的时间.有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁. 我们来看一下Java虚拟机运行时的数据区 结合这张图,下面逐个来分析一下每个数据区域的特点. 1.程序计数器 程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器. 什么意思呢?我们知道,CPU的计算时间是以分片的

  • java虚拟机中多线程总结

    我记得最开始接触多进程,多线程这一块的时候我不是怎么理解,为什么要有多线程啊?多线程到底是个什么鬼啊?我一个程序好好的就可以运行为什么要用到多线程啊?反正我是十分费解,即使过了很长时间我还是不是很懂,听别人说过也自己试过,但总是没有理解透彻: 时间过了很久感觉现在对多线程有了一点新的理解,我们还是从最基本的开始,顺便看看从jvm的角度看看多线程在jvm中是怎么分配内存的,顺便和前面的几篇内容串一下: 1.现实中的多线程 举个例子:假如你一个人在家,你现在听首歌5分钟,烧开水需要10分钟,玩一局游

  • 概述java虚拟机中类的加载器及类加载过程

    1. 类加载子系统 1.1 概述 类加载子系统负责从文件系统或者网络中加载Class文件,Class文件在文件开头有特定的文件标识 ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine 决定 加载的类信息存放于一块成为 :方法区的内存空间,除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射) 字节码中的常量池加载到 方法区 -----> 运行时常量池信息 1

  • 解析Java虚拟机中类的初始化及加载器的父委托机制

    类的初始化 在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值. 在程序中,静态变量的初始化有两种途径: 1.在静态变量的声明处进行初始化: 2.在静态代码块中进行初始化. 没有经过显式初始化的静态变量将原有的值. 一个比较奇怪的例子: package com.mengdd.classloader; class Singleton { // private static Singleton mInstance = new Singleton();// 位置1 // 位置1输

  • 详解Java中类的加载顺序

    本文介绍的是Java中类的加载顺序,下面来看看详细的介绍: 1.虚拟机在首次加载Java类时,会对静态初始化块.静态成员变量.静态方法进行一次初始化 2.只有在调用new方法时才会创建类的实例 3.类实例创建过程:按照父子继承关系进行初始化,首先执行父类的初始化块部分,然后是父类的构造方法:再执行本类继承的子类的初始化块,最后是子类的构造方法 4.类实例销毁时候,首先销毁子类部分,再销毁父类部分 示例 public class Parent { public static int t = par

  • java之jvm加载器例举

    在java的学习中,对于jvm模块我们会不断补充一些知识点,毕竟jvm是比较重要的一个组成部分.本篇围绕jvm加载器展开介绍,在加载类的时候,我们的加载器会逐个进行工作,在具体的加载器类型上,想必大家还不是很清楚.下面我们就java之jvm加载器的4中类型带来介绍. 1.Bootstrap ClassLoader (引导类加载器) 负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现的核心库,也就是JVM调用每个系统的API实现系统功能,实现跨平台的主要模块

  • Java中类的加载顺序执行结果

    具体代码如下所示: public class Parent { public static int a = parentStaticMethod2(); { System.out.println("父类非静态初始化块"); } static { System.out.println("父类静态初始化块"); } public Parent() { System.out.println("父类的构造方法"); } public static int

  • 举例讲解Java的内部类与类的加载器

    内部类 class A { //Inner1 要在 A 初始化后 才能使用,即要被A的对象所调用 class Inner1 { int k = 0; // static int j = 0; //A加载后,Inner1没有加载,所以这个 静态变量j 无法立即使用,报错 final int z = 0; /*static void say1() { }*/ void say2() { } } //Inner2 在A加载好后就可以使用了 static class Inner2 { int k = 0

  • Java中类的加载顺序剖析(常用于面试题)

    这其实是去年校招时我遇到的一道阿里巴巴的笔试题(承认有点久远了-.-),嗯,如果我没记错的话,当时是作为Java方向的一道选做大题.当然题意没有这么直白,题目只要求你写出程序运行后所有System.out.println的输出结果,其中程序是题目给的,而各个System.out.println的执行顺序不同会导致最后程序输出的结果也不同. 具体的题目我肯定记不清,不过我们可以换个直接的问法,如果类A和类B中有静态变量,静态语句块,非静态变量,非静态语句块,构造函数,静态方法,非静态方法,同时类A

  • 概述如何实现一个简单的浏览器端js模块加载器

    在es6之前,js不像其他语言自带成熟的模块化功能,页面只能靠插入一个个script标签来引入自己的或第三方的脚本,并且容易带来命名冲突的问题.js社区做了很多努力,在当时的运行环境中,实现"模块"的效果. 通用的js模块化标准有CommonJS与AMD,前者运用于node环境,后者在浏览器环境中由Require.js等实现.此外还有国内的开源项目Sea.js,遵循CMD规范.(目前随着es6的普及已经停止维护,不论是AMD还是CMD,都将是一段历史了) 浏览器端js加载器 实现一个简

  • 从JVM分析Java的类的加载和卸载机制

    类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构. 加载.class文件的方式: 1.从本地系统中直接加载 2.通过网络下载.class文件 3.从zip,jar等归档文件中加载.class文件 4.从专有数据库中提取.class文件 5.将Java源文件动态编译为.class文件 类的加载的最终产品是位于堆区中的Class对象. Class对象封装了类在

  • 通过实例解析Java class文件编译加载过程

    一.Java从编码到执行 首先我们来看一下Java是如何从编码到执行的呢? 我们有一个x.java文件通过执行javac命令可以变成x.class文件,当我们调用Java命令的时候class文件会被装载到内存中,这个过程叫做classloader.一般情况下我们自己写代码的时候会用到Java的类库,所以在加载的时候也会把Java类库相关的类也加载到内存中.装载完成之后会调用字节码解释器和JIT即时编译器来进行解释和编译,编译完之后由执行引擎开始执行,执行引擎下面对应的就是操作系统硬件了.下图是大

随机推荐