JVM类加载

1.内存结构概述

  1. 类加载器子系统

    1. Loading阶段,加载class文件
    2. Linking阶段
      1. 验证
      2. 准备
      3. 解析
    3. Initialization阶段,初始化
  2. 运行时数据区
    1. 方法区,把需要引用的类的字节码文件都会加载到方法区,存放类的信息,方法信息等等
    2. 堆(heap)区,创建java对象的主体都分配到堆空间中
    3. 虚拟机栈,每个线程一份
    4. pc寄存器(程序计数器),每个线程一份
    5. 本地方法栈,本地方法接口调用,使用本地方法栈
  3. 执行引擎
    1. 解释器
    2. 即时编译器
    3. 垃圾回收期

2.类加载子系统概述

  1. 类加载器子系统的作用

    ClassLoader负责从文件系统或者从网络中加载Class文件,至于它是否可以运行,有Execution Engine来决定。

  2. 加载类到哪里?

    加载的类的信息存放在内存空间的“方法区”,除了类的信息外,方法区还会存放运行时的常量的映射等信息。

3.类的加载过程

2.1加载

  1. 通过类的全类名获取Class文件的二进制流
  2. 将加载进来的字节流转化为方法区运行时的数据结构
  3. 在内存中生成一个这个类的对象Class对象,作为方法区这个类的各种数据的访问入口

2.2Linking

2.2.1验证(Verify)
  1. 确保Class文件的字节流中的信息正确,保证类加载的正确性
  2. 主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证
  3. 例如下面一个元数据的验证,java字节码文件初始文件都是CA FE BA BE

2.2.2准备(Prepare)
  1. 为类变量分配内存,并设置类变量的默认初始值,即零值
  2. final修饰的常量,在编译的时候就会分配值了
  3. 不会对实例变量进行初始化,类变量会分配在方法区中,实例变量会随着对象一起分配到堆空间中
2.2.3解析(Resolve)
  1. 将常量池内的符号引用,转化为直接引用。

    例:Object类的引用

2.3初始化(Initlization)

  1. 初始化阶段就是执行类构造器方法<clinit>()的过程,他和类的构造器<init>()是不一样的。

    此方法是javac编译器自动收集类中的所有静态变量的赋值动作和静态代码块中的语句合并而来

  2. 执行顺序按照编写的代码的顺序执行

  3. <init>为类的构造器方法,任何一个类声明以后,内部至少存在一个类的构造器

  4. 若被加载的类具有父类,JVM会保证子类的<clinit>()执行之前,父类的<clinit>()已经执行完毕

举例:

  1. 定义父类

    public class initTest {
    public static int A = 1;
    static {
    A=2;
    }
    }
  2. 定义子类

    public class Son extends initTest {
    public static int B =A;
    public static void main(String[] args) {
    System.out.println(B);
    }
    }
  3. 编译运行

  4. 查看字节码文件,可以看到加载B的时候先调了父类的clinit,加载了A

  5. 虚拟机必须保证一个类的<clinit>()方法在多线程的情况下被同步加锁

举例:

  1. 编写被调用的class

    public class initTest {
    static {
    //这里使用循环,目的是为了卡住<clinit>()方法,让别的线程等待
    if (true){
    System.out.println(Thread.currentThread().getName()+"进来了");
    while (true){
    }
    }
    }
    }
  2. 编写两个线程,两个线程都加载initTest,因为对于JVM来说,同一个类只会被加载一次,加载以后类信息等存放在方法区中

    public class Son {
    public static void main(String[] args) {
    Runnable r = () ->{
    System.out.println(Thread.currentThread().getName()+"开始");
    initTest initTest = new initTest();
    System.out.println(Thread.currentThread().getName()+"结束");
    }; Thread t1 = new Thread(r,"线程1");
    Thread t2 = new Thread(r,"线程2"); t1.start();
    t2.start();
    }
    }
  3. 编译运行,第一个加载initTest的线程会进去,而另一个加载initTest的线程则会在后面等待

4.类加载器的分类

JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader),和自定义类加载器(User-Defined ClassLoader)

JVM将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器

可能你会疑惑,拓展类加载器,和系统类加载器是什么类型,其实他们都是派生于ClassLoader类的,JVM均视为自定义类加载器

引导类加载器,拓展类加载器,系统类加载器,用户自定义加载器,这四者的关系是包含关系。不是子父继承的关系

3.1虚拟机自带的加载器

3.1.1引导类加载器(Bootstrap ClassLoader)
  1. 这个类加载器使用c/c++实现,嵌套与JVM内部
  2. 它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resource.jar或者 sun.boot.class.path 路径下的内容),用于提供JVM自身需要的类
  3. 它并不继承与java.lang.ClassLoader,c语言编写,无父加载器
  4. 它用来加载拓展类加载器和系统类加载器(应用程序类加载器),并指定Bootstrap为他们的父加载器
  5. 出于安全考虑,Bootstrap ClassLoader只加载包名为java,javax,sun等开头的类
3.1.2拓展类加载器(Extension ClassLoader)
  1. java语言编写,为sun.misc.Launcher的一个内部类
  2. 派生于ClassLoader类
  3. 父加载器为引导类加载器
  4. 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK安装目录的jre/lib/ext子目录下加载类库,如果用户创建的JAR放在此目录下,也会由拓展类加载器自动加载。
3.1.3系统类加载器(应用程序类加载器 AppClassLoader)
  1. java语言编写,为sun.misc.Launcher的内部类
  2. 派生于ClassLoader类
  3. 父类加载器为拓展类加载器
  4. 它负责加载环境遍历classpath或者是系统属性java.class.path指定路径下的类库
  5. 该类加载器是程序中默认的类加载器,一般来说,我们编写的Java应用的类都是由它来完成加载
  6. 通过ClassLoader#getSystemClassLoader()的方法,可以获取到该类加载器

举例:

import sun.misc.Launcher;

import java.net.URL;
import java.security.Provider; public class ClassLoaderTestFyp {
public static void main(String[] args) {
// 获取引导类加载器加载的路径
System.out.println("====================引导类加载器加载的路径==========================");
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for (URL urL : urLs) {
System.out.println(urL.toExternalForm());
}
//从上面的路径中随意选择一个类,看一下这个类的类加载器是什么
System.out.println("====================获取引导类加载器===============================");
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println(classLoader); //应该为null,因为引导类加载器是c和c++编写,我们无法获取到 // 拓展类加载器
System.out.println("====================拓展类加载器加载的路径==========================");
String extDirs = System.getProperty("java.ext.dirs");
System.out.println(extDirs); // 系统类加载器classpath
System.out.println("====================系统类加载器加载的路径==========================");
String classpath = System.getProperty("java.class.path");
for (String s : classpath.split(";")) {
System.out.println(s);
}
}
}
3.1.4用户自定义类加载器

为什么要自定义类的加载器:

  1. 隔离加载类,(主要解决不同版本jar包的版本冲突)
  2. 修改类加载的方式
  3. 扩展加载源
  4. 防止源码泄露

自定义类加载器实现步骤:

  1. 继承java.lang.ClassLoader抽象类
  2. 在jdk1.2之后,建议把自定义的类加载器逻辑写在findclass当中
  3. 如果没有太过于复杂的需求,可以继承URLClassLoader,可以避免自己去读取字节码流的方式,避免自己写URLClassLoader

5.ClassLoader介绍

ClassLoader是一个抽象类,其后所有的类加载器都继承自ClassLoader(除引导类加载器)

获取ClassLoader的途径
  • 方式一:获取当前ClassLoader
clazz.getClassLoader()
  • 方式二:获取当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader()
  • 方式三:获取系统的ClassLoader
ClassLoader.getSystemClassLoader()
  • 方式四:获取调用者的ClassLoader
DriverManager.getCallerClassLoader()

6.双亲委派机制

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

工作原理
  1. 如果一个类加载器收到了类加载的请求,它并不会先自己去加载这个类,而是把这个请求委托给父类的加载器去执行
  2. 如果父类加载器还存在其父类加载器,则依次向上委托,请求最终将到达顶层的引导类加载器
  3. 如果父类加载器可以完成类加载的任务,则就成功返回,如果父类加载器无法完成类加载的任务,则子加载器才会自己尝试去加载类,这就是双亲委派模式。

举例:

当我们加载jdbc.jar包用于实现数据库连接的时候,首先我们要知道的是jdbc.jar是基于SPI接口进行实现的,所以在加载的时候会进行双亲委派,从引导类加载器加载SPI核心的类,然后再加载SPI接口类,接着在进行反向委派,通过线程上下文类加载器进行实现类jdbc.jar的加载。

优势
  1. 避免类的重复加载

  2. 保护程序安全,防止核心的API被随意的修改

    举例:假设我们自己实现了一个java.lang.String,如果给这个类写个main方法,去运行,是不行的,因为当类加载器收到类加载请求的时候会向上委托,会加载核心的String类,而核心的String类无此方法,所以会报错为方法找不到,这也称为沙箱安全机制。

7.其他

  1. 如何判断两个Class对象是否相等

    在JVM中表示两个Class对象是否为同一个类,有两个必要的条件

    • 类的全类名必须相同
    • 加载这个类的ClassLoader必须相同

    也就是说,即使两个类对象来自同一个class文件,但是加载他们的类加载器不同,那这两个对象也是不想等的。

8.类的主动使用和被动使用

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

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

  • 创建类的实例
  • 访问某个类或接口的静态变量,或者对这个静态变量赋值。
  • 调用类的静态方法
  • 反射
  • 初始化一个类的子类
  • Java虚拟机启动的时候被标明为启动类的类
  • JDK 7 开始提供的动态语言的支持

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

JVM上篇:类加载子系统的更多相关文章

  1. JVM解毒——类加载子系统

    带着问题,尤其是面试问题的学习才是最高效的.加油,奥利给! 点赞+收藏 就学会系列,文章收录在 GitHub JavaEgg ,N线互联网开发必备技能兵器谱 直击面试 看你简历写得熟悉JVM,那你说说 ...

  2. jvm (一)jvm结构 & 类加载 & 双亲委托模型

    参考文档: jvm内幕-java虚拟机详解:http://www.importnew.com/17770.html 常量池:https://www.jianshu.com/p/c7f47de2ee80 ...

  3. 【JVM之内存与垃圾回收篇】类加载子系统

    类加载子系统 概述 完整图如下: 如果自己想手写一个 Java 虚拟机的话,主要考虑哪些结构呢? 类加载器 执行引擎 类加载器子系统作用 类加载器子系统负责从文件系统或者网络中加载 Class 文件, ...

  4. Java JVM——2.类加载器子系统

    概述 类加载器子系统在Java JVM中的位置 类加载器子系统的具体实现 类加载器子系统的作用 ① 负责从文件系统或者网络中加载.class文件,Class 文件在文件开头有特定的文件标识. ② Cl ...

  5. JVM笔记 -- 来,教你类加载子系统

    类加载子系统 类文件首先需要经过类加载子系统,进行加载,进类信息等加载到运行时数据区,生成Klass的实例. 在类加载子系统中有以下3个阶段操作(广义上的加载): 加载阶段 Bootstrap Cla ...

  6. <JVM上篇:内存与垃圾回收篇>02-类加载子系统

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  7. JVM_02 类加载子系统

    JVM细节版架构图 本文针对Class Loader SubSystem这一块展开讲解类加载子系统的工作流程 类加载子系统作用 1.类加载子系统负责从文件系统或者网络中加载class文件,class文 ...

  8. JVM虚拟机 类加载过程与类加载器

    目录 前言 类的生命周期 类加载过程 加载 连接 验证 准备 解析 初始化 类加载器 三大类加载器 双亲委派模型 概念 为什么要使用双亲委派模型 源码分析 反双亲委派模型 参考 前言 类装载器子系统是 ...

  9. JVM 的执行子系统

    JVM 的执行子系统. 一.Class类文件结构 1. JVM的平台无关性 与平台无关性是建立在操作系统上,虚拟机厂商提供了许多可以运行在各种不同平台的虚拟机,它们都可以载入和执行字节码,从而实现程序 ...

随机推荐

  1. Webpack之 webpack-dev-server 中的 contentBase配置及作用

    contentBase:主要是指定静态资源的根目录的.  

  2. MySQL 5.7.19 简易安装、卸载教程

    前言:传统的 exe 文件安装的MySQL,安装后特别难卸载,而且一旦处理不好,就容易出错,想再安装别的版本也不容易.因为这种方式的安装,虽然是不断的下一步,但是卸载的时候需要处理很多,在本文最后,有 ...

  3. elasticsearch查询之大数据集分页性能测试

    一.测试环境 python 3.7 elasticsearch 6.8 elasticsearch-dsl 7 安装elasticsearch-dsl pip install elasticsearc ...

  4. cross-env 作用

    是什么 运行跨平台设置和使用环境变量的脚本 出现原因 当您使用NODE_ENV =production, 来设置环境变量时,大多数Windows命令提示将会阻塞(报错). (异常是Windows上的B ...

  5. 获取联系人列表的时候contact_id出现null的值

    因为删除联系人只是把它的contact_id设置为null,所以只要手机上删除过联系人id就会有null,用之前先判断是不是null就好了

  6. VUE3 之 动态组件 - 这个系列的教程通俗易懂,适合新手

    1. 概述 暗示效应告诉我们: 巧妙的暗示会在不知不觉中剥夺我们的判断力,对我们的思维形成一定的影响,造成我们行为的些许改变或者偏差. 例如你的朋友说你脸色不太好,是不是病了,此时,你可能就会感觉浑身 ...

  7. 《Effective Python》笔记——第1章 用Pythonic方式来思考

    一. 遵循PEP8风格指南. PEP8是对python代码格式而编订的风格指南.地址:https://www.python.org/dev/peps/pep-0008/ 个人觉得不一定完全按照PEP8 ...

  8. python基础——生成器与迭代器

    生成器 def func(): print("111") yield 1 print("222") yield 3 print("333") ...

  9. linux用户用户组与ACL

    使用者ID:UID与GID 在使用Linux的过程中,经常会遇到各种用户ID(user identifier, UID)和组ID(group identifier, GID),Linux也是通过对这些 ...

  10. Linux运维-常用操作-培训用例

    一.服务器环境 Centos 7.9 二.常用连接工具(免费) 1.Finalshell 2.MobaXterm 3.Putty + WinSCP 三.Linux  系统目录结构 /bin :是 Bi ...