三个大阶段
一:装载(三个小阶段)
1.装载:就是读取class文件的字节流,放到jvm中
二:连接(分动态和静态,静态很少使用,下面按顺序三步骤)
1.验证:校验字节的正确性
2.常量池解析:把符号引用替换为指针引用,就是将方法名和类名替换成Class对象的内存地址(类A里有类B,使用类A的时候,先判断A的常量池中是B的class对象,还是B的字符串表示,如果是字符串,则将其替换成Class对象的引用)
3.准备:给静态变量分配内存并且根据相应类型赋默认值(注意此处是默认值)
三:初始化:对静态变量赋指定值,同时执行静态代码块
下面将用代码阐述上述逐个过程
一.1:将MyClass.class文件通过字节数组读到jvm中
一.2:校验这些字节是不是和jvm定义的一样,假设int在jvm中的机器码是8B,那么此时要校验int a是不是8B a
一.3:使int a=0;
二:在jvm中m1方法肯定不能直接调用m2方法,因为jvm不知道m2是个什么东西,m1调用的应该是m2方法开始的内存地址
三:使int a=1;
关于双亲委派模型
双亲委派存在的意义
1.java的沙箱安全机制,我们自己不能创建java.lang开头的java类
2.避免重复加载类
什么时候需要打破双亲委派
当需要同一个类,并且不同版本的时候
典型的例子就是tomcat的各种类加载器,tomcat实现了许多类加载器,主要目的就是为了实现使用相同名称但是版本又不相同的类
如何自定义类加载器
1.通常实现findclass,本质该方法也会调用loadClass
2.但是如果想打破双亲模型,则覆盖loadClass方法
Java的命名空间(对程序员是透明的,所以我们自己无法定义命名空间名称)
现在有自定义类加载器CL,有一个类A,类A中有一个属性类B,现new两个CL,一个叫CL1,一个叫CL2,它俩都加载类A,返回Class A对象叫Class1和Class2,那么此时Class1!=Class2,而Class1中的B.class也!=Class2中的B.class,由上述结论可知:不同的自定义类加载器,加载的类是相互隔离的,切这些加载的类中引用的类,也是相互隔离的,下面是本结论的代码验证
将A和B编译成class文件,放到桌面上,下面自定义类加载器
最后一个main方法
下面这行代码会打印出null
因为String属于rt包下,由启动类加载器加载(bootstrap classloader),而启动类加载器由c++实现,所以这行代码打印null,因为java中没有启动类加载器
方法区会存放以下类型信息
0.Class对象的引用
1.类型的全限定名
2.直接父类的全限定名(这样就可以找到父类,根据父类可以找到爷爷类)
3.类型标识(用来标识当前类型是类还是接口)
4.访问修饰符(public,abstract,final)
5.直接接口的全限定名列表(就是实现了哪些接口)
6.该类的常量池(字段信息+方法信息),可参考常量池的表现方式 字段修饰符
字段类型
字段名
方法修饰符
方法返回值
方法名
方法参数的类型集合(按顺序)
方法的字节码
操作数栈和局部变量去大小
常量(其他类如果使用该常量,则其他类会将该常量复制到自己的常量池)
7.ClassLoader的引用(表示哪个类加载器加载的该类)
class对象被回收的条件
class对象被回收的条件必须要同时满足下面3个
1.该class对象不存在实例对象
2.加载该class对象的classloader被回收
3.没有其他地方使用该class对象(通过反射)