之前我们了解了Class文件存储的具体细节,那么字节码流在虚拟机执行引擎中是怎样被解释执行的呢?
1.类加载器的作用
虚拟机把描述类的数据从Class文件加载到内存中,并对数据进行效验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
虚拟机设计团队把类的加载阶段中“通过一个类的全限定名来获取描述此类的二进制字节流”这动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块被称为“类加载器”。
在类的加载阶段,虚拟机需要完成以下三件事情:
- 1)通过一个全限定名来获取定义此类的二进制字节流;
- 2)将这个字节流所代表的静态存储结构转化为方法区中的运行时数据结构,这些数据即该类的描述信息(class mate);
- 3)在Java堆中生成一个代表这个类的Java.lang.Class对象,作为程序访问方法区中这些数据外部接口。
类的描述信息包括:
- 1.类型的完整有效名,类型的修饰符(public,abstract, final的某个子集),类型直接接口的一个有序列表及继承的父类。
- 2.类型的常量池( constant pool)
- 3.域(Field)信息
- 4.方法(Method)信息
- 5.除了常量外的所有静态(static)变量
- 6.classloader的引用
类的描述信息按照既定格式存储在.class文件中,通过类加载器,这些信息被加载到方法区中,然后在堆中生成一个代表这个类的Java.lang.Class对象。所以,这些信息都可以通过对应的Class对象进行获取——例如图中的getClassLoader()方法。
2.类加载器的种类
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
类加载器的种类:
1)虚拟机自带的加载器
种类 | 实现语言 | 负责加载的类的存放位置 |
---|---|---|
启动类加载器(Bootstrap) | C++ | 1)$JAVAHOME/jre/lib/ eg:其下的rt.jar 2)或者-Xbootclasspath参数所指定路径,并且虚拟机能够识别 |
扩展类加载器(Extension) | Java | $JAVAHOME/jre/ext/ |
应用程序类加载器(AppClassLoader) | Java | $CLASSPATH |
注意:启动类加载器因为是C++语言实现,无法被Java程序直接引用。
应用程序类加载器(AppClassLoader)也叫系统类加载器,加载当前应用的classpath的所有类。
2)用户自定义加载器
User-defined ClassLoader
- 由Java语言实现,继承自Java.lang.ClassLoader抽象类,独立于虚拟机外部;
- 用户可以定制类的加载方式
3.类加载的方式
3.1 双亲委派机制
双亲委派模型的式作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完全这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
使用双亲委派模式来组织类加载器之间的关系,使Java类随着类加载器一起具备一种带有优先级的层次关系。例如java.lang.Object,其存放在rt.jar中,无论哪个类加载器加载这个类,或是你自己在ClassPath中写了一个同名的类,最终都是委派启动类加载器加载。这样可以防止类加载的混乱。
3.2 沙箱机制
沙箱机制是由基于双亲委派机制上 采取的一种JVM的自我保护机制,假设你要写一个java.lang.String 的类,由于双亲委派机制的原理,此请求会先交给Bootstrap试图进行加载,但是Bootstrap在加载类时首先通过包和类名查找rt.jar中有没有该类,有则优先加载rt.jar包中的类,因此就保证了java的运行机制不会被破坏.