面试题答案
一键面试Java内存区域主要部分
- 程序计数器(Program Counter Register):
- 是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
- 每个线程都有独立的程序计数器,此区域是线程私有的。
- Java虚拟机栈(Java Virtual Machine Stacks):
- 也是线程私有的,生命周期与线程相同。
- 描述的是Java方法执行的内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
- 本地方法栈(Native Method Stack):
- 与Java虚拟机栈作用类似,不过它是为虚拟机使用到的本地(Native)方法服务的。
- 本地方法一般是用C或C++实现的,同样也是线程私有的。
- 堆(Heap):
- 是Java虚拟机所管理的内存中最大的一块,被所有线程共享。
- 几乎所有的对象实例以及数组都在这里分配内存。Java堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap) 。
- 根据分代收集理论,Java堆还可以细分为:新生代(Young Generation)和老年代(Old Generation),新生代又分为:伊甸园区(Eden Space)和幸存者区(Survivor Space,其中又有From Survivor和To Survivor两块区域)。
- 方法区(Method Area):
- 也是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
- 在JDK 8及之后,方法区被元空间(Meta Space)替代,元空间使用本地内存。
垃圾回收机制在各区域工作情况
- 程序计数器、Java虚拟机栈、本地方法栈:
- 这三个区域随着线程的创建而创建,随着线程的结束而销毁,它们的内存分配和回收都具有确定性,因此不需要垃圾回收机制来管理。
- 堆:
- 标记阶段:垃圾回收器首先会从根对象(如栈中引用的对象、静态变量引用的对象等)开始,通过可达性分析算法来标记出所有可达的对象,剩下的就是不可达对象,这些不可达对象就是可以被回收的对象。
- 清除阶段:标记完成后,垃圾回收器会回收被标记为不可达的对象所占用的内存空间。
- 堆内存的垃圾回收策略:
- 新生代垃圾回收策略:
- 复制算法:新生代中的对象大多“朝生夕灭”,存活率较低。当伊甸园区满了触发Minor GC(新生代垃圾回收)时,会把伊甸园区和From Survivor中存活的对象复制到To Survivor区,然后清空伊甸园区和From Survivor区。下次GC时,To Survivor变为From Survivor,原来的From Survivor变为To Survivor,如此循环。当对象在Survivor区熬过一定次数(默认15次)的GC后,会被晋升到老年代。
- 老年代垃圾回收策略:
- 标记 - 清除算法:老年代对象存活率高,复制成本大,所以老年代一般采用标记 - 清除算法。先标记出老年代中所有不可达对象,然后回收这些对象占用的内存空间,标记 - 清除算法会产生内存碎片。
- 标记 - 整理算法:为了解决标记 - 清除算法产生的内存碎片问题,有些垃圾回收器(如CMS在Full GC时采用的并发标记 - 清除算法,G1采用的标记 - 整理算法)会采用标记 - 整理算法。该算法在标记完不可达对象后,会将存活对象向一端移动,然后直接清理掉端边界以外的内存,这样就不会产生内存碎片。
- 新生代垃圾回收策略:
- 方法区:
- 方法区的垃圾回收主要回收两部分内容:废弃的常量和不再使用的类型。
- 废弃常量:如字符串常量池中存在的字符串对象,如果没有任何地方引用该字符串,就可以被回收。
- 不再使用的类型:判定条件比较复杂,需要满足该类型所有的实例都已经被回收,加载该类型的类加载器已经被回收,该类型对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类型的方法等条件,该类型才能被回收。