连接模型
1、Class文件中的常量池<——映射——>类型的运行时常量池(JVM装载后产生的内部版本的常量池,是一个特定于实现的数据结构)
2、动态扩展的两种方法:Class.forName() ClassLoader.loadClass()
区别:前者的单参数或者三参数且initial为true可以保证返回的类型一定被初始化了。后者返回的类型有可能没有初始化,后者适合用户自定义加载机制(特定的装载需求或者安全性考虑)
3、双亲委派模型 —— 推荐使用双亲委派模型来创建类加载器
--当符合双亲委派模型的类装载一个类型的时候,它首先委派它的双亲——请求它的双亲试着装载这个类型,它的双亲再依次调用各自的双亲。这个委派的过程一直进行到委派链的末端,一般来说应该是启动类装载器。
--在Java术语中,要求某个类装载器去装载一个类型,但是却返回了其他类装载器装载的类型,这种装载器被称为是那个类型的初始类装载器(包括定义的那个);而实际定义那个类型的类装载器被称为该类型的定义类装载器。
4、常量池解析
--数组类:如果是关于引用的数组,数组会被标记为是由定义它的元素类型的类装载器定义的;如果是关于基本类型的数组,数组类会被标记为是由启动类装载器定义的。
如果数组是关于基本类型的数组,那么虚拟机立即就会创建关于那个元素类型的新数组类,维数也在此时确定,然后创建一个Class的实例来代表这个类型。如果数组是关于引用的数组,那么这一步骤发生在解析了元素类型之后。
--非数组类和接口
每一个Java虚拟机必须维护一张内部列表,它列出了所有在运行程序的过程中已被"拘留intern"的字符串对象的引用。维护这个列表的关键是任何特定的字符序列在这个列表上只出现一次。
所有字面上表达的字符串都在解析CONSTANT_String_info入口的过程中被拘留了。
例如:
Class Example{
public void main(String[] args){
String argZero = args[0]; //it's assigned a String from the command line,does //not reference a string literal.so not interned.
String literalString = "Hi";
System.out.println("Before intern..");
if(argZero == literalString)
System.out.println("They are same");
else
System.out.println("They are different");
System.out.println("After intern..");
argZero = argZero.intern();
if(argZero == literalString)
System.out.println("They are same");
else
System.out.println("They are different");
}
}
运行结果(把字符串"Hi"作为命令行第一个参数),第一次不相同,第二次相同;
--装载约束
--编译时常量解析
被初始化为编译时常量的静态final变量的引用,在编译时被解析为常量池的一个本地拷贝,这对于所有的基本类型和java.lang.String都是正确的。这种对于常量的特殊处理使Java语言具有了两个特性。
首先,常量值的本地拷贝使得静态final变量可以用于switch语句中的case表达式。在字节码中实现switch语句需要case值内嵌在字节码流中。这些指令不支持运行时解析case值。
其次,另一个动机就是条件编译。
--直接引用的格式
对于实例方法和实例变量都是通过偏移量寻找,而且子类相应覆盖方法的偏移量和父类保持一致。对于每个类型,它都有一个单独的方法表。例如java.lang.Object中toString()方法的偏移量是7,那么继承它的子类的toString()在它的方法表中的偏移量也是7。
当虚拟机有一个指向类类型的引用的时候,它总是可以依靠方法表偏移量。如果在Dog类中sayHello()方法出现在偏移量7,那么在Dog的所有子类中它都出现在偏移量7.不过当引用是指向接口类型时,这就不成立了。当通过接口引用来访问实例方法的时候,直接引用不能保证得到方法表偏移量,必须搜索对象的类的方法表来找到一个合适的方法。这种调用接口引用的实例方法的途径会比在类引用上调用实例方法慢很多。
--对于CONSTANT_String_info入口的解析过程
例如 ldc #3。ldc指令引用了第3个常量池入口,那是一个CONSTANT_String_info入口,表示指向文字字符串"Hello World"的符号引用,虚拟机在常量池中查找,发现这个入口还没有被解析过。为了解析它,虚拟机创建一个值为"Hello World"的新字符串对象,并且在内部拘留它,然后把这个新对象的引用放到常量池入口3,把ldc指令替换成ldc_quick。
--当Java虚拟机解析一个符号引用的时候,它使用和定义引用类型的同一个类装载器来初始装载被引用的类型。
--类型安全性问题
出现原因:因为在一个Java虚拟机中的多个命名空间可能共享类型。如果某个类装载器委派另外一个类装载器,而后者定义了这个类型,这两个类装载器都会被标记为这个类型的初始类装载器。被委派的类装载器装载的这个类型,在所有被标记为该类型的初始类装载器的命名空间中共享。——产生"类型混淆"问题
要唯一的确定一个类型,Java虚拟机需要知道类型的全限定名以及这个类型的定义类装载器(与初始类装载器区分)。
装载约束:当多个类装载器被标记为同名类型的初始化加载器后,便会有一个装载约束记录下来(这两个装载器中的类型必须为同一个类型),在以后的解析中,便会进行比较,从而避免类型混淆的发生。