likes
comments
collection
share

反射机制(1)--- java类加载机制

作者站长头像
站长
· 阅读数 4

前言

反射机制在我们开发中经常会用到,尤其是在各种框架中频繁的使用,但是想要更加深入的理解反射机制,首先要理解类的加载机制,只有理解了类的加载机制,才能更清楚明白的理解反射的来源和原理,否则面对一大堆反射相关的API,只会是一脸蒙圈,即使当下会用了,回头还是会忘记。本篇文章就来探讨下反射机制和类加载机制之间的关系,从而可以对反射有一个更清楚明白的理解,来加深印象。

加载方式

  • 静态加载:编译时加载相关的类,如果没有则报错

  • 动态加载:运行时加载需要的类,如果运行时用不到该类,即使不存在该类,也不报错,当运行到这个类时,才会报错

类加载时机

  • 当创建对象时(new)(静态加载)
  • 当子类被加载,父类也会被加载(静态加载)
  • 调用类中的静态成员时(静态加载)
  • 通过反射(动态加载)

加载过程

反射机制(1)--- java类加载机制

首先通过javac编译将java文件编译为class文件。

加载

JVM在该阶段的主要目的是将字节码从不同的数据源(class文件、jar包、网络等)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象

链接

  • 验证:目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的额安全,包括文件格式的验证、元数据的验证、字节码验证和符号引用验证
  • 准备:JVM会在该阶段对静态变量分配内存并初始化(对应数据类型的默认初始值,如0、false、null等)。这些变量所使用的内存都将在方法区中进行分配。看如下代码示例,可以看到不同类型的变量以及常量在准备阶段的不同处理:
	//是实例变量,不是静态变量,因此在准备阶段是不分配内存的
	public int a = 0;
	//是静态变量,分配内存,默认初始化为0 而不是10
	public static int b = 10;
	//static final静态常量,和静态变量是不一样的,因为一旦赋值就不变,所以初始化为100
	public static final int c = 100;
  • 解析:虚拟机将常量池中的符号引用替换为直接引用的过程,符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

初始化

  • 到初始化阶段,才真正开始执行类中定义的java程序代码,此阶段是执行< clinit >方法的过程。
  • clinit方法是由静态编译器按照语句在源文件中出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
  • 如果一个类中没有static的语句块,也没有对static变量的赋值操作,那么虚拟机不会为这个类生成< clinit>()方法。
  • 虚拟机会保证一个类中clinit方法在多线程环境下被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的clinit方法,其他线程需要阻塞等待,一直到活动线程执行clinit方法执行完毕

类的加载和反射的关系

我们说的主题是反射,那么为什么要了解类加载的过程呢?我们看一下在加载阶段中,生成了一个代表该类的java.lang.class对象,注意这个对象是在中的,跟方法区中的二进制数据不是同一个。我们看一下类经过加载之后,在内存中的布局情况:

反射机制(1)--- java类加载机制 这个存在于堆中的Class对象是什么呢?(类的字节码二进制数据存放于方法区,包括方法代码、变量名、方法名、访问权限等等)

  • Class也是一个类,是继承自Object
  • Class对象不是new出来的,而是由系统创建的
  • 对于某个类的class类对象,内存中只有一份,因为类只会被加载一次
  • 每个类的实例都会记得自己是由哪个class实例所生成的
  • 通过Class对象可以完整的得到一个类的完整结构

要想使用反射,首先需要获取待操作类的对应的Class对象,通过这个对象可以获取整个类的结构,所以java.lang.Class可以当做是反射的入口点。反射的本质就是在运行时,把java类中的各个成分映射成一个个java对象。

总结

通过以上的分析,我们用图的方式总结一下一个java程序在计算机中的几个阶段:

反射机制(1)--- java类加载机制

  • 代码编译阶段,可以通过Class.forName()来得到Class对象
  • 加载阶段,可以通过类.Class来得到Class
  • 运行期间,可以通过对象.getClass() 来获取Class

当我们得到Class对象之后,就可以通过调用一系列的API来操纵java类,可以创建对象,调用对象方法,操作对象属性等等。这一部分放到下一个小节。