类加载子系统​

JAVA

JAVA语言是跨平台的语言,JVM虚拟机是跨语言的平台。JVM虚拟机并不关心字节码文件是由什么语言生成的。只有不同的语言通过编译器生成符合JVM虚拟机规范的字节码文件,JVM就能够运行。JAVA就是通过编译生成字节码文件,然后再在JVM上运行。因此JAVA语言可以一次编译到处运行。

虚拟机

虚拟机是一台虚拟计算机,是用来执行虚拟计算机指令的软件。虚拟机可以分为系统虚拟机和程序虚拟机。
如VMware就是系统虚拟机,他是对物理计算机的仿真。
Java虚拟机是程序虚拟机,他专门为执行单个计算机程序而设计,在java虚拟机中执行的指令我们称为Java字节码指令。Java技术的核心就是java虚拟机。它能够执行字节码文件,任何语言生成的字节码文件都可以共享java虚拟机带来的跨平台性、垃圾回收器,即时编译器。

java虚拟机

java虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为机器指令。JDK默认的虚拟机是HotSpot,采用解释器和即时编译器并存的架构。

JVM的架构模型

java编译器输入的指令基本上是基于栈的指令集架构。另一种是基于寄存器的指令集架构。

区别

基于栈式架构的特点:
1.设计和实现更简单,适用于资源受限的系统
2.避开了寄存器的分配难题:使用零地址指令方式分配
3.指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。指令集更小,编译器容易实现。
4.不需要硬件支持,可移植性更好,更好实现跨平台。
跨平台性,指令集小,指令多;性能比寄存器差。

基于寄存器架构的特点:
1.指令集架构依赖硬件,可移植性差。
2.性能优秀和执行更高效。
3.花费更少的指令去完成一项操作。
4.在大部分情况下基于寄存器架构的指令都以一地址指令,二地址指令和三地址指令为主。

JVM生命周期

启动

java虚拟机的启动是通过引导类加载器创建一个初始类来完成的,这个类是由虚拟机的具体实现指定的。

执行

程序开始时他才运行,程序结束时他就结束。执行java程序实际上是在执行一个java虚拟机的进程。

退出

1.程序执行结束
2.程序执行时遇到异常或者错误
3.操作系统出现错误
4.调用runtime类或System类的exit方法。或Runtime类的halt方法,并且java安全管理器也允许这次exit或halt操作

类加载子系统

类加载器子系统负责加载Class文件,Class文件在文件开头有特定的文件标识。类加载器只负责class文件的加载。加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面值和数字常量。(这部分常量信息是class文件中常量池部分的内存映射)
类加载器子系统包括三个步骤加载 链接 初始化。

在这里插入图片描述

加载阶段
  1. 通过一个类的全限定名获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
    jvm规范规定了只有两种类加载器引导类加载器和自定义类加载器( 所有派生于抽象ClassLoader的类加载器都划分为自定义类加载器)。自定义类加载器又包括了扩展类加载器,系统类加载器和用户自己定义的类加载器。加载器之前不是继承关系,而是一种组合/包含关系。
public class Test1 {
    public static void main(String[] args) {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
        //获取扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1b6d3586
        //获取引导类加载器
        ClassLoader bootstrapClassLoader  = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//null
        //获取Test1类的类加载器
        ClassLoader test1ClassLoader = Test1.class.getClassLoader();
        System.out.println(test1ClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
        //获取String类的类加载器
        ClassLoader stringClassLoader = String.class.getClassLoader();
        System.out.println(stringClassLoader);//null
    }
}


用户自定义的类是由系统类加载器加载的,而核心类是由引导类加载器加载的。并且我们不能获取到引导类加载器,因为它是由c/c++写的。

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

1.这个类加载器使用c/c++语言实现,嵌套在JVM内部
2.它用来加载Java的核心类库
3.没有父类加载器,不是继承自java.lang.ClassLoader
4.用来加载扩展类和系统类加载器,并指定为他们的父类加载器。

public class Test3 {
    public static void main(String[] args) {
        //获取启动类加载器加载的类的路径
        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        for (URL urL : urLs) {
            System.out.println(urL);
        }
    }
}

在这里插入图片描述

扩展类加载器

1.由java语言编写,是sun.misc.Launcher的一个内部类。
2.派生于ClassLoader类
3.父类加载器为BootStrap类加载器
4.从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。

public class Test4 {
    public static void main(String[] args) {
        //获取系统属性java.ext.dirs包含的值
        String path = System.getProperty("java.ext.dirs");
        for (String s : path.split(";")) {
        //打印扩展类加载的类路径
            System.out.println(s);
        }
    }
}

在这里插入图片描述

系统类加载器(应用程序加载类/AppClassLoader)

1.由java语言编写,是sun.misc.Launcher的一个内部类。
2.派生于ClassLoader类
3.父类加载器为扩展类加载器
4.他负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
5.是程序中默认的类加载器,一般来说,java应用的类都是由它来完成加载。

在这里插入图片描述

public class Test4 {
    public static void main(String[] args) {
        //获取系统属性java.ext.dirs包含的值
        String path = System.getProperty("java.class.path");
        for (String s : path.split(";")) {
        //打印扩展类加载的类路径
            System.out.println(s);
        }
    }
}

在这里插入图片描述

用户自定义类加载器

在java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。
使用用户自定义类加载器的目的
1.隔离加载类
2.修改类加载的方式
3.扩展加载源
4.防止源码泄露
实现方式

  1. 继承ClassLoader类重写loadClass()方法(JDK1.2前)
  2. 继承ClassLoader类并重写findClass()方法(JDK1.2后)
  3. 直接继承URLClassLoader类(如果没有太过复杂的需求)
ClassLoader抽象类提供的六个关键的方法
  1. loadClass
    此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从parentClassLoader中寻找,如仍然没找到,则从System ClassLoader中寻找,最后再调用findClass方法来寻找,如果要改变类的加载顺序,则可覆盖此方法。
  2. findLoadedClass
    此方法负责从当前ClassLoader实例对象的缓存中寻找已加载的类,调用的为native的方法。
  3. findClass
    此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。
  4. findSystemClass
    此方法负责从System ClassLoader中寻找类,如未找到,则继续从Bootstrap ClassLoader中寻找,如仍然未找到,则返回null。
  5. defineClass
    此方法负责将二进制的字节码转换为Class对象。
  6. resolveClass
    此方法负责完成Class对象的链接,如已链接过,则会直接返回。
loadClass(name, false) 源码
protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            //首先,检查类文件是否已经被加载过
            Class<?> c = findLoadedClass(name);
            //类文件未被加载过
            if (c == null) {
                //设定加载时间起始点
                long t0 = System.nanoTime();
                try {
                    //其父加载器不为空,说明父加载器为引导加载器
                    if (parent != null) {
                        //通过父加载器搜索name指定的类文件
                        c = parent.loadClass(name, false);
                    } else {
                        //父加载器为空,说明父加载器为启动加载器搜索类文件
                        //启用bootstrap class loader
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    //如果非空父加载器中找不到name指定的类文件
                    //抛出ClassNotFoundException异常
                }
                //父加载器或者启动加载器中都未搜索到该类文件
                if (c == null) {
                   //设定加载时间结点
                    long t1 = System.nanoTime();
                    //当前加载器内搜索name指定的类文件
                    c = findClass(name);
 
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            //resolve为true时处理类文件
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

ClassLoader解惑

获取ClassLoad的方法
  1. this.getClass.getClassLoader(); // 使用当前类的ClassLoader
  2. Thread.currentThread().getContextClassLoader(); // 使用当前线程的ClassLoader
  3. ClassLoader.getSystemClassLoader(); // 使用系统ClassLoader,即系统的入口点所使用的ClassLoader。
    system ClassLoader与根ClassLoader并不一样。JVM下system ClassLoader通常为App ClassLoader)
链接阶段

连接分为三个过程 验证 准备 解析
一 验证
检验class文件中包含的信息符合虚拟机要求,保证被加载类的正确性,不会危害虚拟机。
二准备
1.为类变量分配内存并且设置该类变量的默认初始值(0)。
2.这里不包含用final 修饰的static,因为final在编译的时候就会分配了,准备阶段会显示初始化。
3.这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到java堆中。
三:解析
将常量池内的符号引用转换为直接引用的过程。
符号引用就是一组符号来描述所引用的目标。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

初始化阶段

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

public class HelloApp {
    private static int a = 1;//prepare:a = 0 ---> initial : a = 1
    public static void main(String[] args) {
        System.out.println(a);
    }
}

在这里插入图片描述

简图

在这里插入图片描述

详细图

在这里插入图片描述

在这里插入图片描述

类加载器与类的加载过程

在这里插入图片描述

类加载过程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
class 类信息结构
class 初始时,会调用 <clinit> 中的指令,而class对象初始化时,会调用 <init> 中的指令,也就是class的构造器
在这里插入图片描述
虚拟机必须保证一个类的clinit方法在多线程下被同步加锁。

public class Test1 {
    public static void main(String[] args) {
        Runnable run = () -> {
            System.out.println(Thread.currentThread().getName()+"开始");
            Test2 t2 = new Test2();
            System.out.println(Thread.currentThread().getName()+"结束");
        };
        Thread t1 = new Thread(run,"线程1");
        Thread t2 = new Thread(run,"线程2");
        t1.start();
        t2.start();
    }
}
class Test2 {
    static {
        int a = 0;
        System.out.println(Thread.currentThread().getName()+"正在初始化Test2");
        boolean f = true;
        while (f){

        }
    }
}

在这里插入图片描述
只有线程1进入了static代码块,因此可以得出,虚拟机在执行clinit时确实会加锁。

案例

在这里插入图片描述

类加载器的分类

sun.misc.Launcher 类为虚拟机的入口
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

双亲委派模型

在这里插入图片描述

沙箱安全机制

在这里插入图片描述

类的主动使用和被动使用

在这里插入图片描述

补充

  • 类加载器加载class的方式

    1. 从本地加载.class文件
    2. 从网络加载.class文件
    3. 从本地加载.zip、.jar等归档文件
    4. 从专用数据库提取.class文件
    5. 从运行时计算生成加载.class文件(动态代理技术)
  • class 类文件开头的特殊标识
    在这里插入图片描述

软件

  • JVM上篇:
    ①JDK
    ②jprofiler10
    ③gcviewer-1.37-SNAPSHOT.jar
    ④JClassLib_windows.zip
    ⑤jclasslib-data-5.3.2.jar
    ⑥JProfiler v11.0.2 64位 免费特别版(附注册码+安装教程).zip
    ⑦jprofiler-plugin.jar
    ⑧MemoryAnalyzer-1.9.0.20190605-win32.win32.x86_64.zip
    ⑨PXBinaryViewerSetup.exe
    ⑩visualvm143.zip

  • JVM中篇:
    ①Beyond Compare文件对比工具.zip
    ②JClassLib_windows.rar
    ③PXBinaryViewerSetup.exe

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值