JVM 面试题【中级】

这个章节记录一些 JVM 面试中,比较深刻的点java


static 静态代码块和 static 变量谁先赋值

这算是一个和很差回答的问题了,干巴巴说你不必定明白我说的是啥,仍是先看看代码面试

public class Max {

    static {
        staticIntValue = 300;
    }
    
    public static int staticIntValue = 100;

    public final int finalIntValue = 3;

    public int intValue = 1;

    public static void main(String[] args) {
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
复制代码
  • 【问:】
    问题就是 staticIntValue 到底等于多少,
  • 【答案:】
    staticIntValue=100

我先把答案说出来,是为了你们能想一想,这个问题在在好几个培训机构的公开课上都看到过,可是很难有讲到位的。这个世界上任务事情都有其根本的道理,有的看着天马行空,其实根本是咱们没有这快的知识体系,因此无从下手缓存

那么这个问题如何分析呢?上面咱们写的是 java 代码,java 代码会编译成字节码,怎么赋值,赋值的前后顺序其实都是在编译时就决定好了的,咱们反编译下字节码就清除了安全

涉及的知识点是:static 的属性申请内存空间是在类加载的验证机阶段,这个阶段会给 static 属性一个默认值,而后把 static 属性的赋值和 static 代码块结合生成一个类的初始化方法 。 中的赋值顺序和代码书写顺序同样,谁写在最后,static 的值就是哪一个优化

先看上文代码,static 代码快在前spa

public class Max {

    static {
        staticIntValue = 300;
    }
    
    public static int staticIntValue = 100;
}
复制代码

反编译的字节码线程

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: sipush        300
         3: putstatic     #9                  // Field staticIntValue:I
         6: bipush        100
         8: putstatic     #9                  // Field staticIntValue:I
        11: return
      LineNumberTable:
        line 11: 0
        line 14: 6
}
复制代码

类加载准备阶段先staticIntValue开辟内存地址,赋默认值0,而后先赋值300,在赋值100设计

先换个顺序看看,static 代码块在后面code

public class Max {

    public static int staticIntValue = 100;
    
    static {
        staticIntValue = 300;
    }
}
复制代码

反编译的字节码cdn

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        100
         2: putstatic     #9                  // Field staticIntValue:I
         5: sipush        300
         8: putstatic     #9                  // Field staticIntValue:I
        11: return
      LineNumberTable:
        line 10: 0
        line 13: 5
        line 14: 11
}
复制代码

类加载准备阶段先staticIntValue开辟内存地址,赋默认值0,而后先赋值100,在赋值300

其实很简单,只要咱们找对了问题对应的知识体系,一切问题其实都没有那么难


static 储存在哪

这个问题其实有陷阱啊,咱们通常都会这么写:String name = new String("AA");,这道题能够问,static 变量=号左边和右边分别储存在哪,我想不少人其实说不清楚

看代码:

public class Max {

    public static byte[] values = new byte[1024*1024*60];

    public static void main(String[] args) {
        System.out.println("values: "+values);
    }
    
}

复制代码

咱们先看=号右边new 的部分: 方法很简单,只要把堆栈信息打印出来就好了,代码里咱们搞了一个 60M 的变量出来

// 堆内存
Heap

 // 年轻代
 PSYoungGen      total 38400K, used 4663K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 14% used [0x0000000795580000,0x0000000795a0dc88,0x0000000797600000)
  from space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
  to   space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
  
 // 老年代 
 ParOldGen       total 87552K, used 61440K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 70% used [0x0000000740000000,0x0000000743c00010,0x0000000745580000)
  
 // 元空间 
 Metaspace       used 3387K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 388K, committed 512K, reserved 1048576K
复制代码

很明显,这60M内存开辟在了老年代里面,对于单个对象体积大于年轻代的通常直接放到老年代里,可是这里60M显然还够档次,我这能够是 Mac Pro 跑的代码


双亲委派机制

这里写双亲委派机制是为了再一次强调该点,面试问的太多啦

咱们先回国头来再看一遍 ClassLoader 的设计:

public abstract class ClassLoader{
    
    private final ClassLoader parent;
    
    protected Class<?> loadClass(String name, boolean resolve){
        ......
    }
}
复制代码

ClassLoader 对象设计有 parent 父加载器,你们看着像不像链表。链表的next指向下一个,ClassLoader parent 这里上一层级

类加载加载机制中默认不会直接由本身加载,会先用本身的父加载器 parent 去加载,父加载器加载不到再本身加载

JVM 3级类加载器,每一级都有本身能加载类的范围,类加载器一级一级提交给父加载器去加载,每一级类加载在碰到本身能加载的类时,没加载过的会去加载,加载过的会返回已经加载的class对象给下一级

看看 ClassLoader.loadClass() 方法代码:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                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.
                    c = findClass(name);
                }
            }
            return c;
    }
复制代码

这个就叫作:双亲委派机制,为啥叫双亲,由于系统类加载器上面就2级类加载器

java 核心类库有访问权限限制,类加载器在发现容许加载范围以外的类加载的加载请求以后,会直接报错的。这个判断通常都是用包名来判断的,好比你本身搞了一个 String 类,包名仍是 java.lang,那引导类加载器在处理这个加载请求时会直接报错,这种报错机制就叫作沙箱安全机制

沙箱安全机制有个典型例子:360沙箱隔离,好比U盘程序只在360认为隔离出来的沙箱内运行,以保护沙箱外的系统不受可能的病毒污染

双亲委派机制的目的是为了保证安全,防止核心 API 被篡改


栈内存在内存的哪块

我想这绝对是能够把你问懵逼的一道问题 o( ̄▽ ̄)d

其实这涉及到 JVM 一个机制:栈顶缓存 技术

因为操做数是存储在内存中的,所以会频繁的执行内存读/写操做,必然会影响执行速度。纬二路解决这个问题,Hotspot 虚拟机的设计者们提出了栈顶缓存技术,讲栈顶元素也就是立刻要执行的栈帧(方法)缓存在cpu寄存器中,以此下降对内存的读写次数,提高执行引擎的效率

啦啦,就是这么简单,栈内存和堆内存同样不用的时候仍是保存在主内存也就是屋里内存中,只有在线程抢到cpu'时间片的时候,才会把栈内存栈顶的栈帧加载进cpu缓存中,至因而:L1->L2->L3 就不得而知了,估计应该是L1的面更大。完事根据java锁升级机制还会对这块有优化,好比很快会再次运行的线程的栈顶栈帧可能会提早加载进cpu缓存,此次估计就是L3了

相关文章
相关标签/搜索