在Java,类型的加载,连接,初始化都是在程序运行期间完成的。
Java语言可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。
类加载到虚拟机内存,到卸载出内存为止,整个生命周期有七个阶段。其中,验证,准备,解析这三个阶段,属于连接的三个部分。需要注意的是,解析阶段可能在初始化之后开始(Java语言的运行时绑定。通常情况下这些阶段都是相互交叉混合进行,会在一个阶段执行的过程中调用激活另一个阶段)
加载(通过类的全限定名获取定义此类的二进制字节流。将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口。)
验证(连接阶段的第一步。文件格式验证,元数据验证,字节码验证,符号引用验证)
准备(为类中定义的变量分配内存并设置类变量初始值的阶段)
解析(符号引用转为直接引用。类或接口的解析,字段解析,方法解析,接口方法解析)
初始化(6种情况下,必须开始初始化,是主动引用。其他的是被动引用。<clinit>()方法。如果有多个线程同时去初始化一个类,只会有一个线程去执行<clinit>()方法,其他线程阻塞等待,如果有耗时很长的操作,可能会造成多个进程堵塞。)
使用()
卸载()
类加载器
通过一个类的全限定名来获取描述该类的二进制字节流。
对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。
一个简单的相同类类不同加载器对比。
public class JvmTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException{
try {
String filename = name.substring(name.lastIndexOf(".")+1)+".class";
InputStream is = getClass().getResourceAsStream(filename);
if(is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
};
Object obj = myLoader.loadClass("MMemory.JvmTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof MMemory.JvmTest);
}
}
类型检查返回false,这是因为Java虚拟机中同时存在两个JVMTest,一个是由虚拟机的应用程序类加载器加载的,另一个是由自定义的类加载器加载的。
双亲委派模型
三种类加载器,启动类加载器
这里使用双亲委派模型,保证了无论哪一个类加载器想要加载比如rt.jar。最后都会委派给启动类加载器。实现集中在java.lang.ClassLoader的loadClass方法中。
模块化下的类加载器
扩展类加载器变成平台类加载器。