类的加载——加载,链接(验证+准备)
Posted huangxiangqi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类的加载——加载,链接(验证+准备)相关的知识,希望对你有一定的参考价值。
加载
创建一个以N为名称的类或接口C,需要通过另一个类或接口D
在运行时,一个类或接口由 它的名字和它的defining loader共同决定
每个类或接口——都属于一个运行时包
一,类或接口C不是数组类型
用类加载器加载类或接口C的二进制表示
类加载器有两种:bootstrap class loader和用户自定义类加载器
加载采用双亲加载机制——发出委托的加载器为initiating loader,最终完成加载的加载器为defining loader
如何加载C呢?
1,如果D由bootstrap loader加载,那么C也由bootstrap loader记载
2,如果D由用户自定义类加载器加载,那么C也由相同的用户自定义类加载器加载
大致过程如下
①initiating loader把委托传给它的父类,父类传给父类的父类.....直到传递到bootstrap loader
②从bootstrap loader开始,JVM检查bootstrap loader是否已经被记录为以N为名称的类C的加载器——如果是,说明C已经加载过了,结束
否则,bootstrap loader会在它的搜索路径搜索,搜索到了就加载这个类,搜索不到,就由bootstrap loader的子类重复上述操作......直到加载完成
二,类或接口C是数组类型
类或接口C的创建通过JAVA虚拟机而不是类加载器,由JVM直接在内存中动态构造出来,但是D的defining loader也会参与数组类C的创建过程.
链接:验证+准备
验证阶段:目的是确保Class文件的字节流中包含的信息符合《JVM规范》的全部约束要求——文件格式验证+元数据验证+字节码验证+符号引用验证
准备阶段:为类中定义的静态变量分配内存并初始化静态变量,static声明的初始化为默认值,final static声明——其值存储在运行时数据区,将值取出用于初始化
由于非静态字段(非静态变量)随着对象在堆中分配空间,这里是在方法区中分配空间,所以不考虑非静态字段,但是还是要为非静态字段设置ID
---------------------------------------------------以上都是JVM规范中的屁话,接下来才是重点---------------------------------------------------------------------------------------------------------------------------------------------
加载阶段,JVM要完成三件事:Ⅰ,通过类的全限定名获取这个类的二进制字节流(byte[ ])。 Ⅱ,将这个二进制字节流所代表的静态存储结构转换为方法区中的运行时数据结构。
Ⅲ,在内存中生成一个代表这个类的实例对象,这个实例对象将作为方法区中这个类的各种数据的访问入口
!!!接下的几段话为我个人理解,不知道是否正确,但我觉得很重要
!!!类由 实例变量(字段)和方法组成——>除了非静态字段,会随着对象的创建在堆中分配空间,这个类的其他所有信息都会存储在方法区中。所以,如果你想访问这个类的信息(某些字段或者方法),你需要在内存中生成一个代表这个类的对象,既可以直接访问这个对象上的非静态字段,还可以通过这个对象作为方法区中这个类的各种数据的访问入口
如何达到这个结果呢?——用JClass作为载体,但是根据类的二进制字节流——>classfile——>JClass,创建出的JClass对象信息并不完整(有字段没有初始化),我们上面说的要让除了非静态字段的其他所有信息都存储在方法区中——>我们把除了非静态字段的其他所有信息都附加到JClass上,然后把JClass添加到方法区中,就达到这个结果了
!!所以我们要保证JClass中存储的信息是完整的,包含了除了非静态字段以外的全部信息
Vars中有slot[ ],所以staticVars用来存储所有的静态字段,静态字段完成初始化后,静态字段用它们的ID作为索引,存放在staticVars.slots[ID]中
InitState表示这个JClass的初始化前的状态
public class JClass {
private short accessFlags;
private String name;
private String superClassName;
private String[] interfaceNames;
private RuntimeConstantPool runtimeConstantPool;
private Field[] fields;
private Method[] methods;
private EntryType loadEntryType;
private JClass superClass;
private JClass[] interfaces;
private int instanceSlotCount;
private int staticSlotCount;
private Vars staticVars;
private InitState initState;
public JClass(ClassFile classFile) {
this.accessFlags = classFile.getAccessFlags();
this.name = classFile.getClassName();
if (!this.name.equals("java/lang/Object")) {
this.superClassName = classFile.getSuperClassName();
} else {
this.superClassName = "";
}
this.interfaceNames = classFile.getInterfaceNames();
this.fields = parseFields(classFile.getFields());
this.methods = parseMethods(classFile.getMethods());
this.runtimeConstantPool = parseRuntimeConstantPool(classFile.getConstantPool());
}
开始叙述过程
①设置搜索路径
ClassFileReader.setUserClasspath(String.join(File.separator,testPath,"user","test4"));
②查找这个类是否已经被加载过(如果一个类已经被加载过,可以在方法区中找到它),如果已经加载过,从方法区中取出这个类,返回。如果没有,开始以下操作
③读取程序——>得到二进制字节流(byte[ ])和definingEntry,definingEntry用于标识这个类的类加载器是谁(对应加载阶段三件事的Ⅰ)
public JClass loadClass(String className, EntryType initiatingEntry) throws ClassNotFoundException { JClass ret; if ((ret = methodArea.findClass(className)) == null) {//查找是否已经加载过 return loadNonArrayClass(className, initiatingEntry); } return ret; } private JClass loadNonArrayClass(String className, EntryType initiatingEntry) throws ClassNotFoundException { try { Pair<byte[], Integer> res = classFileReader.readClassFile(className, initiatingEntry); byte[] data = res.getKey(); EntryType definingEntry = new EntryType(res.getValue());
④二进制字节流——>创建classfile对象——>创建JClass对象A,此时JClass对象只有部分数据完成了初始化,还有 EntryType loadEntryType,JClass superclass,JClass[ ]interfaces,
int instanceSlotCount,int staticSlotCount,Vars staticVars,InitState initstate没有完成初始化
⑤,设置A的loadEntry,递归创建父类的JClass,创建接口们的JClass,把父类的JClass和接口的JClass设为A的superclass和interfaces
private JClass defineClass(byte[] data, EntryType definingEntry) throws ClassNotFoundException { ClassFile classFile = new ClassFile(data);//4 JClass clazz = new JClass(classFile);//4 resolveSuperClass(clazz);//5 resolveInterfaces(clazz);//5 clazz.setLoadEntryType(definingEntry);//5 return clazz; } private void resolveSuperClass(JClass clazz) throws ClassNotFoundException { //递归加载父类的JClass,并将其设为A的superclass if(!clazz.getName().equals("java/lang/Object")){ JClass father=loadClass(clazz.getSuperClassName(),clazz.getLoadEntryType()); clazz.setSuperClass(father); }else{ return; } } private void resolveInterfaces(JClass clazz) throws ClassNotFoundException { //加载接口们,并将其设为A的interfaces String[] namesOfInterfaces=clazz.getInterfaceNames(); JClass[]interfaces=new JClass[namesOfInterfaces.length]; for(int i=0;i<namesOfInterfaces.length;i++){ interfaces[i]=classLoader.loadClass(namesOfInterfaces[i],clazz.getLoadEntryType()); } clazz.setInterfaces(interfaces); }
以上为加载阶段,接下来为准备阶段
private void linkClass(JClass clazz) { verify(clazz); prepare(clazz); } private void verify(JClass clazz) { } private void prepare(JClass clazz) { calStaticFieldSlotIDs(clazz);//7 calInstanceFieldSlotIDs(clazz);//8 allocAndInitStaticVars(clazz);//9 clazz.setInitState(InitState.PREPARED);//9 }
⑦为每个非静态字段设置ID,并返回最终的ID值用于初始化instanceSlotCount——对应方法 calStaticFieldSlotIDs(clazz);
注意两点——一是,由于父类的非静态字段会被子类所继承,但是从子类取出的Field[ ]并不会包含从父类继承来的字段,所以要先从父类取得父类的InstanceSlotCount( )
二是,long和double占据2个ID
private void calInstanceFieldSlotIDs(JClass clazz) { int slotID = 0; if (clazz.getSuperClass() != null) { slotID = clazz.getSuperClass().getInstanceSlotCount(); } Field[] fields = clazz.getFields(); for (Field f : fields) { if (!f.isStatic()) { f.setSlotID(slotID); slotID++; if (f.isLongOrDouble()) slotID++; } } clazz.setInstanceSlotCount(slotID); }
⑧为每个静态字段分ID,并返回最终的ID值用于初始化staticSlotCount(double和long占2个ID)——对应方法calStaticFieldSlotIDs( );
private void calStaticFieldSlotIDs(JClass clazz) { int slotID = 0; Field[] fields = clazz.getFields(); for (Field f : fields) { if (f.isStatic()) { f.setSlotID(slotID); slotID++; if (f.isLongOrDouble()) slotID++; } } clazz.setStaticSlotCount(slotID); }
⑨初始化静态字段——非final的初始化为默认值,final的从运行时常量池中取出相对应的值用于初始化——对应方法allocAndInitStaticVars( );
并且设置JClass的IniState——对应方法clazz.setInitState(InitState.PREPARED);
private void allocAndInitStaticVars(JClass clazz) { clazz.setStaticVars(new Vars(clazz.getStaticSlotCount())); Field[] fields = clazz.getFields(); for (Field f : fields) { if(f.isStatic()){ if(f.isFinal()){ loadValueFromRTCP(clazz,f); }else{ initDefaultValue(clazz,f); } } } } private void initDefaultValue(JClass clazz, Field field) { Vars vars=clazz.getStaticVars(); int slotID=field.getSlotID(); String descriptor=field.descriptor; char judge=descriptor.charAt(0); if(judge==‘Z‘){ vars.setInt(slotID,0); }else if(judge==‘B‘){ vars.setInt(slotID,0); }else if(judge==‘C‘){ vars.setInt(slotID,0); }else if(judge==‘S‘){ vars.setInt(slotID,0); }else if(judge==‘I‘){ vars.setInt(slotID,0); }else if (judge==‘F‘){ vars.setFloat(slotID,0.0F); }else if(judge==‘J‘){ vars.setLong(slotID,0); }else if(judge==‘D‘){ vars.setDouble(slotID,0.0D); }else { vars.setObjectRef(slotID,new NullObject()); } } private void loadValueFromRTCP(JClass clazz, Field field) { RuntimeConstantPool runtimeConstantPool=clazz.getRuntimeConstantPool(); Vars vars=clazz.getStaticVars(); int slotID=field.getSlotID(); int consantValueIndex=field.getConstValueIndex(); String descriptor=field.getDescriptor(); char judge=descriptor.charAt(0); if(consantValueIndex>0){ if (judge == ‘Z‘) { int intval = ((IntWrapper) runtimeConstantPool.getConstant(consantValueIndex)).getValue(); vars.setInt(slotID, intval); } else if (judge == ‘B‘) { int intval = ((IntWrapper) runtimeConstantPool.getConstant(consantValueIndex)).getValue(); vars.setInt(slotID, intval); } else if (judge == ‘C‘) { int intval = ((IntWrapper) runtimeConstantPool.getConstant(consantValueIndex)).getValue(); vars.setInt(slotID, intval); } else if (judge == ‘S‘) { int intval = ((IntWrapper) runtimeConstantPool.getConstant(consantValueIndex)).getValue(); vars.setInt(slotID, intval); } else if (judge == ‘I‘) { int intval = ((IntWrapper) runtimeConstantPool.getConstant(consantValueIndex)).getValue(); vars.setInt(slotID, intval); } else if (judge == ‘J‘) { long longval = ((LongWrapper) runtimeConstantPool.getConstant(consantValueIndex)).getValue(); vars.setLong(slotID, longval); } else if (judge == ‘D‘) { double doubleval = ((DoubleWrapper) runtimeConstantPool.getConstant(consantValueIndex)).getValue(); vars.setDouble(slotID, doubleval); } else if (judge == ‘F‘) { float floatval = ((FloatWrapper) runtimeConstantPool.getConstant(consantValueIndex)).getValue(); vars.setFloat(slotID, floatval); } else { return; } } }
⑩,到这边,JClass的除了非静态字段以外的所有信息已经具备,把JClass放入方法区中,这样JClass携带的所有信息就放入了方法区中
methodArea.addClass(clazz.getName(),clazz);
最后思考:为什么设置ID的时候long和double要用2个ID呢
因为:Vars中的slot[ ] varslot,其元素为Slot,结构如下
public class Slot { private JObject object; private Integer value; }
如果往Vars存入JObject,用到的是Slot中的JObject,Integer用不到
如果往Vars中存入基本数据类型,用到Slot中的Integer,JObject用不到
因为long和double都是64位,而int位32位,为了防止精度丢失,我们都是把long和double拆成2个int存放在varslot中,所以要占2个ID,对应varslot中2个位置
以上是关于类的加载——加载,链接(验证+准备)的主要内容,如果未能解决你的问题,请参考以下文章