[置顶] 【面试题】java类加载机制探索
2017-01-01 17:45
363 查看
【面试题】java类加载机制探索
参考文章
参考文章二
先看看控制台打印结果:
有人问我是不是把面试题又抄了一遍?再仔细看看。
对,我只是对count2做了一个初始化。
答案:因为类初始化的顺序为,先加载 static,再构造器。
初始化也就是一个赋值的过程。
例如,本例:
第二行没有赋值,所以count1=1不变。
第三行赋值count2为0。
故,答案为count1 =1,count2 = 0。好吧,全部都是套路。
先推断一番,肯定不是在初始化的时候才分配内存空间的,因为先执行构造方法的时候可以设置count值。这个时候肯定已经分配好了。OK,不推测了。
直接回到主题:
先摆理论:
知道理论后:
1)加载:在java堆中,创建class类对象。
2)准备:准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。比如本例,将分配count1,count2内存,并赋默认值0,0。(int类型默认值)
3)初始化:先static,再构造器。则按照static在方法内的顺序。
OK,基本上问题回答完了。也知道了count1,count2的分配内存在准备阶段,而初始化的时候早就有了这两个内存空间。
现在举个例子:
父类A:
原因在这:
最后留一个思考题:
参考文章
参考文章二
面试题一:
class SingleTon { private static SingleTon singleTon = new SingleTon(); public static int count1; public static int count2; 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); } }经常看到这么一句话:这是一个单例模式,通过classLoader机制避免多线程同步的问题。
先看看控制台打印结果:
count1=1 count2=1似乎是一个显而易见的答案。OK,问题刚刚开始。
问题一、如何通过classLoader机制避免多线程同步的问题?
答:多个线程同时调用getInstance()方法时,就本例来说,如果不存在SingleTon实例对象,则会触发类的初始化。已经存在类初始化,则直接会去调用。jvm有严格的规定(五种情况): 1.遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,假如类还没进行初始化, 则马上对其进行初始化工作。 其实就是3种情况:用new实例化一个类时、读取或者设置类的静态字段时(不包括被final修饰的静态字段, 因为他们已经被塞进常量池了)、以及执行静态方法的时候。 2.使用java.lang.reflect.*的方法对类进行反射调用的时候, 如果类还没有进行过初始化,马上对其进行。 3.初始化一个类的时候,如果他的父亲还没有被初始化,则先去初始化其父亲。 4.当jvm启动时,用户需要指定一个要执行的主类(包含static void main(String[] args)的那个类), 则jvm会先去初始化这个类。 5.用Class.forName(String className);来加载类的时候,也会执行初始化动作。 注意:ClassLoader的loadClass(String className);方法只会加载并编译某类,并不会对其执行初始化。
面试题二:
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); } }
有人问我是不是把面试题又抄了一遍?再仔细看看。
对,我只是对count2做了一个初始化。
public static int count2 = 0;可能你认为的结果是这样的:
count1=1 count2=1然而结果是:
count1=1 count2=0不过是一个对count2的初始化,为什么结果居然变了?
答案:因为类初始化的顺序为,先加载 static,再构造器。
初始化也就是一个赋值的过程。
例如,本例:
private static SingleTon singleTon = new SingleTon(); public static int count1; 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,不推测了。
直接回到主题:
先摆理论:
类从被加载到虚拟机内存中开始,直到卸载出内存为止, 它的整个生命周期包括了: 加载、验证、准备、解析、初始化、使用和卸载 这7个阶段。 其中, 验证、准备和解析这三个部分统称为连接(linking) 。
知道理论后:
1)加载:在java堆中,创建class类对象。
2)准备:准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。比如本例,将分配count1,count2内存,并赋默认值0,0。(int类型默认值)
3)初始化:先static,再构造器。则按照static在方法内的顺序。
OK,基本上问题回答完了。也知道了count1,count2的分配内存在准备阶段,而初始化的时候早就有了这两个内存空间。
面试题四、父类,子类加载顺序。
最开始说初始化一直没提继承父类的类的初始化顺序,由于参杂在一起看起来就比较复杂了。现在举个例子:
父类A:
public class A{ static{ System.out.println("父类-静态代码块"); } { System.out.println("父类-非静态代码块"); } public A(){ System.out.println("父类-构造方法"); } }子类B:
public class B extends A{ static{ System.out.println("子类-静态代码块"); } { System.out.println("子类-非静态代码块"); } public B(){ System.out.println("子类-构造方法"); } }测试一下:
public class Test{ public static void main(String[] args) { B b = new B(); } }看看效果:
父类-静态代码块 子类-静态代码块 父类-非静态代码块 父类-构造方法 子类-非静态代码块 子类-构造方法看到这,就知道初始化子类会先初始化父类。顺序为 父类静态——》子类静态——》父类非静态代码块——》父类构造方法——》子类非静态代码块——》子类构造方法。
原因在这:
3.初始化一个类的时候,如果他的父亲还没有被初始化,则先去初始化其父亲。
最后留一个思考题:
public class Main { public static void main(String[] args){ System.out.println("我是main方法,我输出Super的类变量i:"+Sub.i); Sub sub = new Sub(); } } class Super{ { System.out.println("我是Super成员块"); } public Super(){ System.out.println("我是Super构造方法"); } { int j = 123; System.out.println("我是Super成员块中的变量j:"+j); } static{ System.out.println("我是Super静态块"); i = 123; } protected static int i = 1; } class Sub extends Super{ static{ System.out.println("我是Sub静态块"); } public Sub(){ System.out.println("我是Sub构造方法"); } { System.out.println("我是Sub成员块"); } }
相关文章推荐
- 关于Java类加载双亲委派机制的思考(附一道面试题)
- 【Java面试题】之类加载:从面试题分析Java类加载机制
- 关于Java类加载双亲委派机制的思考(附一道面试题)
- 关于Java类加载双亲委派机制的思考(附一道面试题)
- 关于Java类加载父类委托机制 /双亲委派模型(附两道面试题)
- Java深入探索之道Java类加载机制的奥秘
- [置顶] 剑指Offer——知识点储备-故障检测、性能调优与Java类加载机制
- 关于Java类加载双亲委派机制的思考(附一道面试题)
- [置顶] Android热修复(一):底层替换、类加载原理总结 及 DexClassLoader类加载机制源码探索
- 关于Java类加载双亲委派机制的思考(附一道面试题)
- 关于Java类加载双亲委派机制的思考(附一道面试题)
- 关于Java类加载双亲委派机制的思考(附一道面试题)
- 关于Java类加载双亲委派机制的思考(附一道面试题)
- java类加载机制
- Java类加载机制
- Java类加载机制
- 【Java】ClassLoader源码全面解析java类加载机制
- java类加载机制
- 黑马程序员_JAVA类加载机制
- 从一道面试题来认识java类加载时机与过程