JVM 之 类加载

1.类加载

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,解析和初始化,
最终形成可以被虚拟机直接使用的java类型。

2.加载机制

规范中没有明确说明。Hotspot采用懒加载机制。(用的时候才查询加载)

3.加载过程

①加载-> ②连接(验证,准备,解析)-> ③初始化-> ④使用-> ⑤卸载
在这里插入图片描述

3.1加载:

加载有哪些动作:

	1.通过一个类的全限定名来获取定义此类的二进制流。
	2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
	3.在内存中生成一个代表这个类的class对象,作为这个类的各种数据的访问入口。

从哪里加载:

  • 文件中
     class文件
     jar文件
  • 网络
  • 计算生成一个二进制流(proxy)
  • 数据库

3.2连接:

加载开始之后就可以连接了(部分并行)。但是加载完毕之后连接才会结束。

3.2.1 验证

保证class文件的字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的安全。
  • 校验信息:
     文件格式:cafebebe,版本号等
     元数据:语义方面的校验,比如final类不能继承
     字节码:最复杂,分析控制流确定是否符合语义
     符号引用:校验引用方法,属性是否存在。比如错误no such method。确保解析动作正常执行

3.2.2 准备:

	正式为类变量(static)分配内存并设置变量的初始值(此时的初始值为默认值,
并不是我们指定的值)。这些变量将在方法区中进行分配。但是如果被final,在这
个过程中,常量值会被同时指定。
  • 默认值:
     int:0
     Boolean:false
     Float:0.0
     char:‘0’
     抽象数据类型:null

3.2.3 解析:

虚拟机将常量池中的符号引用替换为直接引用的过程。不同虚拟机不同的实现。
  • 类或者接口的解析
     字段解析(查找当前类,实现接口,父类,没有找到抛出异常)
     类方法解析(类似字段解析)
     (以上三个解析都要判断权限,接口不需要判断,因为是public)
     接口方法解析

3.3 初始化:

  • 1.遇到new,getstatic,putstatic,invokestatic初始化,final对象会放到常量池中,当引用时不会初始化。
  • 2.使用java.lang.reflect包的方法对类进行反射调用时。
  • 3.初始化一个类的时候,父类没有初始化时先触发父类初始化。
  • 4.虚拟机启动时,用户指定一个要执行的主类。

不被初始化:

  • 1.通过子类引用父类的静态字段,父类初始化,子类没有初始化。
  • 2.通过数组定义来引用类。
     Child[] child = new Child[10]; //不会初始化
  • 3.调用类的常量(见规则1,final)

类加载过程中,除了加载阶段用户可以自定义类加载器,其他阶段都是由虚拟机完成。

	1.<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中
	的语句合并产生的。收集顺序由语句在源文件中出现的顺序决定的,静态语句块
	中只能访问定义在语句块之前的变量,定义在它之后的变量,在前面的语句块中
	可以赋值,但是不能访问。
public class Hello {
		static{
			i = 0;  //这里不会报错
			System.out.println(i);  //这里报错
		}
		public static int i = 3;
	}
2.子类的<clinit>()在执行之前,虚拟机保证父类的先执行完毕。

3.接口中也有变量赋值,也会生成<clinit>(),但不需要先执行父类的<clinit>()方法。只有
父类接口中定义的变量使用时才会初始化。

4.如果多个线程同时初始化一个类,只有一个线程会执行这个类的<clinit>()方法,其他
线程等待执行完毕。如果方法执行时间过长,那么就会造成多个线程阻塞。

4.类加载器

	"通过一个类的全限定名来获取定义此类的二进制流",放到虚拟机外部实现,
以便让应用程序自己决定如何获取所需要的类。

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

4.1 分类:

  • 启动类加载器
     由C++实现,是虚拟机的一部分,用于加载javahome下的lib目录下的类
  • 扩展类加载器
     加载javahome下/lib/ext目录中的类
  • 应用程序类加载器
     加载用户类路径上的锁指定的类库
  • 自定义类加载器
    • 1.定义一个类,继承ClassLoader
    • 2.重写loadClass方法
    • 3.实例化Class对象

4.2 优势:

  • 高度的灵活性
  • 通过自定义类加载器实现热部署
  • 代码加密
//自定义类加载器
import java.io.IOException;
import java.io.InputStream;

public class Loader {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader load = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {

                //com.test.Loader
                String filename = name.substring(name.lastIndexOf(".")+1)+".class";
                InputStream in = getClass().getResourceAsStream(filename);

                if (in == null){
                    return super.loadClass(name);
                }

                try {
                    byte[] buff = new byte[in.available()];
                    in.read(buff);

                    return defineClass(name, buff, 0, buff.length);
                } catch (IOException e) {
                    e.printStackTrace();
                    throw  new ClassNotFoundException();
                }
            }
        };

        Object ob = load.loadClass("com.test.Loader").newInstance();

        //查看类名
        System.out.println(ob.getClass());

        //加载类是否等于当前类
        System.out.println(ob instanceof Loader);

        //结果
//        class com.test.Loader
//        false     //因为当前类是应用程序加载的,和自定义加载器不同。
    }
}

4.3 类加载器工作方式

  • 双亲委派模型(推脱加载)
加载器收到任务扔到上一级(父类)加载。上一级继续。如果上一级抛出异常,下一级(子类)
再处理。如果最初接收到任务的加载类也抛出异常,会抛出ClassNotFoundException。

在这里插入图片描述

4.4 优点:

所有类都委托上层类加载,保证基础类稳定加载。
发布了193 篇原创文章 · 获赞 13 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u013919153/article/details/105392508