Java 虚拟机面试题之一

Java 虚拟机

JVM 体系结构

JVM 内存结构主要有三大块:栈、堆内存、方法区。堆内存是 JVM 中最大的一块,由新生代和老年代组成,不包括永久代(方法区);而新生代内存又被分成 Eden 空间、From Survivor 空间、To Survivor 空间,默认情况下新生代按照 8:1:1 的比例来分配。方法区存储类信息、静态变量、常量、常量池等数据,是线程共享的区域,为了与 Java 堆区分,方法区还有一个别名 Non-Heap (非堆)。栈又分为 Java 虚拟机栈和本地方法栈,主要用于方法的执行。

java-jvm-architecture

JVM 垃圾收集机制

GC 发生在堆中,GC 的类型如下:

  • 次数上频繁收集新生代(Minor GC)
  • 次数上少收集老年代(Full GC)
  • 基本不动方法区

GC 算法

  • 如何确定一个对象是否会被回收

    • 引用计数算法(Reference Counting)
    • 可达性分析算法(Reachability Analysis)
  • GC 算法

    • 复制算法(Copying)
    • 标记 - 清除算法(Mark-Sweep)
    • 标记 - 算法(Mark-Compact)
    • 分代收集算法

GC 算法对比

  • 复制算法

    • 复制算法执行的速度较快,典型的空间换时间
    • 当对象的存活率很高的时候,不断的复制操作会显得耗时
    • 复制算法很明显的缺点就是浪费内存空间,因为将内存分为两块,一次只能使用一块,这也意味着分的块越大,浪费的内存越多
  • 标记 - 清除算法

    • 首先是速度慢,因为” 标记 - 清除算法” 在标记阶段需要使用递归的方式从根结点出发,不断寻找可达的对象;而在清除阶段又需要遍历堆内存中的所有对象,查看其是否被标记,然后清除;并且其实在程序进行 GC 的时候,JVM 中所有的 Java 程序都要进行暂停,俗称 Stop-The-World,后面会提到。
    • 其次是其最大的缺点,使用这种算法进行清理而得的堆内存的空闲空间一般是不连续的,由于对象实例在堆内存中是随机存储的,所以在清理之后,会产生许多的内存碎片,如果这个时候来了一个很大的对象实例,尽管显示内存还足够,但是已经存不下这个大对象了,内存碎片太多会导致当程序需要为较大对象分配内存时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。再者,这种零散的碎片对于数组的分配也不是很方便。
  • 标记 - 整理算法

    • 首先这种算法克服了” 标记 - 清除算法” 中会产生内存碎片的缺点,也解决了复制算法中内存减半使用的不足
    • 而其缺点则是速度也不是很快,不仅要遍历标记所有可达结点,还要一个个可达存活对象的地址,所以导致其效率不是很高