上一节课老师讲了java类的动态装载与反射,上课时听的稀里糊涂的,课后自己找了《深入java虚拟机》看了相关的内容,今晚就大概的总结一下吧,或者说是在写读书笔记。
平时在编写并运行java程序时,就体验到相关的java技术。首先是编译器把源程序编译成java class文件,然后在java虚拟机中运行class文件。要想深刻的理解java类的动态加载与反射,首先是要理解java虚拟机的体系结构。下面是java虚拟机的体系结构图:
从图中可以看出Java虚拟机运行一个程序时,首先是通过类装载器来装载class文件的,然后从已装载的class文件中得到类的相关信息,如程序创建的对象,传递给方法的参数,返回值,局部变量等,java虚拟机将这些东西存储到运行时数据区进行管理的。
类装载器:java虚拟机中有两种类装载器:1.启动类装载器,2.用户自定义装载器。启动类装载器是java虚拟机实现的一部分,而用户自定义类装载器是普通的java对象,它的类必须派生自java.lang.ClassLoader类,ClassLoader类中定义的方法为程序提供了访问类装载器机制的接口,从ClassLoader类的源代码中可以看出主要是通过以下四个方法通向Java虚拟机的。
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)throws ClassFormatError
{
return defineClassCond(name, b, off, len, protectionDomain, true);
}
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
protected final Class<?> findSystemClass(String name)
throws ClassNotFoundException
{
ClassLoader system = getSystemClassLoader();
if (system == null) {
if (!checkName(name))
throw new ClassNotFoundException(name);
Class cls = findBootstrapClass(name);
if (cls == null) {
throw new ClassNotFoundException(name);
}
return cls;
}
return system.loadClass(name);
}
两个重载的defineClass方法能够把新的类或者接口导入到方法区中,findSystemClass方法通过启动类装载器来装载指定类型,resolveClass方法能够让装载器执行连接动作。此外,对于每一个被装载的类或者接口,java虚拟机都会为它创建一个java.lang.Class的实例来代表该类或者接口。用户自定义的类装载器以及Class类的实例都放在堆中,而装载的类或者接口则放在方法区中。
方法区:在java虚拟机中被装载的类或者接口的信息存储在逻辑上称为方法区的内存中,虚拟机会在方法区中存储以下类或者接口的信息:
1.这个类或者接口的权限定名。
2.这个类或者接口的直接超类的权限定名,除非java.lang.Object类。
3.这个类或者接口的的标识,即是类还是接口。
4.这个类或者接口的访问修饰符。
5.任何直接超接口的权限定名的有序列表。
6.该类或者接口的常量池。
7.字段信息。
8.方法信息。
9.除了常量以外的所有类变量。
10.一个到ClassLoader类的引用。
11.一个到Class类的引用。
堆:java程序在运行时创建的所有类的实例或数组都放在同一个堆中。在一个java虚拟机实例中,只存在一个堆空间,因此所有线程都共享这个堆,在这种情况下就要考虑多线程访问对象的同步问题了。
Java栈:每当启动一个线程时,虚拟机都会为它分配一个Java栈,虚拟机只会直接对Java栈执行两种操作:以帧为单位的压栈和出栈。
从以上的的表述中大概了解了java虚拟机的体系结构,下面大概总结下虚拟机的工作过程:
java虚拟机通过装载、连接、初始化一个java类或者接口,使得可以被正在运行的java程序所使用。其中,装载是把class文件读入虚拟机中,连接是把已读入虚拟机中的数据类型合并到虚拟机的运行时状态中去,连接分为三个阶段:验证、准备和解析,验证是确保java类型数据格式正确并使用于虚拟机,准备则是负责为该类型(类或者接口)分配所需的内存空间,解析是把常量池中的符号引用转换为直接引用。初始化是将类变量赋以适当的初始值。
装载阶段是由三个基本动作组成:
1.通过该类型的完全限定名,产生一个代表该类型的二进制数据流。
2.解析这个二进制数据流为方法区内的内部数据结构。
3.创建一个表示该类型的java.lang.Class类的实例。
总结:装载的最终产品是Class类的实例对象,它成为Java程序与内部数据结构之间的接口。
初始化包含两个步骤:
1.如果类存在直接超类,且直接超类还没有被初始化,就先初始化直接超类。
2.如果类存在一个类的初始化方法,就执行此方法。
一旦一个类被装载、连接和初始化,它就可以随时使用了,程序可以访问它的静态变量,调用它的静态方法,或者创建它的实例,在java程序中可以明确的使用new;或者调用Class或者java.lang.reflect.Constructor对象的newInstance()方法;或者调用现有对象的Clone()方法来创建对象。
java程序通过传递类型的名字到java.lang.Class的forName()的方法或者用户自定义的类装载器的loadClass()方法可以在运行时决定加载哪一个类型并使用它们。
好了,先写到这里吧,总结的很粗糙,以后有深入的理解再总结吧。