java 类加载器与程序加载范例分析

java 类加载器与程序加载实例分析
java 类加载器 可以分为

根加载器

扩展加载器

应用加载器

用户自定义加载器(继承自 java.lang.ClassLoder)

查看一个类是被何种加载器 可以使用以下代码

  public class ClassLoder {

	public static void main(String[] args) {
		System.out.println(ClassLoder.class.getClassLoader());
	}
}
 


输出的结果为

sun.misc.Launcher$AppClassLoader@19821f

可以看出  这个类 是被应用加载器加载。


判断一个类是否会被加载,可以根据 6个条件。

首次主动使用的情形:
创建某个类的新实例时--new、反射、克隆或反序列化;
调用某个类的静态方法时;
使用某个类或接口的静态字段或对该字段赋值时(final字段除外);
调用Java的某些反射方法时
初始化某个类的子类时
在虚拟机启动时某个含有main()方法的那个启动类。
除了以上几种情形以外,所有其它使用JAVA类型的方式都是被动使用的,他们不会导致类的初始化。

看一个例子

public class LoderDemo {

	public static void main(String[] args) {
		System.out.println(GG.a);
	}
}

class GG{
	public static final int a = 10;
	static{
		System.out.println("GG...");
	}
}



这短代码会输出多少呢


答案是 10
很明显  final 修饰的常量引用并不会引起类的初始化,我们再看下面一段代码。


public class LoderDemo {

	public static void main(String[] args) {
		System.out.println(GG.a);
	}
}

class GG{
	public static  final int b = 12;
	public static final int a = 1+b;
	static{
		System.out.println("GG...");
	}
}


 会输出什么呢 

答案是  13
虽然a 是个表达式  但是,表达式里面的量都是常量,会被jvm 提前折叠
再看下面这段代码


 public class LoderDemo {

	public static void main(String[] args) {
		System.out.println(GG.a);
	}
}

class GG{
	public static   int b = 12;
	public static final int a = 1+b;
	static{
		System.out.println("GG...");
	}
}



  这次的结果却是 
 GG...
 13

 虽然a 是个被final 修饰的,但是表达式含有 b一个不确定的量,所以jvm 会初始化整个类。而且静态代码块优先被执行。
 我们再看 下面一短代码


 class TestF{
	public static TestF test = new TestF();
	public static int count1 ;
	public static int count2 = 0;

	private TestF(){
		count1++;
		count2++;
	}
	public static TestF getInstances(){
		return test;
	}
}
public class SingleTownTest {
	public static void main(String[] args) {
		TestF f = TestF.getInstances();
		System.out.println("count1="+f.count1);
		System.out.println("count2="+f.count2);
	}
}



输出的结果是什么呢

 count1=1
 count2=0


根据类的加载和初始化机制,直接在启动类里面调用其他类的静态方法,会导致初始化整个类。

根据代码执行的顺序。
TestF test 将会被赋给一个null 值,count1 count2 在第一轮赋值中都被初始化为0
new TestF(); 会调用构造函数去操作 类的两个静态成员,这个时候
count = 1 ,count2 = 1,
再往下 执行
count1 没有被再次赋值,但是count2 被赋值 为 0.
所以输出会是 1,0

我们再看下面一段代码

 class TestF{
	public static int count1 ;
	public static int count2 = 0;
	public static TestF test = new TestF();

	private TestF(){
		count1++;
		count2++;
	}
	public static TestF getInstances(){
		return test;
	}
}
public class SingleTownTest {
	public static void main(String[] args) {
		TestF f = TestF.getInstances();
		System.out.println("count1="+f.count1);
		System.out.println("count2="+f.count2);
	}
}


  你会发现仅仅是将 
public static TestF test = new TestF();
 这句代码 移动了,但输出的结果却不同了。按照上面的分析,再来看一下。

 调用 静态方法 初始化了 TestF 这个类
 
 根据执行顺序,将 count1 count2 test 分别赋值为 0,0,null。
 接着 将用户手动赋值的 0 赋值给 count2,
 new TestF()  调用构造函数 并且赋值给 test
 构造函数开始给 静态成员赋值,
 count1  count2 分别自增1,值为1
 输出。
 这就是整个过程。
 好好分析类加载器,其中的强大会直接影响到你的程序效率和正确性。