前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >jvm之类加载机制(五)

jvm之类加载机制(五)

作者头像
周杰伦本人
发布2022-10-25 16:17:09
1740
发布2022-10-25 16:17:09
举报
文章被收录于专栏:同步文章

类加载机制

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、解析、和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制 懒加载

类加载生命周期:

image-20200626131230779
image-20200626131230779
加载

加载源:

• 文件 class 文件 Jar文件 • 网络 • 计算生成二进制流 • 数据库 • 其他文件生成 jsp

验证

验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。

准备

准备阶段正式为类变量分配内存并设置变量的初始值。这些变量使用的内存都将在方法区中进行分配

代码语言:javascript
复制
class Sub{
public static int age = 10;
}
解析

解析阶段是虚拟机将常量池中的方法引用替换为直接引用的过程 • 类或者接口解析 • 字段解析 • 类方法解析 • 接口方法解析

初始化

初始化是执行<clinit>()方法的过程

<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问

代码语言:javascript
复制
package jvm;

/**
 * 编译报错非法向前引用
 */
public class Demo1 {

    static {
        i=0;
        System.out.println(i);
    }

    public static void main(String[] args) {

    }
    static int i =1;
}

编译器报错:非法向前引用

Java虚拟机必须保证一个类的<clinit>()方法在多线程环境中被正确地加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行完毕<clinit>()方法。如果在一个类的<clinit>()方法中有耗时很长的操作,那就可能造成多个进程阻塞 [2] ,在实际应用中这种阻塞往往是很隐蔽的

代码语言:javascript
复制
package jvm;

public class DeadLoopClass {

    static class Hello{
        static {
            System.out.println(Thread.currentThread().getName()+"init...");

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" start");

                Hello hello = new Hello();

                System.out.println(Thread.currentThread().getName()+" end");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" start");

                Hello hello = new Hello();

                System.out.println(Thread.currentThread().getName()+" end");
            }
        }).start();
    }
}

运行结果:

image-20200626150307312
image-20200626150307312
类加载器
image-20200626150616151
image-20200626150616151

双亲委派:

java.lang.ClassLoader的loadClass()方法

代码语言:javascript
复制
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                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();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

先检查请求加载的类型是否已经被加载过,若没有则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。假如父类加载器加载失败,抛出ClassNotFoundException异常的话,才调用自己的findClass()方法尝试进行加载

自定义类加载器:

  1. 定义一个类,继承ClassLoader
  2. 重写loadClass方法
  3. 实例化Class对象
代码语言:javascript
复制
package jvm;

import java.io.IOException;
import java.io.InputStream;

public class Demo4 {

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {

        //自定义classLoader
        ClassLoader myClassLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                //jvm.Demo4
                String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                InputStream inputStream = getClass().getResourceAsStream(fileName);
                if (inputStream == null) {
                    //名字不存在让父类加载
                    return super.loadClass(name);
                }
                try {
                    byte[] buff = new byte[inputStream.available()];
                    inputStream.read(buff);
                    return defineClass(name,buff,0,buff.length);
                } catch (IOException e) {
                     throw new ClassNotFoundException();
                }
            }
        };

        //加载自定义classloader的是哪个classloader
        System.out.println(myClassLoader.getClass().getClassLoader());
        System.out.println(Demo4.class.getClassLoader());
        //用自定义加载器加载demo4的实例
        Object oj= myClassLoader.loadClass("jvm.Demo4").newInstance();

        System.out.println(oj.getClass().getClassLoader());
        //对象是否为demo4的实例
        System.out.println(oj instanceof Demo4);
    }
}

运行结果:

image-20200626154536789
image-20200626154536789

只有被同一个类加载器加载的类才可能会相等

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-06-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 类加载机制
    • 加载
      • 验证
        • 准备
          • 解析
            • 初始化
              • 类加载器
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档