1.类加载:加载、验证、准备、解析、初始化(clinit)
注意:类的初始化和实例化不同,初始化只是将Class文件加载到方法区,只初始化了静态块和静态变量,未掉用构造函数,类的加载只进行一次,但是实例化是创建对象,可以多次创建并且javap查看指令,在构造函数内部<init>使用<invokespecial>初始化实例域和构造函数,可以多次进行
2.需要进行类加载的五种情况(主动引用):
(1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化,生成这四条指令常见的Java代码:new实例化一个对象、读取或者设置一个类的静态字段(被final修饰的、已在编译器把结果放入常量池的静态字段除外)、以及调用一个类的静态方法的时候。
(2)使用 java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化、则需要先触发其初始化
(3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
(4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类
(5)当使用JDK 1.7的动态语言支持时如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先初始化
3.被动引用(不能对类进行初始化):
(1)子类调用父类的静态变量(static非final)
(2)构造类的数组对象
package 被动引用;
public class SuperClass {
static{
System.out.println("SuperClass init!");
}
public static int value = 123;
}
===========================================================================================
package 被动引用;
public class SubClass extends SuperClass {
static{
System.out.println("SubClass init!");
}
}
=========================================================================================
package 被动引用;
public class NotInitialization {
public static void main(String[] args) {
System.out.println(SubClass.value);
//SuperClass init!
//123
SuperClass[] sca = new SuperClass[10];
}
}
=========================================================================================
(3)类调用static final变量不会导致类加载,因为final在编译器就将常量存到类的常量池了
接口也有初始化的,与类的区别在于一个类初始化时要求父类已经全部初始化,但是一个接口在初始化时,并不要求父接口全部完成了初始化,只有在真正使用到父接口的时候(如引用接口中的常量)才会初始化
4.类加载的过程:加载、验证、准备、解析和初始化详解
加载:加载阶段首先要通过一个类的全限定名来获取定义此类的二进制字节流,然后将这个二进制流代表的静态存储结构转化为方法区的运行时数据结构,最后在内存中生成一个代表这个类的java.lag.CLass对象作为方法区这个类的各种数据的访问入口。在类加载中,可以定义自己的类加载器,可能要重写loadClass或者findClass方法,对于数组类型,如果是int[],Java虚拟机会把数组标记为与引导类加载器相关联
验证:编译器虽然会检查程序代码,但是Class文件并不会只通过编译产生,自己也可以编写,这样可能破坏系统,所以虚拟机要进行验证,主要包括文件格式验证(基于二进制字节流验证)、元数据验证(是否有父类、是抽象类、子类字段与父类字段是否冲突)、字节码验证(保证语义的正确,比如int不会当成long、类型间的随意转换)、符号引用验证(发生在解析阶段符号引用转换为直接引用的过程,比如是否能根据类名查找类),准备阶段会将变量初始化为零(public static int value = 123;)实际上会将其置为0,
解析:将符号引用转换为直接引用
初始化阶段:执行类构造器<clinit>()方法的过程,<clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块的语句合并而成的,虚拟机会保证在子类的<clinit>方法执行之前,父类的<clinit>()方法已经执行完毕
5.类加载器与双亲委托模型
对于任意一个类,都需要由类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间,两个类只有同一类加载器加载的前提下才有意义,否则必不相等。
import java.io.IOException;
import java.io.InputStream;
public class ClassLoaderTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String className = name.substring(name.lastIndexOf(".") + 1) + ".class";
//返回读取指定资源的输入流
InputStream is = getClass().getResourceAsStream(className);
if (is == null) return super.loadClass(name);
byte[] b = new byte[is.available()];
is.read(b);
//将一个byte数组转换为Class类的实例
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object object1 = myLoader.loadClass("ClassLoaderTest").newInstance();
Object object2 = Class.forName("ClassLoaderTest").newInstance();
System.out.println(object1.getClass());
System.out.println(object1 instanceof ClassLoaderTest);
System.out.println(object1.getClass().getClassLoader().toString());
System.out.println("==============================================");
System.out.println(object2.getClass());
System.out.println(object2 instanceof ClassLoaderTest);
System.out.println(object2.getClass().getClassLoader().toString());
}
}
class ClassLoaderTest
false
ClassLoaderTest$1@7852e922
==============================================
class ClassLoaderTest
true
sun.misc.Launcher$AppClassLoader@73d16e93
更多详细可参见
http://m.blog.csdn.net/itermeng/article/details/75669628与http://m.blog.csdn.net/tyx001174/article/details/77844951等
总结:类的双亲委托模型会层级调用父类的类加载Application ClassLoader到Extension ClassLoader一直到Bootstrap ClassLoader,父类路径下没有该类调用则再子类加载器,总之是先父后子,破坏双亲委托模型主要是自定义实现类加载器,主要是重写loadClass方法和findClass方法,当自己定义一个第三方类时,一般只重写findClass时,不过只重写findClass时默认仍会调用第三方类加载器加载,为了避免这一现象,可以重写loadClass方法,因为不重写该方法始终会调用parent加载,当然可以下在自定义的类加载器中的构造其中将parent设为Extension或者Bootstrap或者为空这样更改父加载器绕过第三方类加载器,但是类加载器间的父子关系一般不会以继承的关系来实现,而是使用组合的关系来复用父加载器的代码
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
/**
* Returns a class loaded by the bootstrap class loader;
* or return null if not found.
*/
private Class<?> findBootstrapClassOrNull(String name)
{
if (!checkName(name)) return null;
return findBootstrapClass(name);
}
// return null if not found
private native Class<?> findBootstrapClass(String name);
private boolean checkName(String name) {
if ((name == null) || (name.length() == 0))
return true;
if ((name.indexOf('/') != -1)
|| (!VM.allowArraySyntax() && (name.charAt(0) == '[')))
return false;
return true;
}