类加载机制及发射

1.类的加载、连接和初始化

1.1JVM和类

  • 当系统出现以下情况,JVM进程将被终止。
  1. 程序运行到最后正常结束
  2. 程序运行到使用System.exit()或Runtime.getRuntime().exit()代码处结束程序。
  3. 程序执行到使用中遇到未捕获的异常或错误而结束。
  4. 程序所在平台强制结束了JVM进程。

当java程序运行结束时,JVM进程结束,该进程在内存中的状态将会丢失。

1.2类的加载

​ 当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、链接、初始化三个步骤来对该类进行初始化。如果没有意外,JVM将会完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。

​ 类加载指的是将类的class文件读入内存,并为之创建一个java.lang.class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

java类加载机制

什么是类加载机制?

我们编写的java文件是如何加载到JVM运行的
	java文件----->javac命令----->class文件----java命令----
当调用java命令执行class文件就会加载类,也就是如下三个过程
类加载主要分为如下三个过程
加载过程     在硬盘上查找并通过IO流读入字节码进JVM内存结构的方法区同时在堆上形成class对象
连接过程  
    验证 校验字节码文件的正确性
    准备 为类的静态变量分配内存初始化为默认值,对于final static变量编译就分配了
    解析 将类中的符号引用转换为直接引用
初始化过程 对类的静态变量初始化为指定的值 执行静态代码块
12345678910

类加载器主要分为以下四类

不同的加载器加载不同的class文件
	引导类加载器  负责加载jre/lib目录下的核心类库  类似rt.jar等jar包
	扩展类加载器  负责加载jre/lib/ext目录中的IAR类包 
	应用类加载器  负责加载ClassPath路径下的class字节码文件 主要就是自己写的类
	自定义加载器  负责加载用户自定义路径下的class字节码文件 (路径自己定义)
12345

类加载器

产生过程

	本篇文章针对windows系统的类加载过程
		第一步java会调用底层C++代码创建JVM虚拟机
		创建一个引导类加载器实例C++实现
		调用C创建JVM启动器实例sun.misc.Launcher类
			该类初始化的时候会创建两种类加载器
				扩展类加载器 
				应用类加载器
		源码如下
		//构造器
		public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
        	//创建 扩展类加载器 
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            ...
        }
        try {
        	//创建 应用类加载器 将扩展类加载器传入作为应用类加载器的parent属性
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            ...
        }
        获取运行类自己的加载器,默认是AppClassLoader加载器
        ClassLoader c = Launcher.getClassLoader()
        c.loadClass("...xxx.class")加载类
        加载完成后执行main方法入口
        运行
        JVM销毁   
1234567891011121314151617181920212223242526272829

三种类加载器的关系

	应用类加载器的父加载器是扩展类加载器
    扩展类加载器的父加载器是引导类加载器
12

双亲委派机制

	双亲委派机制是类加载过程中实现的一种加载方法 
	当第一次加载类的时候
		应用类加载器加载类不会直接去classPath路径下找class文件,而是会交给自己的父加载器(parent)
		向上委托给扩展类加载器,而扩展类加载器仍然不会直接去ext路径下查找class文件,而是会交给
		引导类加载器(C++实现),此时引导类加载器会去jre/lib目录下查找class文件,找不到返回null
		找到直接加载,如果返回null则此时交给扩展类加载器处理,它会去ext路径下查找class文件,同理
		找到加载,找不到返回null交给应用类加载器去classPath路径下加载,有则加载,没有则报错。
	为什么要采用这种类加载方法?
		沙箱安全机制保证jdk核心代码加载完毕 
		可以避免类重复加载 
	为什么不直接从引导类加载器向下加载?
    	大部分业务类是由应用类加载器加载 第一次加载慢 之后加载会很快 
123456789101112

loadClass()方法

功能:加载类

	类加载过程中 ClassLoader的loadClass()是非常重要的
	如下源码演示 c代表 因为引导类加载器是C++实现的 所以扩展类加载器的parent为空
	Class<?> c = findLoadedClass(name);//如果是应用类,第一次c为null 第二次就直接返回
    if (c == null) {
        //判断parent是否为空 应用类加载器的parent就是扩展类加载器,在初始化Launcher的时候指定的
        if (parent != null) {
            //有父加载器就调用
            //应用类加载器---->扩展类加载器 
            c = parent.loadClass(name, false);
        }else {
            //扩展类加载器 ---> 引导类加载器
            c = findBootstrapClassOrNull(name);
        }
        //没有找到则向下调用findClass方法  扩展类加载器->应用类加载器
        if (c == null) {
            c = findClass(name);
        }
    }
    if (resolve) {
       resolveClass(c);
    }
    return c;

1234567891011121314151617181920212223

findClass()方法

功能:获取class对象

	该方法由URLClassLoader类实现 关键代码如下
		name就是类路径例如("com.nicky.classloader.TestClass")
		res为字节数组
		return defineClass(name, res);
		返回class对象 Class<?>类型交给loadclass方法
12345

如何自定义类加载器?

	根据源码ClassLoader里的例子演示
	如果不想破坏双亲委派机制 直接重写findclass方法自定义类加载路径即可
	如果想破坏双亲委派机制,就需要重写loadClass方法将关键代码修改
123

NickyClassLoader.java 自定义类加载器 破坏了双亲委派机制 重写loadclass方法 因为双亲委派机制会在第二次加载同类名的时候直接加载 例如Tomcat部署多个war包的情况下,2个不同的war包会有相同的类名 双亲委派机制则会导致第二个加载的war包不能加载到自己war包的内容 所以如果是java的包仍然采用双亲委派机制,自定义的包就必须得破坏双亲委派机制

package com.nicky.classloader;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;


/**
 * 自定义类加载器
 */
public class NickyClassLoader {

    static class MyClassLoader extends ClassLoader {

        //破坏双亲委派机制
        @Override
        public Class<?> loadClass( String name ) 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();
                    //java自己的类 传递给上层加载器加载
                    if (!name.startsWith("com.nicky")){
                        c = super.getParent().loadClass(name);
                    }else{
                        c = findClass(name);
                    }
                }
                return c;
            }
        }

        private String classpath;

        public MyClassLoader( String classpath ) {
            this.classpath = classpath;
        }

        //可以自定义classloader加载class文件
        @Override
        protected Class<?> findClass( String name ) throws ClassNotFoundException {
            try {
                byte[] data = getByte(name);
                //将一个字节数组转为class对象 这个字节数组是class文件读取后最终的字节数组
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

        private byte[] getByte( String name ) throws IOException {
            name = name.replace(".", "/");
            FileInputStream fileInputStream = new FileInputStream(classpath + "/" + name + ".class");
            int len = fileInputStream.available();
            byte[] bytes = new byte[len];
            fileInputStream.read(bytes);
            fileInputStream.close();
            return bytes;
        }
    }

    //tomcat 如何实现 多个项目自定义类加载 
    public static void main( String[] args ) throws Exception {
    
        MyClassLoader classLoader = new MyClassLoader("E:/test");
        Class<?> aClass = classLoader.loadClass("com.nicky.classloader.TestClass");
        Object o = aClass.newInstance();
        Method say = aClass.getDeclaredMethod("say");
        say.invoke(o);

        MyClassLoader classLoader1 = new MyClassLoader("E:/test1");
        Class<?> aClass1 = classLoader1.loadClass("com.nicky.classloader.TestClass");
        Object o1 = aClass1.newInstance();
        Method say1 = aClass1.getMethod("say");
        say1.invoke(o1);
    }
}

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081

两个不同的TestClass.class文件 一个路径在 E:\test\com\nicky\classloader\TestClass.class 另一个在 E:\test1\com\nicky\classloader\TestClass.class 两个文件是两次编译形成

 public void say(){
        System.out.println("1111111");
    }
123
     public void say(){
        System.out.println("2222222");
    }
123

最后打印效果如下 在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值