七.ClassLoader(abstract):类加载器
类加载器是负责加载类的对象。ClassLoader 类是一个抽象类。如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。
一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”。每个 Class 对象都包含一个对定义它的 ClassLoader 的引用。
数组类的 Class 对象不是由类加载器创建的,而是由 Java 运行时根据需要自动创建。数组类的类加载器由 Class.getClassLoader() 返回,该加载器与其元素类型的类加载器是相同的;
如果该元素类型是基本类型,则该数组类没有类加载器(bootstrap classLoader)。
应用程序需要实现 ClassLoader 的子类,以扩展 Java 虚拟机动态加载类的方式。
java包中可供参考使用的ClassLoader实现有:java.net.URLClassLoader,java.security.SecureClassLoader,
- protected ClassLoader(ClassLoader parent)
- protected ClassLoader()
ClassLoader的构造器是protected,由此可见其为继承而设计的,构造器中可以指定其parent类加载器;无参构造器的父加载器为getSystemClassLoader()返回的类加载器;
有parent类加载器创建新的类加载器.
- public Class<?> loadClass(String name) throws ClassNotFoundException
- protectd Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException
有ClassLoader实例负责加载类,注意第二个方法是protected,意味着getClassLoader()获取的实例只能使用loadClass(String name)来加载指定的类,开发者可以通过继承ClassLoader来扩展loadClass(String name,boolean resolve),第二个参数resolve表示是否链接class文件(并不初始化Class),在
一个Class类型可以创建实例之前,必须被初始化(newInstance()将会对是否resolve进行校验,如果Class还没有resolve将会立即resolve,同时初始化Class).
loadClass(String name)默认resolve为false.其等效于Class.forName(String name,false)。注意resolve的过程就是将Class中的static属性设置为默认值,但不会执行static区块;Class的初始化时将会执行static区块,初始化static属性,其中Class.forName则会初始化类,而loadClass不会。其中对Class引用使用反射机制,或者调用一个类的静态方法或者属性时,均会触发Class的初始化。
loadClass方法是一个同步方法(在1.7开始,loadClass方法执行时,首先需要获取一个object同步锁,这个锁根据classname而定,因此并发加载类并不会带来问题).
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
....
}
}
protected Object getClassLoadingLock(String className) {
Object lock = this;
if (parallelLockMap != null) {
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);//ConcurrentHashMap
if (lock == null) {
lock = newLock;
}
}
return lock;
}
对于同一个classLoader实例,对类的加载是顺序的,每个classLoader实例(自定义)都维持有一个"已加载类"缓存表(vector),在加载类时,
如果指定的类已经加载则直接获得现有的Class引用.因此不同的classLoader实例加载相同的类,将会得到不同的Class引用.(OSGI)
ClassLoader即为类加载器,负责将编译成功的.class文件或者符合JVM编译规则的文件/二进制流信息,加载至JVM中,最终获取一个java.lang.Class实例.
JVM中有3个级别的ClassLoader(层级关系):Bootstrap ClassLoader <-- Extension ClassLoader <-- System classpath ClassLoader <-- Customer ClassLoader
- Bootstrap ClassLoader为JVM内置的引导类加载器,纯粹基于本地实现,无法被直接获取或者扩展,它负责加载$java$/lib下类,是JDK运行的必要类.任何JDK API类的加载器均为Bootstrap ClassLoader,此类加载器不可被外部使用或者获取,尝试获取Bootstrap ClassLoader将得到null.
- Extension ClassLoader是JVM扩展API类加载器,负责加载$java$/lib/ext下第三方扩展类(包括sun扩展包,自定义扩展包,JDK优化增益包等),其parent类加载器为Bootstrap.
- System classpath ClassLoader即为AppClassLoader,负责加载classpath下(应用的classpath下)API,当然用户自定义的类也是由此ClassLoader加载.它的引用可被程序获取.它是自定义Classloader的父加载器.
自定义ClassLoader也属于AppClassLoader.
ClassLoader类提供了一个静态方法getSystemClassLoader(),可以直接获取AppClassLoader的引用.它经常作为自定义ClassLoader的parent.
loadClass(Stirng name,boolean resolve)内部执行步骤:
1) 查找name的类是否已经被加载,已经被加载的被将会在VM中保存并标记..如果此类已经被加载,则直接返回相应Class的引用. --->findLoadedClass(name)
2) 如果此类未被加载,则托付给其指定的parent类加载器(ClassLoader构造器制定parent类加载器,默认为SystemClassLoader).
parent.loadClass同是也会查找类,如果parent找到此类则加载 --->parent.loadClass(name,false),即不分析类
3) 如果parent类加载器为null(传递参数为null,可能期望通过bootstrap查找类),则直接使用Bootstrap去查找类是否被加载.
4) 如果2)和3)均为查找到类,则抛出ClassNotFoundException
5) 抛出异常之后,将会被捕获,此时类加载将交付给当前ClassLoader,通过调用当前classLoader.findClass(name)来执行.[真绕..........]
自定义ClassLoader类中findClass方法需要被重写(此方法非abstract,但是默认实现没有任何实体,直接抛出ClassNotFoundException)
根据ClassLoader API的要求,findClass最主要职责是从本地/网络地址/IO流等任何方式,获取class字节码,并把字节码交付给defineClass方法有JVM为其解析.
defineClass为native方法,一般情况下无需扩展(事实上我们最好不要扩展,此方法已经为我们做好了通过字节流解析成Class引用的所有底层功能).
思路:重写findClass,在findClass中加载并获得class文件的字节流信息,然后直接交付给defineClass即可.
在对类加载时,会触发对classname的校验,任何自定义类或者package,不能以java.开头..(原因是:java api中的类不能被外部以任何方式再次加载.)
对于相同的类,JVM只会加载一次.如果不同的classLoader实例多次(并发操作,或者使用不当,比如loadClass时不校验类是否已经被加载)加载,将会得到不同的class引用.
//伪代码:
/例如:
URL urls[] = new URL[]{new URL("file:/c:/...")};
URLClassLoader ucl1 = new URLClassLoader(urls);
URLClassLoader ucl2 = new URLClassLoader(urls);
Class<?> c1 = ucl1.loadClass("Foo");
Class<?> c2 = ucl2.loadClass("Foo");
System.out.println(c1 == c2); //false
///
ClassLoader s = ClassLoader.getSystemClassLoader();//AppClassLoader
System.out.println("System:" + s);
ClassLoader e = s.getParent();//ExtClassLoader
System.out.println(e);
System.out.println(e.getParent());//null
//
通常情况下,所有的servlet和filter使用一个ClassLoader。每个jsp都使用一个独立的ClassLoader。(这也是JSP页面可以实现动态部署的原因)
加载类可以通过Class.forName(name)或者classloader.loadClass(name),其中Class.forName底层采用了ClassLoader.getCallerClassLoader()作为类加载器(即调用者的类加载器)
- protected Class<?> findClass(String name) throws ClassNotFoundException:查找class文件,此方法默认直接抛出异常,需要实现类是实现.内部仍然需要defineClass()支持。对于自定义的classLoader,通常需要实现此方法,并在方法内调用defineClass方法。
- protected final C lass<?> defineClass(String name,byte[] b,int off,int len) throws ClassFormatError: 此方法时JVM内部实现,将字节码数组构建成Class引用,通常此方法我们无法修改。
此方法不能背重写,将一个byte[]转换成class类的实例.off为数据起始偏移量,len为数据长度.底层基于native实现.
- protected final void resolveClass(Class clazz): 链接指定的类.
- protected final Class<?> findLoadedClass(String name):查找已经被加载的类.
//ClassLoader类中还提供了大量获取资源的方法,方便我们获取.class/properties/resource等资源信息.
- public InputStream getResourcesAsStream(String);
- public URL getResource(String name);
- public InputStream getResourceAsStream(String name);
- public Enumeration<URL> getResources(String name) throws IOException;
八.Enum类(abstract)
Enum类是枚举enum的类型,它扩展了comparable和seriaziable接口.即enum支持序列化和反序列化,也是简单的单利模式控制策略之一.
1) Enum类不应该具有finalize()实现,被空实现.
2) Enum类中,在反序列化是readObject(ObjectInputStream)直接抛出异常.
九.Object类:
- public final Class<?> getClass():获取当前对象的Class实例引用.
- public native int hashCode():获取当前对象的hashcode,为int类型.hashCode生成算法对hash存储具有很重要的意义.用户可以重写此方法来生成自己的hashCode.对于JVM,对象的hashcode默认算法为内存地址所对应的int值.对于某些重写hashcode方法之后的类(JDK中,比如string),hashcode值可能为负值;对于Object.hashCode()方法内部实现为对象引用所在的内存地址(并在"位溢出"之后的转换成int),当对象第一次调用hashCode方法时会把它的hashcode缓存起来(对象实例的header中),此后的每次调用都将返回相同的值..一个对象所在的内存实际地址会因为OS或者JVM的runtime期间的调整而发生迁移,因此hashcode值本身并不完全反应出object的内存地址情况,同时内存地址的迁移也会导致不同的对象hit在同一个地址上....对于未重写hashcode和equals方法的Object实例而言,JVM只能保证互相equals的对象,hashcode是一样的;但是不能保证不同的对象其hashcode一定是不同的..有可能相同hashcode的对象实例,它们是不同类型的(classType).[参见hotspot源码实现:openjdk\hotspot\src\share\vm\runtime\synchronizer.cpp中FastHashCode方法,oops/markOop.hpp(对象header信息)]
if (mark->is_neutral()) { hash = mark->hash(); // this is a normal header if (hash) { // if it has hash, just return it return hash; } hash = get_next_hash(Self, obj); // allocate a new hash code temp = mark->copy_set_hash(hash); // merge the hash code into header ... }
- public boolean equals(Object):比较对象是否"相同",可以重写..默认行为等效于"==".
- public Object clone():对象"复制",只对对象的属性指针进行复制.即浅复制,只有实现了Clonable接口的对象才能被"复制".
- public String toString():返回对象的String形式,默认格式为getClassName() + "@" + Integer.toHexString(hashCode()).
- public final void notify():唤醒在此对象上监视器等待的单个线程(随即选取).如果有多个线程在此对象上等待,则选取一个.直到当前线程放弃对象上的锁定,才能继续执行被唤醒的线程.被唤醒的线程将以常规的方式与该对象上主动同步的其他线程进行竞争.例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。 如果被notify的线程没有"抢到"对象锁,那么它将继续wait.直到再次被notify.此方法只应由作为此对象监视器的所有者的线程来调用.一次只能有一个线程持有对象的监视器.注意notify()方法有可能会导致"假醒",所以它需要条件性的循环判断:
一次notify()可能在某些情况下导致"唤醒信号"丢失,造成"死锁性"的等待.建议使用notifyAll()while(!condition){ object.wait(); }
- public final void notifyAll():唤醒所有线程,事实只会有一个线程最终获取锁.
- public final void wait(long timeout) throws InteruptedException:阻塞当前线程,直到被notify成功.只有获取监视器的线程才能执行此方法.wait方法会丢失对象监视器即释放锁(原子性释放锁).
- protected void finalize() throws Throwable :当垃圾回收器确定不存在该对象的更多引用时,此对象将会被回收.即此对象将不能被任何线程的任何方法所访问时,将可能被GC.在回收之前做一些清除操作,当然也可以再finalize方法中再次使此对象对其他线程可用.finalize方法执行前将会把实例的"终结标记"为true,如果在finalize方法中再次获得了此实例的引用,将不会导致finalize方法再次执行.如果finalize方法抛出未捕获的异常,那么该异常将被忽略,并且该对象的终结操作将终止。finalize方法也遵循Override机制,当子类的实例被回收时,不会执行父类的finalize方法;这称为“终止器逃逸”,在某些情况下会出现问题,建议在子类的finalize方法中(尾部)调用supper.finalize(),即首先执行子类的终止逻辑,然后执行父类的。