IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> JVM-类加载器 -> 正文阅读

[Java知识库]JVM-类加载器

目录

类加载的各个阶段

一、加载

?二、连接

三、初始化

类加载器

Bootstrap ClassLoader:

Extension ClassLoader?

?双亲委派

自定义类加载器



类加载的各个阶段

一、加载

将类的字节码载入方法区(1.8后为元空间,在本地内存中)中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 ?eld 有:

  • _java_mirror 即 java 的类镜像,例如对 String 来说,它的镜像类就是 String.class,作用是把 klass 暴露给 java 使用
  • _super 即父类
  • _?elds 即成员变量
  • _methods 即方法
  • _constants 即常量池
  • _class_loader 即类加载器
  • _vtable 虚方法表
  • _itable 接口方法

如果这个类还有父类没有加载,先加载父类

加载和链接可能是交替运行的

?二、连接

1、验证

验证类是否符合 JVM规范,安全性检查。
UE 等支持二进制的编辑器修改 HelloWorld.class 的魔数,在控制台运行就会出现以下报错现象

2、准备

  • static 变量分配空间,设置默认值
  • static 变量在 JDK 7 之前存储于 instanceKlass 末尾,从 JDK 7 开始,存储于 _java_mirror 末尾
  • static 变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
  • 如果 static 变量是 final的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶 段完成
  • 如果 static 变量是 final的,但属于引用类型,那么赋值也会在初始化阶段完成

3、解析

将常量池中的符号引用解析为直接引用
一个未解析的类在常量池中会被标明未unresolvedClass,这种情况下是找不到他在内存中的地址的,也就是符号引用

当解析就不会被标记为unresolvedClass,并且能够找到他在内存中的地址

?

三、初始化

初始化即调用 <cinit>()V ,虚拟机会保证这个类的『构造方法』的线程安全
  • 发生的时机
  1. 概括得说,类初始化是【懒惰的】
  2. main 方法所在的类,总会被首先初始化
  3. 首次访问这个类的静态变量或静态方法时
  4. 子类初始化,如果父类还没初始化,会引发
  5. 子类访问父类的静态变量,只会触发父类的初始化
  6. Class.forName
  7. new 会导致初始化
  • 不会导致类初始化的情况
  1. 访问类的 static final静态常量(基本类型和字符串)不会触发初始化
  2. 类对象.class 不会触发初始化
  3. 创建该类的数组不会触发初始化
  4. 类加载器的 loadClass 方法
  5. Class.forName 的参数 2 false

类加载器


对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个 Java 虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等!
?

名称
加载哪的类
说明
Bootstrap ClassLoader
JAVA_HOME/jre/lib
无法直接访,c++层次的,显示为 null
Extension ClassLoader
JAVA_HOME/jre/lib/ext
上级为 Bootstrap null
Application ClassLoader
classpath
上级为 Extension
自定义类加载器
自定义
上级为 Application

Bootstrap ClassLoader:

先介绍一个jvm指令:-Xbootclasspath 表示定义启动类加载的路径

  • -Xbootclasspath:<new bootclasspath>? 表示覆盖之前的路径(JAVA_HOME/jre/lib)
  • -Xbootclasspath/a:<new bootclasspath> 表示追加一个新的路路径在后面
  • -Xbootclasspath/p:<new bootclasspath> 表示在前面追加一个新路径

  • 1、先创建一个类F
public class F {
        static {
            System.out.println("我被初始化了");
        }
}
  • 2、然后在main方法中加载类
public class asdasd {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> f = Class.forName("F");
        ClassLoader classLoader = f.getClassLoader();
          System.out.println("classLoader:"+classLoader);
    }

}
  • 3、接着编译并输出查看classLoader

  • 4、 由此看到,启动类加载器并不能访问,是一个null值

Extension ClassLoader?

首先我自己创建了一个F类

?然后这时候打包这个类为jar包,放入jdk/jre/lib/ext目录下

然后开始main方法,加载F类,可以看到是由拓展类类加载器进行加载的

?双亲委派

源码:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 1检查该类是否已经加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 2、有上级的话,委派上级 loadClass
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                  // 3. 如果没有上级了(ExtClassLoader),则委派 BootstrapClassLoader
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
             
            }

            if (c == null) {
                 long t1 = System.nanoTime();
            // 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载
              
                c = findClass(name);

                // 5、记录耗时
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

?过程

1. sun.misc.Launcher$AppClassLoader //1 处, 开始查看已加载的类,结果没有
2. sun.misc.Launcher$AppClassLoader // 2 处,委派上级sun.misc.Launcher$ExtClassLoader.loadClass()
3. sun.misc.Launcher$ExtClassLoader // 1 处,查看已加载的类,结果没有
4. sun.misc.Launcher$ExtClassLoader // 3 处,没有上级了,则委派 BootstrapClassLoader
查找
5. BootstrapClassLoader 是在 JAVA_HOME/jre/lib 下找 H 这个类,显然没有
6. sun.misc.Launcher$ExtClassLoader // 4 处,调用自己的 fifindClass 方法,是在
JAVA_HOME/jre/lib/ext 下找 H 这个类,显然没有,回到 sun.misc.Launcher$AppClassLoader
// 2
7. 继续执行到 sun.misc.Launcher$AppClassLoader // 4 处,调用它自己的 fifindClass 方法,在
classpath 下查找,找到了

自定义类加载器


使用场景

  • 想加载非 classpath 随意路径中的类文件
  • 通过接口来使用实现,希望解耦时,常用在框架设计
  • 这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于 tomcat 容器

步骤

  1. 继承 ClassLoader 父类
  2. 要遵从双亲委派机制,重写 ?ndClass 方法
  3. 不是重写 loadClass 方法,否则不会走双亲委派机制
  4. 读取类文件的字节码
  5. 调用父类的 de?neClass 方法来加载类
  6. 使用者调用该类加载器的 loadClass 方法

自定义类加载器代码:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        //指定类加载的路径
        String path = "d:\\" + name + ".class";
        try {
            //创建一个字节输出流
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            //将类文件拷贝为字节输出流
            Files.copy(Paths.get(path), os);
            //得到一个字节数组
            byte[] bytes = os.toByteArray();
            //将字节数组转换成class文件
            return defineClass(name, bytes, 0, bytes.length);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException("类文件未找到", e);
        }

    }
}

调用


       MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> f = myClassLoader.loadClass("F");
     

即时编译器优化:

对于java中的每一条指令,都会先通过虚拟机进行解释成机器码,然后再进行执行。但是如果对一些重复了很多次很多次的热点数据指令,一直在做重复的解释操作,无疑会耗费很多时间

JIT编译的交互过程:具体请参考

当一个方法被调用时,会先检查该方法是否存在被JIT 编译过的版本:如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器值之和是否超过方法计数器的阈值。若超过了,则将会向即时编译器提交一个该方法的代码编译请求;

如果不做任何设置:执行引擎并不会同步等待编译请求完成,而是继续进入解释器按照解释方式执行字节码,直到提交的请求被编译器编译完成。当编译工作完成之后,这个方法的调用入口地址就会被系统自动改写成新的,下一次调用该方法时就会使用已变异的版本,整个JIT 编译的交互过程
?

具体包含以下几种优化

逃逸分析

分析对象的动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递给其他方法,称为方法逃逸。甚至还有可能被外部线程访问到,比如赋值给类变量或可以在其他线程中访问到的实例变量,称为线程逃逸。

优化方案

如果能证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,就可以为这个变量进行一些高效的优化:如:栈上分配、同步消除、标量替换等。

  • 栈上分配

如果确定一个对象不会逃逸出方法之外,那让这个对象在栈上分配内存将是一个不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁;

  • 同步消除

线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,那这个变量的读写肯定不会有竞争,对这个变量实施的同步措施就可以消除掉;

  • 标量替换
  1. 标量定义:它是指一个数据已经无法再分解成更小的数据来表示了,java虚拟机中的原始数据类型都不能再进一步分解,他们就可以称为标量;
  2. 聚合量定义:如果一个数据可以继续分解,它被称为聚合量;
  3. 标量替换:如果把一个java对象拆散,根据程序访问的情况,将其使用到的成员变量恢复原始类型来访问就叫做标量替换;那程序真正执行的时候,将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。

例如:

public static void main(String[] args) {
   alloc();
}

private static void alloc() {
   Point point = new Point(1,2);
   System.out.println("point.x="+point.x+"; point.y="+point.y);
}
class Point{
    private int x;
    private int y;
}

point方法并没有发生方法逃逸,那么就可以优化为:

private static void alloc() {
   int x = 1;
   int y = 2;
   System.out.println("point.x="+x+"; point.y="+y);
}

方法内联

对于一个方法的执行,会在线程的栈内存中压入栈帧内存,当方法执行完毕后又会将栈帧内存弹出,如果一个方法非常的简单,但是又重复多次执行(热点数据),也就是多次对栈帧的压栈和弹出,虚拟机认为有优化的空间,那么就直接将方法的内容直接放到调用的位置上,从而避免的压栈出栈的开销

例如:

private int add4(int x1, int x2, int x3, int x4) {  
        return add2(x1, x2) + add2(x3, x4);  
    }  

    private int add2(int x1, int x2) {  
        return x1 + x2;  
    }  

优化为:

private int add4(int x1, int x2, int x3, int x4) {  
        return x1 + x2 + x3 + x4;  
    }  

当启用方法内联的时候,如果在一个方法中多次使用一个成员变量,那么方法在读取成员变量的时候,会用一个局部变量来存储成员变量,这样再以后使用这个成员变量的时候只需要再本地查找局部变量就可以

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-12-01 17:34:35  更:2021-12-01 17:36:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:22:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码