JVM类加载机制

JVM运行时需要把class文件加载到内存中,将class文件内容解析成jvm认识的数据结构,比如把类和类成员变量,类方法,运行时常量,类对应的class对象,并根据代码逻辑对类进行初始化,这一整个过程即是JVM的类加载过程。整个过程分为装载 验证 准备 解析 初始化五步。

装载:JVM通过类的全限定名读取class文件到内存,转化为特定的数据结构存储到方法区之中,并生成类对应的Class对象放在内存中,作为方法区该类数据的访问入口,Hotspot虚拟机的类Class对象和类的数据一起放在方法区的。
验证:JVM对读取到的类数据并不是完全放心的,所以接下来对类数据进行验证。包含文本格式验证 元数据验证 字节码验证 符号引用验证。
准备:为类变量(static修饰的变量)分配内存设置类变量的默认初始值(零值,非用户程序赋值的初始值)。如果是final static修饰的常量,在编译阶段就会当成常量,此时JVM会为其直接赋值为用户程序初始值。
解析:将常量池中的符号引用解析成直接引用。即解析前,运行时常量池里的符号引用只是一个符号,用来描述目标。解析后的直接引用,可以直接找到对应的目标。
初始化:JVM执行用户编写的初始化程序代码。即static块和static变量的赋值。编译器会自动生成这两个部分的代码生成类的构造方法 < clinit >,初始化时执行该方法。

JVM规定了有且只有五种情况下会去初始化一个类。

1、遇到new getstatic putstatic invokestatic 这四条字节码指令时,会去对类进行初始化。从用户编写程序代码层面看,在使用new关键字创建对象时 读取或者设置一个static修饰的类变量时,以及调用一个类的static方法时。 个人理解:当进行这些操作时,目标类必须是一个完整的类,即类的初始状态已经准备就绪,否则得先去进行初始化操作。

2、使用反射技术去调用的时候,比如Class.forName() 这时是通过想要通过类的Class对象获取对该类进行操作,同理,该类必须是一个初始化状态的就绪的类。

3、在初始化一个子类时,会先初始化其父类。

4、启动类,即包含入口main()方法的类会先进行初始化。指的是该类的main方法会作为程序入口方法。如果只是存在一个main方法,但是不是作为入口方法,也不会初始化,spring项目中定义一个类包含main方法,但是不去启动main方法,用tomcat启动时并不会初始化。

public class Test {

    static {
        System.out.println("Test 初始化静");
    }
    /**
     * 4、启动类,即包含入口main()方法的类会先进行初始化。
     * 指的是该类的main方法会作为程序入口方法。
     * 如果只是存在一个main方法,但是不是作为入口方法,也不会初始化,
     * spring项目中定义一个类包含main方法,但是不去启动main方法,用tomcat启动时并不会初始化。
     * 
     * 直接执行该方法会初始化
     * @param args
     */
    public static void main(String[] args) {

    }
}

5、动态链接时,需要先去初始化目标类。

双亲委派模型

启动类加载器 扩展类加载器 应用类加载器
启动类加载器是JVM实现的一部分,程序员无法干预的。用来加载JAVA_HOME/lib下的基础jar,如rt.jar.或者启动参数-Xbootclasspath指定路径下被JVM认可的类。
扩展类加载器,加载JAVA_HOME/lib/ext下的扩展jar,或者java.ext.dirs系统变量指定路径中的类库.
应用类加载器,系统类加载器Launcher.AppClassLoader。由ClassLoader.getSystemClassLoader()方法返回,用来加载用户路径下的类,既我们编写的类默认都是由它加载。

	/**
     * 类加载时调用该方法去加载类。
     */
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    	// 加锁 保证只会有一个线程的加载器进入该方法
        synchronized (getClassLoadingLock(name)) {
            // 查询已加载的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();
                    // 调用自己的findClass方法加载
                    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();
                }
            }
           // 已加载  去初始化  返回该类的Class对象
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

当类加载器加载一个类时,首先把加载请求委托给它的父类加载器,父类加载器无法加载时,才自己去加载。保证JVM中不会加载完全相同的两个类。
全限定名相同的类只会加载第一个加载到的,之后的类不会加载。

能不能自己写个类叫java.lang.Math?

答案是不能。1、如果使用默认加载器加载,会委托给启动类加载器加载rt.jar下的java.lang.Math.不会再加载自定义的这个Math。

package java.lang;

/**
 * @author neil
 * @date 2019-07-18
 */
public class Math {

    public static void main(String[] args) {
        System.out.println("123");
    }
}

执行main方法报错,表明未加载该自定义的java.lang.Math
在这里插入图片描述

2、如果指定自定义的类加载器去加载,抛出异常java.lang.SecurityException。

package org.neil.util;

import java.io.InputStream;

/**
 * @author neil
 * @date 2019-07-18
 */
public class ClassLoaderTest extends ClassLoader {

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try{
            String className = null;
            if(name.startsWith("java.java.lang")){
                className = "/" + name.replace('.', '/') + ".class";
            }else{
                className = name.substring(name.lastIndexOf('.') + 1) + ".class";
            }
            System.out.println(className);
            InputStream is = getClass().getResourceAsStream(className);
            System.out.println(is);
            if(is == null){
                return super.loadClass(name);
            }

            byte[] b = new byte[is.available()];
            is.read(b);
            return defineClass(name, b, 0, b.length);
        }catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }

    }
}

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ClassLoader myLoader = new ClassLoaderTest();
        Object obj = myLoader.loadClass("java.java.lang.Math").newInstance();
        System.out.println(obj);
    }

在这里插入图片描述

3、自建一个包名为java.lang,也会抛出异常java.lang.SecurityException。

package java.lang;

/**
 * @author neil
 * @date 2019-07-18
 */
public class MyMath {

    public static void main(String[] args) {
        System.out.println("123");
    }


}

禁止使用包名java.
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值