JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制[通俗易懂]

JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制[通俗易懂]1、类加载器站在Java虚拟机的角度看,只有两种不同的类加载器:一种是启动类加载器(BootstrapClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机、JDK8中),是虚拟机自身的一部分;另外一种是其他所有类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoaderJDK8及以前版本中绝大多数程序都会使用到以下3个系统提供的类加载器来进行加载启动类(引导类)加载器:负责加载支撑JVM运行的位于&l

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

1、类加载器

站在Java虚拟机的角度看,只有两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机、JDK8中),是虚拟机自身的一部分;另外一种是其他所有类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类 java.lang.ClassLoader

JDK8及以前版本中绝大多数程序都会使用到以下3个系统提供的类加载器来进行加载

  1. 启动类(引导类)加载器:负责加载支撑JVM运行的位于<JAVA_HOME>\lib目录下的核心类库,而且是Java虚拟机能够识别的类库加载到虚拟机内存中(如rt.jar、tools.jar、charsets.jar等,名字不符合的类库即使放到lib目录下也不会被加载)。

  2. 扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc. $ExtClassLoader中以Java代码的形式实现的。负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量所制定的路径中所有的类库,是一种Java系统类库的扩展机制

  3. 应用程序类加载器(Application Class Loader):是由sun.misc.launcher$AppClassLoader来实现,由于应用程序加载器是ClassLoader类中getSystemClassLoader()方法的返回值,也称为系统类加载器。负责加载用户类路径(ClassPath)上所有的类库,如应用程序中没有默认自己的类加载器,则使用应用程序加载器为默认加载器。

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

1.1、Launcher源码

package com.learn.jvm;

import com.sun.crypto.provider.DESKeyFactory;

/** * @author liushiwei */
public class JvmClassLoader { 
   


    public static void main(String[] args) { 
   
        // 查看String的类加载器
        System.out.println(String.class.getClassLoader());
        // 查看DESKeyFactory扩展包中的加载器
        System.out.println(DESKeyFactory.class.getClassLoader().getClass().getName());
        // 查看当前类加载器
        System.out.println(JvmClassLoader.class.getClassLoader().getClass().getName());

    }
}

1.1.1、Launcher

sun.misc.Launcher类是java的入口,在启动java应用的时候会首先创建Launcher类,创建Launcher类的时候回准备应用程序运行中需要的类加载器。

Java命令执行代码的大体流程,结合图可以查看Launcher类的源码
在这里插入图片描述

类加载器是一个抽象类。给定类的二进制名称,类装入器应该尝试查找或生成构成该类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。

2、双亲委派机制

双亲委派模型的工作过程是:如果一个类加载器收到了类加载请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层都是如此,因此所有加载请求最终都传送给顶层的启动类加载器,只有父加载器反馈自己无法加载这个加载请求时(它的搜索范围没有找到所需的类),子加载器才会尝试自己去完成加载,双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载
在这里插入图片描述
好处

  1. 具备优先级的层次关系,例如java.lang.Object类,它放在rt.jar之中,无论那个类加载器加载这个类,都会向上委派给模型的最顶端启动类加载器加载,因此Object类在程序的各个类加载器中都能保证是一个类,从而保证被加载类的唯一性

  2. 这样便可以防止核心API库被随意篡改,如自定义Object类

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

    结果在这里插入图片描述

2.1、ClassLoader源码

AppClassLoader的继承关系
在这里插入图片描述
在这里插入图片描述

应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下

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) { 
   
                    c = parent.loadClass(name, false);
                } else { 
   
                    // 如果当前加载器父加载器为空则委托引导类加载器加载该类
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) { 
   
                // 如果找不到类 抛出异常ClassNotFoundException
                // 说明父类加载器无法完成加载请求
            }

            if (c == null) { 
   
                long t1 = System.nanoTime();
                // 在父加载器无法加载时,在调用本身的findClass方法进行类加载,
                // 最终调用的是URLClassLoader.findClass方法
                // 真正加载类的逻辑
                c = findClass(name);

                // 这是定义类加载器; 记录统计
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) { 
   
            resolveClass(c);
        }
        return c;
    }
}

defineClass:经历验证、解析等
在这里插入图片描述

3、自定义类加载器

加载的类

public class JvmClassLoader1 { 
   
    
    public void getValue(){ 
   
        System.out.println("JvmClassLoader1");
    }
}

自定义加载器,及测试内部类

package com.learn.jvm;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
public class MyClassLoader  extends ClassLoader { 

private  String classPath;
public MyClassLoader(String classPath){ 

this.classPath= classPath;
}
private byte[] loadByte(String name) throws IOException { 

String path = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + path.concat(".class"));
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException { 

try { 

byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) { 

e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
class MyClassLoaderDemo{ 

public static void main(String[] args) throws Exception { 

// 初始化自定义类加载器,会先初始化父类ClassLoader,
// 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
// D盘创建 demo/com/learn/jvm 几级目录,将JvmClassLoader类的复制类JvmClassLoader1.class丢入该目录
MyClassLoader myClassLoader = new MyClassLoader("D:/demo");
Class<?> aClass = myClassLoader.loadClass("com.learn.jvm.JvmClassLoader1");
// 通过反射调用类中方法
Object object = aClass.newInstance();
Method getValues = aClass.getDeclaredMethod("getValue", null);
getValues.invoke(object);
System.out.println(aClass.getClassLoader().getClass().getName());
}
}
  • 执行结果:
    • 如果当前项目路径下有JvmClassLoader1.class文件,则输出AppClassLoader应用程序加载器,因为自定义加载器的父加载器是AppClassLoader

    • 如果当前项目路径下无JvmClassLoader1.class文件,则输出MyClassLoader

为什么自定义的类加载器的父加载器是AppClassLoader

  • 因为初始化自定义类加载器时,会初始化父类ClassLoader,而ClassLoader的构造方法中,会给this.parent赋值,如下l.getClassLoader();中对应的类加载器就是AppClassLoader,满足双亲委派机制
    在这里插入图片描述

4、打破双亲委派机制

例如在Tomact中部署多个项目,每个项目使用的相同但不用版本的组件

自定义类加载器,在加载类时,没有遵循双亲委派机制(先委托父加载器,父加载器没有此类,最后在交给子加载器加载),可以是自己先加载,加载不到在委托父加载器,或不需要父加载器加载。

package com.learn.jvm;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
public class MyClassLoader  extends ClassLoader { 

private  String classPath;
public MyClassLoader(String classPath){ 

this.classPath= classPath;
}
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 t1 = System.nanoTime();
if(name.contains("com.learn.jvm")){ 

// 自定义加载器加载类
c = findClass(name);
}else{ 

// 父加载器加载
c = this.getParent().loadClass(name);
}
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) { 

resolveClass(c);
}
return c;
}
}
private byte[] loadByte(String name) throws IOException { 

String path = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + path.concat(".class"));
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException { 

try { 

byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) { 

e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
class MyClassLoaderDemo{ 

public static void main(String[] args) throws Exception { 

// 初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
// D盘创建 demo/com/learn/jvm 几级目录,将JvmClassLoader类的复制类JvmClassLoader1.class丢入该目录
MyClassLoader myClassLoader = new MyClassLoader("D:/demo");
// 使用当前类加载器加载
ClassLoader classLoader = myClassLoader.getClass().getClassLoader();
Class<?> aClass = myClassLoader.loadClass("com.learn.jvm.JvmClassLoader1");
// 通过反射调用类中方法
Object object = aClass.newInstance();
Method getValues = aClass.getDeclaredMethod("getValue", null);
getValues.invoke(object);
System.out.println(aClass.getClassLoader().getClass().getName());
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/197533.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • HttpClient4.X 升级 入门 + http连接池使用

    HttpClient4.X 升级 入门 + http连接池使用转载请注明出处,谢谢~http://blog.csdn.net/shootyou/archive/2011/05/12/6415248.aspx 在一次服务器异常的排查过程当中(服务器异常排查的过程我会另起文章),我们决定使用HttpClient4.X替代HttpClient3.X或者HttpConnection。为什么使用HttpClient4?主要是HttpConnection

  • apache 虚拟主机配置详解_linux 配置虚拟主机

    apache 虚拟主机配置详解_linux 配置虚拟主机文章目录Apache虚拟主机企业应用部署一个端口不同域名调试worker工作模式进行压力测试权限设置Apache配置文件详解ApacheRewrite规则讲解Apache配置文件权限操作总结Apache虚拟主机企业应用企业真实环境中,一台WEB服务器发布单个网站会非常浪费资源,所以一台WEB服务器上会发布多个网站,少则3-5个,多则2-30。在一台服务器上发布多哥网站,也称之为部署多个虚拟主机,WEB虚拟主机配置方法有三种:基于单个IP地址多个Socket端口基于

  • MySQL 1045登录失败

    MySQL 1045登录失败当你登录MySQL数据库出现:Error1045错误时(如下图),就表明你输入的用户名或密码错误被拒绝访问了,最简单的解决方法就是将MySQL数据库卸载然后重装,但这样的缺点就是就以前的数据库中的信息将丢失,如果你不想重装,那么就需要找回密码或者重置密码。解决的方法应该有多种,这里我推荐大家使用一种原理通过,操作简单的方法,适用于windows以及linux平台。 MySQL1045错误如

  • phpproxy建立代理服务器_proxy设计模式

    phpproxy建立代理服务器_proxy设计模式代理,指的就是一个角色代表另一个角色采取行动,就象生活中,一个红酒厂商,是不会直接把红酒零售客户的,都是通过代理来完成他的销售业务。而客户,也不用为了喝红酒而到处找工厂,他只要找到厂商在当地的代理就行了,具体红酒工厂在那里,客户不用关心,代理会帮他处理。代理模式,就是给某一对象提供代理对象,并由代理对象控制具体对象的引用。代理模式涉及的角色:抽象主题角色,声明了代理主题和真实主题的公共…

    2022年10月30日
  • 数据同步利器-otter的搭建使用说明「建议收藏」

    数据同步利器-otter的搭建使用说明「建议收藏」一、Otter目前支持了什么1.单向同步,mysql/oracle互相同步2.双向同步,无冲突变更3.文件同步,本地/aranda文件4.双A同步,冲突检测&冲突补救5.数据迁移,中间表/行记录同步导历史表还需要程序实现吗?还在用mysql的主从复制吗?Otter都能为你解决。典型的场景

  • Eureka集群配置

    Eureka集群配置eureka作为注册中心,生产环境必须多节点部署,保证其高可用性。现以两台服务器来完成集群部署。服务器A:172.16.21.34服务器B:172.16.21.35方式一:使用ip形式完成。服务器A:172.16.21.34server:port:7777spring:application:name:register#指定eureka客户端的登录账户security:user:…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号