介绍了类的加载时机和类的加载过程
class SingleTon {
private static SingleTon singleTon = new SingleTon();
public static int count1;
public static int count2 = 0;
private SingleTon() {
count1++;
count2++;
}
public static SingleTon getInstance() {
return singleTon;
}
}
public class Test {
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}
分析:
1:SingleTon singleTon = SingleTon.getInstance();调用了类的SingleTon调用了类的静态方法,触发类的初始化
2:类加载的时候在准备过程中为类的静态变量分配内存并初始化默认值 singleton=null count1=0,count2=0
3:类初始化化,为类的静态变量赋值和执行静态代码快。singleton赋值为new SingleTon()调用类的构造方法
4:调用类的构造方法后count=1;count2=1
5:继续为count1与count2赋值,此时count1没有赋值操作,所有count1为1,但是count2执行赋值操作就变为0
被动引用例子
1.子类调用父类的静态变量,子类不会被初始化。只有父类被初始化。。对于静态字段,只有直接定义这个字段的类才会被初始化.
2.通过数组定义来引用类,不会触发类的初始化
3.访问类的常量,不会初始化类
class SuperClass {
static {
System.out.println("superclass init");
}
public static int value = 123;
}
class SubClass extends SuperClass {
static {
System.out.println("subclass init");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(SubClass.value);// 被动应用1
SubClass[] sca = new SubClass[10];// 被动引用2
}
}
程序运行输出 superclass init
123
从上面的输入结果证明了被动引用1与被动引用2
class ConstClass {
static {
System.out.println("ConstClass init");
}
public static final String HELLOWORLD = "hello world";
}
public class Test {
public static void main(String[] args) {
System.out.println(ConstClass.HELLOWORLD);// 调用类常量
}
}
程序输出结果
hello world
从上面的输出结果证明了被动引用3
类的初始化
类什么时候才被初始化:
1)创建类的实例,也就是new一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName(“com.lyj.load”))
5)初始化一个类的子类(会首先初始化子类的父类)
6)JVM启动时标明的启动类,即文件名和类名相同的那个类
只有这6中情况才会导致类的类的初始化。
类的初始化步骤:
1)如果这个类还没有被加载和链接,那先进行加载和链接
2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
3)加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的Java.lang.Class对象,用来封装类在方法区类的对象
类的加载的最终产品是位于堆区中的Class对象
Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口
加载类的方式有以下几种:
1)从本地系统直接加载
2)通过网络下载.class文件
3)从zip,jar等归档文件中加载.class文件
4)从专有数据库中提取.class文件
5)将Java源文件动态编译为.class文件(服务器)
类加载(每种类加载器的作用)
启动类加载器(bootstrp class loader)
扩展类加载器
应用程序类加载器
自定义类加载器
双亲委托模型:
代理模式:交给其他加载器来加载指定的类
双亲委托机制:就是某个特定的类加载在接到加载类的请求,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
双亲委托机制是为了保证Java核心库的类型安全。Java之所以出现这条机制,因为是处于安全性考虑。害怕用户自己定义class文件然后自己写一个类加载器来加载原本应该是JVM自己加载的类。这样会是JVM虚拟机混乱或者说会影响到用户的安全。
这种机制就保证不会出现用户能定义java.lang.Object类的情况。
类加载器除了用于加载类,也是安全的最基本的屏障。
双亲委托机制是代理模式的一种
并不是所有的类加载都采用双亲委托机制
tomcat服务器类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。
深入类加载器层次结构(三种类加载器)代理加载模式,双亲委托机制
我们首先要知道在Java中,类加载器也是分等级的。最高级的一种加载器是加载java中的核心包下的类。比如说java.ang.String类就是通过这种类加载器进行加载的。下一个等级的就是额外的类加载器。也是加载一些类的。再下一级的就是应用程序的类加载器。再下一级的就是自定义的类加载器了。但是这几种加载器之间不是继承关系的,而是组合关系的。这是要注意的。
在代理模式中,有一个比较重要的一种是双亲委托模式。所谓的双亲委托模式就是:比如说,我定义了一个类A,在加载类A的时候,首先由自定义的类加载器,再到应用程序的类加载器,再到拓展的类加载器,最后到最高级的加载器。首先是由最高级的加载器进行判断,它是否能够加载这个类,如果能够加载的话,就进行加载。如果不能够进行加载的话,再接下来看一下额外的类加载器能否进行加载。这样一层一层地往下。如果都不能加载的话,那么就会出现错误。这是要注意的。
双亲委托机制的最大的好处就是可以确保是安全的。
比如说,我举一个例子:
我首先定义可java.lang包。然后我在这个package的下面定义了一个String类。由于String类是java中的核心包,理应是由最高等级的那个类加载器进行加载的。我们采用了双亲委托的模式: 由上往下,一层一层进行判断,是否能够加载。首先是在最高等级的那个类加载器当中,发现String类是核心包下的一个类。那么就不会对我们自定义的一个类进行加载了。所以即使我们定义了那个类,我们也不能使用这个类。这就是双亲委托模式的好处,可以确保了安全。但是也并不是所有的类加载的过程都是采用双亲委托模式的。比如Tomcat服务器采用的就不是双亲委托模式,它的加载的过程和双亲委托模式是正好相反的。但是也是代理的模式。
自定义类加载器:
考察点:如何定义自己的类加载器,自己的类加载器和Java自带的类加载器关系如何处理?
虽然在绝大多数情况下系统默认提供的类加载器实现已经可以满足需求。但是在某些情况下,您还是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输Java类的字节代码,为了保证安全性,这些字节码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在Java 虚拟机中运行的类来。
类加载器的开发:
①ClassLoader加载类的顺序
1调用findLoadedClass(String) 来检查是否已经加载类
2在父类加载器上调用loadClass方法。如果父亲不能加载,一次一级一级传给子类
3调用子类findClass(String)方法查找类。若还加载不了就返回ClassNotFoundException,不交给发起请求的加载器的子加载器
②实现自己的类加载器
1 获取类的class文件的字节数组,如loadClassData方法
2将字节数组转换为Class类的实例,重写findClass中调用的defineClass方法
一般来说自己开发的类加载器只需要覆写findClass(Stringname)方法即可。java.lang.ClassLoader类的方法loadClass()封装了前面提到的代理模式的实现。该方法会首先调用findLoadedClass()方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的loadClass()方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用findClass()方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写loadClass()方法,而是覆写findClass()方法。
扩展类加载器——-改变JAVA的父优先类加载顺序
Java的类加载机制默认情况下是采用委托模型:当加载某个类时JVM会首先尝试用当前类加载器的父类加载器加载该类,若父类加载器加载不到再由当前类加载器来加载,因此这种模型又叫做“父优先”模型。
但是在实际项目中我们可能会要求先从当前类加载加载再从父类加载器加载,如项目中的某类的版本可能和Container中的不一致的时候,若还从container加载就会报jar包冲突的异常,实际上jar包冲突的问题在实际开发过程中是经常会遇到的。如我们在开发Loong时就遇到了类似问题。
解决方案是通过扩展自定义的ClassLoader,重写loadClass方法,先从当前类加载器加载再从父类加载器加载。
JVM根据两个方面判断两个类是否相同:一是类的全称;另一个是类加载器.
即使类的全称相同,而使用的加载器不同那Class对象也是不同的.