java类的装载
类的装载过程:
1 类加载
2 链接(验证-准备-解析)
3 初始化
首先是类加载过程:
先是获取类的二进制流,转到内存的方法区(放置类的元数据,类型,方法,形参类型,返回值地址。。。),在堆中生成对应的java.lang.class对象。
再到链接:
验证:文件格式验证(是否以0xCAFEBABE开头,版本号),元数据验证(是否有父类,object类等,是否有继承错误,方法是否实现,重写等),字节码校验(操作数栈,变量是否吻合,栈空间大小问题,跳转指令跳转到偏移量等问题),符号引用验证(权限验证,private,protected这种,检查常量池中描述的类或接口是否存在)。
准备:会对全局变量进行初始值的设定(例如:private static int i=1 ,会先初始成0,在初始化<clinit>才会初始成1,除了private static final i = 1,一开始就会初始成1,final会特例)。
解析:符号引用会替换为直接引用(符号引用指的是,若没有继承,默认是继承java.lang.object类,在这个类的常量池中有个字符串是java.lang.object,只是用来表示,直接引用是一段内存地址,指向真正的对象)。
看过本书说过,引用不止有指向堆中得对象,还有种是在堆中有个句柄池,指向对象,引用指向句柄池。在进行gc回收算法的时候,对象发生地址改变,会对直接引用有影响,用句柄池的话,引用地址就不会变化了,我想这就是有些引用前面加final原因,使用句柄池后,在发生对象部分回收后在内存中移动,这时候修改同在堆的句柄池地址会比修改栈上的引用地址快。
最后初始化:
执行类构造初始化<clinit>,给staic变量赋值,运行静态块,对象块,父类必须在之前先调用父类的<clinit>(<clinit>是线程安全的)。
类的装载器ClassLoader:
ClassLoader是一个抽象类,ClassLoader的实例将读入java字节码将类装载入jvm,可以定制符合不同字节码流获取方式(可以从网络或文件加载),在装载负责加载阶段。
ClassLoader比较重要方法:loadClass()(根据类名去装载,返回类的信息),defineClass()(定义一个类,在形参传入类的二进制字节码),findClass()(loadClass回调方法,自定义装载器做法),findLoad()(查看是否是已装载类)
ClassLoader拥有四种:
BootStrap ClassLoader(加载系统类 rt.jar)
Extension ClassLoader(加载扩展类 lib/ext/*.jar)
App ClassLoader(加载应用类,也就是我们自己写的类 ,classpath下)
Custom ClassLoader(自定义加载器)
除了第一个加载都有个Parent作为父亲,按照上面的排序进行 自底向上检查类是否加载(寻找需要加载的类是否在对应装载器的目录下,找不到会报ClassNotFoundException),自顶向下加载类(自上而下加载对应装载器路径下的类)。
protected synchronized Class<?> loadClass(String name,boolean resolve) throw ClassNotFoundException{
//检查请求的类是否已经被加载过了
Class c = findLoadedClass(name);
if(c==null){
try{
if(parent!=null){
c = parent.loadClass(name,false);
}else{
c = findBootstrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
//如果父类加载器抛出ClassNotFoundException说明父类无法完成类加载
}
if(c == null){
c = findClass(name);
}
}
if(resolve){
resolveClass(c);
}
return c;
}
上面这段代码是加载类,先是查找是否被加载,没有的话会去请求父类加载,这就是自顶向下的加载。
使用(/-Xbootclasspath)指定应用类加载路径,最终会使用bootstrap加载器加载应用类(我们自己写的类),本来是使用app加载器加载。使用反射用app classloader先加载,就可以避免以上情况。
双亲模式:顶层ClassLoader无法加载底层的ClassLoader类,是因为寻找加载类和加载类的方式的问题。但是在核心类rt.jar却有应用层的类的接口,这样会造成问题,这样就需要个上下文加载器(Thread.setContextClassLoader()),这只是个线程。
双亲模式破坏:tomcat(先加载自身的.class),OSGi(可以进行模块化,热加载)
热替换:系统无需重启,在类被替换后马上生效。
内存溢出:生成对象过多。
内存泄漏:对象无法回收。