1)使用PC寄存器存储字节码指令地址有什么用呢?为什么使用PC寄存器记录当前线程的执行地址呢?

因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。

JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。

2)PC寄存器为什么被设定为私有的?

我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无差呢?为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。

3)为什么java的指令是根据栈来设计的,为什么不是基于寄存器的?

不同平台CPU架构不同,所以不能设计为基于寄存器的

4)基于栈的设计有什么优点?

  能够跨平台,指令集小,编译器容易实现,效率低,实现同样的功能需要更多的指令

5)栈是运行时的单位,而堆是存储的单位

6)开发中可能遇到哪些异常?

  Java虚拟机规范允许java栈的大小是动态的或者是固定不变的

  如果采用固定大小的java虚拟机栈,那每一个线程的java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈

允许的最大容量,Java虚拟机将会抛出一个StackOverflowError

  如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那java

虚拟机将会抛出一个OutOfMemoryError 异常。

7)如何设置栈内存的大小

我们可以使用参数 -Xss选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度 

8)java中有两种方法返回函数的方式,一种是正常的函数返回,使用return指令;另一种是抛出异常。不管使用哪种方式,都会导致栈帧被弹出

9)栈帧的内部结构

  局部变量表、操作数栈、动态链接、方法返回地址、一些附加信息

10)局部变量表是否存在数据安全问题

  局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题

11)关于Slot的理解

  在局部变量表中,最基本的存储单元是Slot

  32位以内的占用一个slot,64位的占用两个

12)与类变量初始化不同的是,局部变量表不存在系统初始化的过程,这意味着一旦定义了局部变量则必须认为的初始化,否则无法使用

13)操作数栈是jvm执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的

14)什么是栈顶缓存技术

15)由于操作数是存储在内存中的,因此频繁的执行内存读写操作必然会影响执行速度。为了解决这个问题,HotSpot JVM的设计者们提出了栈顶缓存技术,

将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率

16)虚方法和非虚方法

  如果方法在编译期间就确定了具体的调用版本,这个版本在运行时是不可变的,这样的方法称为非虚方法

  静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法。其他方法称之为虚方法。

17)虚方法表

  在面向对象的编程中,会很频繁的使用到动态分派,如果在每次动态分派的过程中都要重新在类的方法元数据中搜索合适的目标的话就可能影响到执行效率。因此,为了提高性能,JVM采用在类的方法区建立一个虚方法表 (virtual method table)(非虚方法不会出现在表中)来实现。使用索引表来代替查找。

18)栈的相关面试题

  • 举例栈溢出的情况?(StackOverflowError)
    • 通过 -Xss设置栈的大小
  • 调整栈大小,就能保证不出现溢出么?
    • 不能保证不溢出
  • 分配的栈内存越大越好么?
    • 不是,一定时间内降低了OOM概率,但是会挤占其它的线程空间,因为整个空间是有限的。
  • 垃圾回收是否涉及到虚拟机栈?
    • 不会
  • 方法中定义的局部变量是否线程安全?
    • 具体问题具体分析。如果对象是在内部产生,并在内部消亡,没有返回到外部,那么它就是线程安全的,反之则是线程不安全的。