【JVM】Java类加载器设计原理(ClassLoader源码解读/ SPI机制/ 绕开双亲委派/ 常见Java虚拟机)

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 【JVM】Java类加载器设计原理(ClassLoader源码解读/ SPI机制/ 绕开双亲委派/ 常见Java虚拟机),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文


1. 什么是类加载器

将我们的class文件读取到内存中

2. 类加载器加载的过程

类加载器加载我们的class文件,并且经历过验证、准备、解析,在初始化我们该类。

3. Class文件读取来源

1.本地磁盘文件 java源代码编译的class文件
2.通过网络下载的class文件
3.War、Jar解压的class文件
4.从专门的数据库中读取的class文件
5.使用java cglib、动态代理生成的代理类class文件
Jvm虚拟机中 通过 类加载器(用户可以自定义类加载器)

4. 类加载器的分类

1.启动(Bootstrap)类加载器:加载JVM自身工作需要的类,它由JVM自己实现。它会加载

J

A

V

A

H

O

M

E

/

j

r

e

/

l

i

b

C

2.

E

x

t

e

n

s

i

o

n

J

V

M

s

u

n

.

m

i

s

c

.

L

a

u

n

c

h

e

r

E

x

t

C

l

a

s

s

L

o

a

d

e

r

E

x

t

C

l

a

s

s

L

o

a

d

e

r

E

x

t

C

l

a

s

s

L

o

a

d

e

r

J

A

V

A

H

O

M

E

/

j

r

e

/

l

i

b

/

e

x

t

S

y

s

t

e

m

.

g

e

t

P

r

o

p

e

r

t

y

(

j

a

v

a

.

e

x

t

.

d

i

r

s

)

J

a

v

a

3.

A

p

p

C

l

a

s

s

L

o

a

d

e

r

s

u

n

.

m

i

s

c

.

L

a

u

n

c

h

e

r

JAVA_HOME/jre/lib下的文件 底层是C语言实现 2.扩展(Extension)类加载器:它是JVM的一部分,由sun.misc.LauncherExtClassLoader实现,他会加载ExtClassLoader实现,他会加载ExtClassLoader实现,他会加载JAVA_HOME/jre/lib/ext目录中的文件(或由System.getProperty(“java.ext.dirs”)所指定的文件)。 底层是Java实现 3.(应用)AppClassLoader 类加载器:应用类加载器,我们工作中接触最多的也是这个类加载器,它由sun.misc.Launcher

JAVAHOME/jre/libC2.ExtensionJVMsun.misc.LauncherExtClassLoaderExtClassLoaderExtClassLoaderJAVAHOME/jre/lib/extSystem.getProperty(java.ext.dirs)Java3.AppClassLoadersun.misc.LauncherAppClassLoader实现。他加载我们工程目录classpath下的class及jar包 底层是java实现
4.自定义类加载器: 也就是用户自己定义的类加载器
在这里插入图片描述
Launcher 源码解读

Launcher.ExtClassLoader var1;
try {
// 获取到我们扩展类加载器
    var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
    throw new InternalError("Could not create extension class loader", var10);
}

try {
//获取到我们应用类加载器
    this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
    throw new InternalError("Could not create application class loader", var9);
}
// 当前程序启动的线程 默认的 ClassLoader 应用类加载器
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
    SecurityManager var3 = null;
    if (!"".equals(var2) && !"default".equals(var2)) {
        try {
            var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
        } catch (IllegalAccessException var5) {
        } catch (InstantiationException var6) {
        } catch (ClassNotFoundException var7) {
        } catch (ClassCastException var8) {
        }
    } else {
        var3 = new SecurityManager();
    }

    if (var3 == null) {
        throw new InternalError("Could not create SecurityManager: " + var2);
    }

    System.setSecurityManager(var3);


5. 那些操作会初始化类加载器

类的主动使用:

  1. 调用类的静态方法
  2. invokeStatic 调用静态方法
  3. Main
  4. New
  5. Class.formname
  6. 子类初始化一定会初始化父类
    初始化一个类,那么一定会触发类加载器
    但是类加载器加载了该类,但是该类不一定初始化。

6. 类加载器的双亲委派机制

首先在我们类加载器分为四种 自定义类加载器、应用类加载器、扩展类加载器、启动类加载器。
当一个类加载器收到请求之后,首先会依次向上查找到最顶层类加载器(启动类加载器),依次向下加载class文件,如果已经加载到class文件,子加载器不会加继续加载该class文件。

6.1 双亲委派机制机制的好处

目的就是为了防御开发者为定义的类与jdk定义源码类产生冲突问题,保证该类在内存中的唯一性。

7. ClassLoader源码解读

7.1 Launcher类源码解读

public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
//获取我们的扩展类加载器
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
// 获取我们的应用类加载器
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
    // 默认设置我们的类加载器是为应用类加载器
    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    if (var2 != null) {
        SecurityManager var3 = null;
        if (!"".equals(var2) && !"default".equals(var2)) {
            try {
                var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
            } catch (IllegalAccessException var5) {
            } catch (InstantiationException var6) {
            } catch (ClassNotFoundException var7) {
            } catch (ClassCastException var8) {
            }
        } else {
            var3 = new SecurityManager();
        }

        if (var3 == null) {
            throw new InternalError("Could not create SecurityManager: " + var2);
        }

        System.setSecurityManager(var3);
    }

}


7.2 双亲委派机制源码分析

  1. ClassLoader.getSystemClassLoader().loadClass()
// 查询缓存中是否有缓存 该class
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();
// 如果父加载器(扩展和启动类加载器都没有加载class,则使用当前(应用类加载器加载))
        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;


7.3 如何自定义一个类加载器

public class DemoClassLoader extends ClassLoader {

    private File fileObject;

    public DemoClassLoader(File fileObject) {
        this.fileObject = fileObject;
    }

    public void setFileObject(File fileObject) {
        this.fileObject = fileObject;
    }

    public File getFileObject() {
        return fileObject;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = getClassFileBytes(this.fileObject);
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    /**
     * 从文件中读取去class文件
     *
     * @throws Exception
     */
    private byte[] getClassFileBytes(File file) throws Exception {
        //采用NIO读取
        FileInputStream fis = new FileInputStream(file);
        FileChannel fileC = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel outC = Channels.newChannel(baos);
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        while (true) {
            int i = fileC.read(buffer);
            if (i == 0 || i == -1) {
                break;
            }
            buffer.flip();
            outC.write(buffer);
            buffer.clear();
        }
        fis.close();
        return baos.toByteArray();
    }


}


代码测试:

Class<?> aClass = new DemoClassLoader(new File("D:\\code\\com\\demo\\DemoEntity.class"))
        .loadClass("com.demo.DemoEntity");
Object o = aClass.newInstance();
System.out.println(o.getClass().getClassLoader());

7.4 自定义类加载器

ClassLoader 类加载器中 双亲委派机制 核心源码部分

findLoadedClass()— 首先,检查类是否已经加载
parent.loadClass(name, false); 读取到parent.loadClass

findBootstrapClassOrNull 使用启动类加载器读取
findClass 扩展和应用类加载器、自定义类加载器

public class DemoClassLoader extends ClassLoader {

    private File fileObject;

    public DemoClassLoader(File fileObject) {
        this.fileObject = fileObject;
    }

    public void setFileObject(File fileObject) {
        this.fileObject = fileObject;
    }

    public File getFileObject() {
        return fileObject;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = getClassFileBytes(this.fileObject);
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    /**
     * 从文件中读取去class文件
     *
     * @throws Exception
     */
    private byte[] getClassFileBytes(File file) throws Exception {
        //采用NIO读取
        FileInputStream fis = new FileInputStream(file);
        FileChannel fileC = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel outC = Channels.newChannel(baos);
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        while (true) {
            int i = fileC.read(buffer);
            if (i == 0 || i == -1) {
                break;
            }
            buffer.flip();
            outC.write(buffer);
            buffer.clear();
        }
        fis.close();
        return baos.toByteArray();
    }


}


7.5 根据类加载器手写热部署插件

public class ClassFileEntity {

    /**
     * 类的名称
     */
    private String name;

    /**
     * class
     */
    private Class aClass;

    /**
     * 最后被更改的时间
     */
    private long lastModified;
    public ClassFileEntity(String name, long lastModified) {
        this.name = name;
        this.lastModified = lastModified;
    }

    public ClassFileEntity(String name, long lastModified, Class aClass) {
        this.name = name;
        this.lastModified = lastModified;
        this.aClass = aClass;
    }

    public String getName() {
        return name;
    }

    public Class getaClass() {
        return aClass;
    }

    public long getLastModified() {
        return lastModified;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setaClass(Class aClass) {
        this.aClass = aClass;
    }

    public void setLastModified(long lastModified) {
        this.lastModified = lastModified;
    }
}


public class HotDeploymentPlug {
    //存放所有的class文件
    private Map<String, ClassFileEntity> mapClassFiles = new HashMap<>();
    private String path;
    /**
     * 包的名称
     */
    private String packageName = "com.demo.";

    public HotDeploymentPlug(String path) {
        this.path = path;
    }

    public void start() {
        listener();
    }

    /**
     * 监听方法
     */
    public void listener() {
        new Thread(() -> {
            while (true) {
                // 1.读取该文件下
                File files = new File(path);
                File[] tempList = files.listFiles();
                // 2.读取class文件 存入到 mapClassFiles
                for (File file :
                        tempList) {
                    String name = file.getName();
                    if (StringUtils.isEmpty(name)) {
                        continue;
                    }
                    long l = file.lastModified();
                    // 使用类加载器读取该 class
                    String className = packageName + name.replace(".class", "");
                    if (mapClassFiles.containsKey(className)) {
                        // 则比对该class文件 是否被修改
                        ClassFileEntity mapClassFileEntity = mapClassFiles.get(className);
                        if (mapClassFileEntity.getLastModified() != l) {
                            try {
                                mapClassFileEntity.setLastModified(l);
                                DemoClassLoader demoClassLoader = new DemoClassLoader(file);
                                Class<?> aClass = demoClassLoader.loadClass(className);
                                Object o = aClass.newInstance();
                                log.info(className + "class文件发生了变化");

                            } catch (Exception e) {
                                log.error("e:{}", e);

                            }
                        }

                    } else {
                        ClassFileEntity newClassFileEntity = new ClassFileEntity(className, l);
                        // 如果不存在 则存入到mapClassFiles集合中
                        mapClassFiles.put(className, newClassFileEntity);
                    }
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }


            }
        }).start();
    }

    public static void main(String[] args) {
        HotDeploymentPlug hotDeploymentPlug = new HotDeploymentPlug("D:\\code\\com\\demo");
        hotDeploymentPlug.listener();
    }
}


7.6 什么是SPI机制

Java SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制.
实现方式:

  1. 首先需要再resources目录下:创建文件夹META-INF services
  2. 定义接口文件的名称:
    D:\code\demo_jvm\src\main\resources\META-INF\services\com.demo.service.MyService
    名称规范:包名+类名组成。
com.demo.service.impl.MyServiceImpl01
com.demo.service.impl.MyServiceImpl02

ServiceLoader<MyService> load = ServiceLoader.load(MyService.class);
load.forEach((t) -> {
    System.out.println(t.get());
});

获取当前线程对应的应用类类加载器,加载该class。

ServiceLoader<MyService> load = ServiceLoader.load(MyService.class);
load.forEach((t) -> {
    System.out.println(t.get());
});

7.7 如何绕开双亲委派原则

//        Thread.currentThread().setContextClassLoader(Test02.class.getClassLoader().getParent());
//        Connection root =
//                DriverManager
//                        .getConnection
//                                (
//                                        "jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8",
//                                        "root", "root");
//        Class.forName("com.mysql.jdbc.Driver");

        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();
        try{
            while(driversIterator.hasNext()) {
                Driver next = driversIterator.next();
                System.out.println(next);
            }
        } catch(Throwable t) {
            // Do nothing
        }


8. 常见Java虚拟机

(1)HotSpot VM
HotSpot VM是目前主流的虚拟机。像Oracle / Sun JDK、OpenJDK的各种变种(例如IcedTea、Zulu),用的都是相同核心的HotSpot VM。从Java SE 7开始,HotSpot VM就是Java规范的“参考实现”,JDK8的HotSpot VM已经是以前的HotSpot VM与JRockit VM的合并版,也就是传说中的“HotRockit”,只是产品里名字还是叫HotSpot VM。这个合并并不是要把JRockit的部分代码插进HotSpot里,而是把前者一些有价值的功能在后者里重新实现一遍。移除PermGen、Java Flight Recorder、jcmd等都属于合并项目的一部分。
(2)J9 VM
J9是IBM开发的一个高度模块化的JVM。J9 VM的性能水平大致跟HotSpot VM是一个档次的。
(3)JRockit
以前Java SE的主流JVM中还有JRockit,跟HotSpot与J9一起并称三大主流JVM。这三家的性能水平基本都在一个水平上,竞争很激烈。自从Oracle把BEA和Sun都收购了之后,Java SE JVM只能二选一,JRockit就炮灰了。JRockit最后发布的大版本是R28,只到JDK6,原本在开发中的R29及JDK7的对应功能都没来得及完成项目就被终止了。

9. 常见的几款java虚拟机

SUN Classic VM:第一款商用java虚拟机,1996年1月jdk1.0中带的java虚拟机,只能使用纯解释器的方式来执行java代码
Exact VM:准确式内存管理,编译器和解释器混合工作以及两级即时编译 ,只在Solaris平台发布
HotSport VM:即时编译,节约了时间和存储,称霸武林
KVM:简单,轻量,高可以执行,主要在手机平台使用
JRockit:BEA,世界上最快的java虚拟机,专注服务端应用,优势:垃圾回收机制,MissionControl服务套件
j9:IBM  Technology for java virtual Machines IT4J
dalvik:不能直接指向class文件,寄存器架构,执行dex文件,由class文件转化而来
MicrosoftJvm:只能在windows平台运行,
高性能java虚拟机
  Azul VM:专用虚拟机,经HotSport改进得来,运行在本公司专有硬件中
  Liquid VM:不需要操作系统的支持
taobao虚拟机:淘宝深度定制的产品,硬件依赖性比较高

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131192.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!