三连问下来,恐怕自己已经被劝退了,有的同学肯定学过jvm基本原理的,但是被面试官一问,就一脸懵了,主要原因是没有掌握到精髓,不知道其中的原理,光靠死记硬背是不行的,面试官都看在眼里的。
本文将带着大家一起分析面试题,来梳理下其中主要的知识点,相信大家在看完之后,一定会有所收获,可以彻底告别面试官的连连追问了。
现在我们进入正题:
面试题1
面试官直接抛过来第一道面试题,看看大家能猜出结果不
package org.apache.dubbo.demo.provider;
public class JVMClass extends BaseCodeBlock {
{
System.out.println("子类的普通代码块");
}
public JVMClass() {
System.out.println("子类的构造方法");
}
@Override
public void msg() {
System.out.println("子类的普通方法");
}
public static void msg2() {
System.out.println("子类的静态方法");
}
static {
System.out.println("子类的静态代码块");
}
public static void main(String[] args) {
BaseCodeBlock bcb = new JVMClass();
bcb.msg();
}
Other o = new Other();
}
class BaseCodeBlock {
public BaseCodeBlock() {
System.out.println("父类的构造方法");
}
public void msg() {
System.out.println("父类的普通方法");
}
public static void msg2() {
System.out.println("父类的静态方法");
}
static {
System.out.println("父类的静态代码块");
}
Other2 o2 = new Other2();
{
System.out.println("父类的普通代码块");
}
}
class Other {
Other() {
System.out.println("初始化子类的属性值");
}
}
class Other2 {
Other2() {
System.out.println("初始化父类的属性值");
}
}
大家可以把上面的代码拷贝到编辑器上面,执行下看看和自己预期的结果是否一致,这段代码基本上可以展示出了类加载和初始化顺序,给大家看下结果
可以看出如果有继承父类的话,会优先去初始化父类。遵循这样一个顺序
面试题2
面试官开始出第二题了,又抛来一段代码,细品:
package com.example.demo;
public class JVMClass2 {
public static void main(String[] args) {
Singleton1 s1 = Singleton1.getSingleton();
Singleton2 s2 = Singleton2.getSingleton();
System.out.println("s1:counter1 = "+ s1.counter1);
System.out.println("s1:counter2 = "+s1.counter2);
System.out.println("s2:counter1 = "+ s2.counter1);
System.out.println("s2:counter2 = "+s2.counter2);
}
}
class Singleton1{
private static Singleton1 singleton = new Singleton1();
public static int counter1;
public static int counter2 = 0;
public Singleton1(){
counter1++;
counter2++;
}
public static Singleton1 getSingleton(){
return singleton;
}
}
class Singleton2{
public static int counter1;
public static int counter2 = 0;
private static Singleton2 singleton = new Singleton2();
public Singleton2(){
counter1++;
counter2++;
}
public static Singleton2 getSingleton(){
return singleton;
}
}
大家可以开动脑经,结合上面的加载顺序,来分析分析这道题的答案。 我们直接看运行结果
是不是和内心预期的有点出入啊,我们按照上面的思路分析下,
到此,面试已经结束,我们来结合理论知识总结下jvm类加载和初始化顺序。
java类加载分为五个过程,如图:
类加载顺序
1
加载
这阶段主要是加载class文件到JVM中,class文件可以是来自本地,也可以是一段二进制流:jvm主要做了如下工作:
1)通过classloader 获取XXX.class文件,将其以二进制流的形式读入内存。
2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2
验证
这阶段主要是为了确保加载的class 文件能够符合JVM规范,特别是来自网络的二进制字节流,必须要经过校验,防止发生不可预知的错误,当然如果你确信你的class文件是经过多次测试没有任何问题的话,是可以使用参数关闭验证的,感兴趣的可以了解下。
这阶段主要进行了如下内容的验证:
(1)文件格式的验证
验证文件格式是否按照虚拟机的规范,也就是我们前面class文件结构中的内容,比如这是不是一个Class文件(看魔数,是否为CAFEBABE);
Java版本是否符合当前虚拟机的范围(Java可以向下兼容,但是不能处理大于当前版本的程序)等等。
(2)元数据验证
对Class文件中的元数据进行验证,是否存在不符合Java语义的元数据信息。
有的朋友可能会比较疑惑,什么是元数据呢?
一般情况下,一个文件中包括数据和元数据。数据指的是实际数据,而元数据(Metadata)是用来描述数据的数据。用过Java注解的朋友应该对元数据这种叫法并不陌生,对应的元注解,其实说的差不多都是一个意思。
举个例子:比如说我们定义了一个变量 int a = 1;可以理解成数据就是1,而元数据就是描述有一个字符串变量“a”,这个“a”的类型是int型的,它的值也是一个int型的1,这就是描述数据的数据,就是元数据。
(3)字节码的验证
通过数据流和控制流分析,来确定程序语义是否合法。
以数据来说,要保证类型转换是有效的;对于控制流程的代码,不能让指令跳转到其它方法的字节码指令上等……
(4)符号引用的验证
为了保证解析动作能正常完成,还需在虚拟机将符号引用转成直接引用的时候,判断其它要引用的类是否符合规定。比如,要引用的类是否能够被找到;引用的属性在对应类中是否存在,权限是否符合要求(private的是不能访问的)等。
3
准备
准备阶段是用来为静态变量在方法区分配内存,并设置零值,上面有提到过,可以串联起来理解。
4
解析
将虚拟机常量池内的符号引用解析为直接引用,指到内存中的具体地址。
5
初始化
这一步才真正开始执行我们的代码,进行变量的赋值和相应初始化操作,如果有继承关系,则优先初始化父类。
6
使用
就是使用。。。
7
卸载
jvm会将对象标记为null,等待垃圾回收器进行GC
什么时候会触发类的初始化?
了解完加载的五大步之后,我们再看下最后一个重要的知识点,就是我们使用类的时候,什么时候会触发类的初始化呢,分为以下五种情况:
今天分享的面试内容到此结束,我们下一期再见吧