【面试题】java类加载机制探索

【面试题】java类加载机制探索

参考文章

参考文章二

面试题一:

 
  1. class SingleTon {

  2. private static SingleTon singleTon = new SingleTon();

  3. public static int count1;

  4. public static int count2;

  5.  
  6. private SingleTon() {

  7. count1++;

  8. count2++;

  9. }

  10.  
  11. public static SingleTon getInstance() {

  12. return singleTon;

  13. }

  14. }

  15.  
  16. public class Test {

  17. public static void main(String[] args) {

  18. SingleTon singleTon = SingleTon.getInstance();

  19. System.out.println("count1=" + singleTon.count1);

  20. System.out.println("count2=" + singleTon.count2);

  21. }

  22. }

经常看到这么一句话:这是一个单例模式,通过classLoader机制避免多线程同步的问题。

先看看控制台打印结果:

 

 
  1. count1=1

  2. count2=1

似乎是一个显而易见的答案。OK,问题刚刚开始。

问题一、如何通过classLoader机制避免多线程同步的问题?

答:多个线程同时调用getInstance()方法时,就本例来说,如果不存在SingleTon实例对象,则会触发类的初始化。已经存在类初始化,则直接会去调用。

 

 
  1. jvm有严格的规定(五种情况):

  2.  
  3. 1.遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,假如类还没进行初始化,

  4. 则马上对其进行初始化工作。

  5. 其实就是3种情况:用new实例化一个类时、读取或者设置类的静态字段时(不包括被final修饰的静态字段,

  6. 因为他们已经被塞进常量池了)、以及执行静态方法的时候。

  7.  
  8. 2.使用java.lang.reflect.*的方法对类进行反射调用的时候,

  9. 如果类还没有进行过初始化,马上对其进行。

  10.  
  11. 3.初始化一个类的时候,如果他的父亲还没有被初始化,则先去初始化其父亲。

  12.  
  13. 4.当jvm启动时,用户需要指定一个要执行的主类(包含static void main(String[] args)的那个类),

  14. 则jvm会先去初始化这个类。

  15.  
  16. 5.用Class.forName(String className);来加载类的时候,也会执行初始化动作。

  17. 注意:ClassLoader的loadClass(String className);方法只会加载并编译某类,并不会对其执行初始化。

  18.  

面试题二:

 
  1. class SingleTon {

  2. private static SingleTon singleTon = new SingleTon();

  3. public static int count1;

  4. public static int count2 = 0;

  5.  
  6. private SingleTon() {

  7. count1++;

  8. count2++;

  9. }

  10.  
  11. public static SingleTon getInstance() {

  12. return singleTon;

  13. }

  14. }

  15.  
  16. public class Test {

  17. public static void main(String[] args) {

  18. SingleTon singleTon = SingleTon.getInstance();

  19. System.out.println("count1=" + singleTon.count1);

  20. System.out.println("count2=" + singleTon.count2);

  21. }

  22. }

有人问我是不是把面试题又抄了一遍?再仔细看看。

对,我只是对count2做了一个初始化。

 

public static int count2 = 0;

可能你认为的结果是这样的:

 
  1. count1=1

  2. count2=1

然而结果是:

 
  1. count1=1

  2. count2=0

不过是一个对count2的初始化,为什么结果居然变了?
答案:因为类初始化的顺序为,先加载 static,再构造器。

初始化也就是一个赋值的过程。

例如,本例:

 
  1. private static SingleTon singleTon = new SingleTon();

  2. public static int count1;

  3. public static int count2=0 ;

先第一行初始化,调用new SingleTon()对count1,count2赋值为count1=1,count2=1。

第二行没有赋值,所以count1=1不变。

第三行赋值count2为0。

故,答案为count1 =1,count2 = 0。好吧,全部都是套路。
 

面试题三、此处有个疑问:虽然能够答出上面这个答案的应该基本都不存在这个疑问。

先初始化new SingleTon()的时候count1 ,count2什么时候分配内存空间的?

先推断一番,肯定不是在初始化的时候才分配内存空间的,因为先执行构造方法的时候可以设置count值。这个时候肯定已经分配好了。OK,不推测了。

直接回到主题:

先摆理论:

 

 
  1. 类从被加载到虚拟机内存中开始,直到卸载出内存为止,

  2. 它的整个生命周期包括了: 加载、验证、准备、解析、初始化、使用和卸载 这7个阶段。

  3. 其中, 验证、准备和解析这三个部分统称为连接(linking) 。


知道理论后:

 

1)加载:在java堆中,创建class类对象。

2)准备:准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。比如本例,将分配count1,count2内存,并赋默认值0,0。(int类型默认值)

3)初始化:先static,再构造器。则按照static在方法内的顺序。

OK,基本上问题回答完了。也知道了count1,count2的分配内存在准备阶段,而初始化的时候早就有了这两个内存空间。

面试题四、父类,子类加载顺序。

最开始说初始化一直没提继承父类的类的初始化顺序,由于参杂在一起看起来就比较复杂了。

现在举个例子:

父类A:

 
  1. public class A{

  2. static{

  3. System.out.println("父类-静态代码块");

  4. }

  5. {

  6. System.out.println("父类-非静态代码块");

  7. }

  8. public A(){

  9. System.out.println("父类-构造方法");

  10. }

  11. }

子类B:

 
  1. public class B extends A{

  2. static{

  3. System.out.println("子类-静态代码块");

  4. }

  5. {

  6. System.out.println("子类-非静态代码块");

  7. }

  8. public B(){

  9. System.out.println("子类-构造方法");

  10. }

  11. }

测试一下:

 

 
  1. public class Test{

  2. public static void main(String[] args) {

  3. B b = new B();

  4. }

  5. }

看看效果:

 

 
  1. 父类-静态代码块

  2. 子类-静态代码块

  3. 父类-非静态代码块

  4. 父类-构造方法

  5. 子类-非静态代码块

  6. 子类-构造方法

看到这,就知道初始化子类会先初始化父类。顺序为 父类静态——》子类静态——》父类非静态代码块——》父类构造方法——》子类非静态代码块——》子类构造方法。

原因在这:

 

3.初始化一个类的时候,如果他的父亲还没有被初始化,则先去初始化其父亲。

 

最后留一个思考题:

 

 
  1. public class Main {

  2. public static void main(String[] args){

  3. System.out.println("我是main方法,我输出Super的类变量i:"+Sub.i);

  4. Sub sub = new Sub();

  5. }

  6. }

  7. class Super{

  8. {

  9. System.out.println("我是Super成员块");

  10. }

  11. public Super(){

  12. System.out.println("我是Super构造方法");

  13. }

  14. {

  15. int j = 123;

  16. System.out.println("我是Super成员块中的变量j:"+j);

  17. }

  18. static{

  19. System.out.println("我是Super静态块");

  20. i = 123;

  21. }

  22. protected static int i = 1;

  23. }

  24. class Sub extends Super{

  25. static{

  26. System.out.println("我是Sub静态块");

  27. }

  28. public Sub(){

  29. System.out.println("我是Sub构造方法");

  30. }

  31. {

  32. System.out.println("我是Sub成员块");

  33. }

  34. }





https://blog.csdn.net/haibo_bear/article/details/53841000

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值