1.JVM概述、2.类的加载机制

JVM概述与类的加载机制
JVM 内存模型
对象逃逸分析、JVM 内存分配和回收策略
垃圾回收算法详解、垃圾收集器全解
JVM 调优

1 概览

1.1 jdk 体系结构

在这里插入图片描述

java 虚拟机阵营:Sun HotSpot VM, BEA JRockit VM, IBM, J9 VM, Azul VM, Apache Harmony, Googole Dalvik VM, Microsoft JVM,,,,,,

jvm 再向底层结构图如下:
在这里插入图片描述

1.2 java 虚拟机

JVM(Java Virtual Machine,java 虚拟机):指以软件的方式模拟具有完整硬件系统功能,运行在一个完全隔离环境中的完整计算机系统,是物理机的软件实现。常用的虚拟机有 VMWare,Virtual Box,Java Virtual Machine

jvm 内存结构图

在这里插入图片描述

2 类加载机制

2.1 类加载过程

类加载:类加载器将 class 文件加载到虚拟机的内存。

类加载流程图如下:

在这里插入图片描述

  • 类加载:类加载器将 class 文件加载到虚拟机的内存。

  • 加载:在硬盘上查找并通过 IO 读入字节码文件。

  • 连接:执行校验,准备,解析步骤。

  • 校验:校验字节码的正确性(-Xverifynone 设置该参数关闭大部分的验证)。

  • 准备:给类的静态变量分配内存,并赋予默认值。

  • 解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main() )替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的符和引用替换为直接引用。

  • 初始化:对类的静态变量初始化为指定的值,执行静态代码块。

我们重点来看下“解析”。

我们创建一个 Math 类,代码如下:

public class Math {
    public static final Integer CONSTANT = 666;
    public static User user = null;
    // 一个方法对应一块栈帧内存区域
    public int compute(){
        int a =1;
        int b =2;
        int c = (a + b) * 10;
        return c;
    }
    public static void main(String[] args) {
        Math math = new Math();
        math.compute();
        //        User user = new User();
    }
}

我们运行 main 方法后,进入 Math.class 所在文件夹,进入 Terminal 终端,对 Math.class 进行翻汇编,并把结果保存为 Math.txt 文件(javap -v Math.class > Math.txt),如下图:

在这里插入图片描述

接下来我们打开这个 Math.txt 文件,内容如下:
在这里插入图片描述

上面红色方框内,#3 就是符号引用,它用到了#2 和#35,解析这步骤要做的就是把间接引用替换为直接引用,例如,把 Methodred 的引用替换为#2 和#35 引用的目标。这整个过程是在类加载期间完成的,这就是静态链接。而动态链接是在程序运行期间完成的。

2.2 类加载器

2.2.1 java 中有如下几种类加载器

  • 启动类加载器:负责加载支撑 JVM 运行的位于 JRE 的 lib 目录下的核心类库,比如,rt.jar,charsets.jar 等。

  • 扩展类加载器:负责加载支撑 JVM 运行位于 JRE 的 lib 目录的 ext 扩展目录中的 JAR 类包。

  • 应用程序类加载器:负责加载 ClassPath 路径下的类包,主要就是加载你自己写的那些类。

  • 自定义加载器:负责加载用户自定义路径下的类包。

我们来用代码测试下看看:

/**
 * 测试类加载器
 */
public class TestJDKClassLoadr {
    public static void main(String[] args) {
        // 底层用 C 语言实现的,所以找不到
        System.out.println(String.class.getClassLoader());
    System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(TestJDKClassLoadr.class.getClassLoader().getClass().getName());
        System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());
    }
}

输出如下:

在这里插入图片描述

2.2.2 自定义类加载器

我们自己来写一个类加载器 MyClassLoader,加载我们的类 User1,User1.java 代码如下:

package com.example.demo.vo;
public class User1 {
    public void sout(){
        System.out.println("=================自己的类加载器加载类调用方法=====================");
    }
}

MyClassLoader.java 代码如下:

/**
 * 自定义类加载器
 * 准备把类 User1{@link com.example.demo.vo.User1}的编译后文件 User1.class 文件复制一份放到 C:/temp/com/example/demo/vo 目录下
 */
public class MyClassLoader extends ClassLoader {
    private String classPath;
    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }
    
    /**
     * 读入文件保存成字节数
     * @param name
     * @return
     * @throws Exception
     */
    private byte[] loadByte(String name) throws Exception{
        name = name.replaceAll("\.", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }
    
    /**
     * 重写父类的 findClass 方法
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
    /**
     * 从 classPath + name 即,C:/temp/com/example/demo/vo 下找到 User1.class
     * 加载到包 com.example.demo.vo 中 即,com.example.demo.vo.User1
     * 如果 target 下的包 com.example.demo.vo 里面有 User1.class 了,那么就会用类加载器
     * ApplicationClassLoader 去加载 User1.class 了
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader("c:/temp");
        Class clazz = classLoader.loadClass("com.example.demo.vo.User1");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

运行后,输出如下:
在这里插入图片描述

2.2.3 双亲委派机制

2.2.3.1 介绍

双亲委派机制

加载某个类时会先委托父加载器去加载,找不到再委托上层父加载器加载,如果所有的父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。

类加载的双亲委派机制如下图:

在这里插入图片描述

上图的类加载器加载顺序是什么呢?

在实际应用中,我们很少自己写类加载器。假如,现在有一个.class 文件要加载。在没有自定义类加载器的情况下。

应用程序类加载器先不加载,它向上委托给其父加载器让扩展类加载器加载;

到了扩展类加载器那儿,它也先不加载,再向上委托给其父加载器让启动类加载器去加载;

到了启动类加载器那儿,它如果可以加载就把.class 文件加载到 jvm 中,加载完成。如果它加载不了,就又返回给子加载器扩展类加载器加载;

如果返回到扩展类加载器这儿,扩展类加载器可以加载了,加载完成。如果它加载不了,就又返回给子加载器应用程序类加载器去加载。

如果存在自定义类加载器,原理也是一样的。

我们来看个例子:

编写一个 String 类,和原有的 String 类在同一个包名,如下:

package java.lang;
public class String {
    public static void main(String[] args) {
        System.out.println("==================My String Class ===============");
    }
}

我们运行该类,看能不能打印出我们编写字符串“==================My String Class ===============”。控制台打印如下:

在这里插入图片描述

可以看到她说 java.lang.String 类中找不到 main 方法,可是我们明明写了啊。其实是这样的,

当运行时,加载 String.class 时,根据双亲委派机制,一直向上委托,启动类加载器发现 rt.jar 中有 String.class 文件,就加载了。所以运行时用到的 String.class 就不是我们编写的,用到是 jdk 中原有的 String.class 文件了,这个文件中没有 main 方法,所以就会报找不到 main 方法了。

2.2.3.2 原理

为什么要设计双亲委派机制?

沙箱安全机制:自己写的 String.class 类不会被加载,这样可以防止核心 API 库被随意篡改。

避免类的重复加载:当父加载器已经加载了该类时,就没有必要子加载器再加载一次,保证被加载类的唯一性。

我们以应用类加载器 AppClassLoader 展开分析:

找到 AppClassLoader 的源码,即 AppClassLoader.class,找到 loadClass 方法,如下:

在这里插入图片描述

我们点进去这个 super.loadClass(var1, var2),如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rJclEfcQ-1611561069567)(https://uploader.shimo.im/f/B4CZ9EJg14p0Iib0.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

接着看 c=findClass(name);自己去加载这块。我们再 AppClassLoader 中寻找 findClass 方法,发现没有找到,那么我们去其父类 URLClassLoader 中去寻找,源码如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值