常见面试题
JVM:JVM是一种可以执行字节码的虚拟机器。它是 Java 平台的代码执行组件。
类的生命周期
类的生命周期包括这几个部分,加载、连接、初始化、使用和卸载,其中前三部是类的加载的过程,如下图:
- 加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
- 连接,连接又包含三块内容:验证、准备、初始化。 1)验证,文件格式、元数据、字节码、符号引用验证; 2)准备,为类的静态变量分配内存,并将其初始化为默认值; 3)解析,把类中的符号引用转换为直接引用
- 初始化,为类的静态变量赋予正确的初始值
- 使用,new出对象程序中使用
- 卸载,执行垃圾回收
引用
4种引用的级别由高到低依次为强引用、软引用、弱引用和虚引用。
如果一个对象具有强引用,那垃圾回收器绝不会回收它。
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
虚引用的对象,它是如此脆弱以至于我们通过虚引用甚至无法获取到被引用的对象,它存在的唯一作用就是当它指向的对象被回收后,虚引用本身会被加入到引用队列中,用作记录它指向的对象已被回收。
什么是Java的内存结构、内存模型和对象模型
内存结构是和运行时数据区有关。
内存模型是指用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果,是虚拟机的规范之一。
对象模型是指java对象在内存中真正的存储(表示)形式有关。
运行时数据区
存放实例对象的堆。
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器生成代码的方法区。
为了支持多线程的程序计数器。
用于支持方法的运行的虚拟机栈和本地方法栈。
多态
面向对象的第三大特性之一。
是指同一个行为具有多个不同表现形式或形态的能力。
Java作为面向对象的语言,同样可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person。
JVM类加载过程
JVM 中类的装载是由类加载器,也就是ClassLoader,和它的子类来实现的,Java 中的类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。
由于 Java 的跨平台性, 经过编译的 Java 源程序并不是一个可执行程序, 而是一个或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接( 验证、 准备和解析)和初始化。
类的加载是指把类的.class 文件中的数据读入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应的 Class 对象。加载完成后, Class 对象还不完整, 所以此时的类还不可用。当类被加载后就进入连接阶段, 这一阶段包括验证、准备( 为静态变量分配内存并设置默认的初始值) 和解析( 将符号引用替换为直接引用) 三个步骤。
最后 JVM 对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句, 就依次执行这些初始化语句。
类加载器
从JDK 1.2开始, 类加载过程采取了双亲委派机制。更好的保证了 Java 平台的安全性,在该机制中,JVM 自带的 Bootstrap 是根加载器, 其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载。
JVM 不会向 Java 程序提供对 Bootstrap 的引用。
-
根加载器(BootStrap)一般用本地代码实现,负责加载 JVM 基础核心类库(rt.jar)。
-
扩展加载器( ExtClassLoader)从 java.ext.dirs 系统属性所指定的目录中加载类库,它的父加载器是 Bootstrap。
-
系统加载器( AppClassLoader) 又叫应用类加载器,其父类是 Extension。它是应用最广泛的类加载器。它从环境变量 classpath 或者系统属性 java.class.path 所指定的目录中记载类,是用户自定义加载器的默认父加载器。
-
用户自定义类加载器 ( java.lang.ClassLoader 的子类)父类是AppClassLoader。
如何识别垃圾
- 引用计数法:存在循环依赖的问题
- 根可达性算法:主流算法
垃圾回收算法
-
标记-清除算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
-
复制算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。缺点是浪费空间,优点是回收速度快,没碎片。
-
标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,完成碎片整理。
-
分代收集算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法,我觉得它更像是一种思想,而不是算法。
垃圾回收器
-
Serial 收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。
-
ParNew 收集器,ParNew 收集器其实就是 Serial 收集器的多线程版本。
-
Parallel 收集器,Parallel Scavenge 收集器类似 ParNew 收集器,Parallel 收集器更关注系统的吞吐量。
-
Parallel Old 收集器,Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多线程和“标记-整理”算法
-
CMS 收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
-
G1 收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征
Java对象创建过程
-
JVM 遇到一条新建对象的指令时首先去检查这个指令的参数是否能在常量池中定义到一个类的符号引用。然后加载这个类(类加载过程在后边讲)
-
为对象分配内存。一种办法“指针碰撞”、一种办法“空闲列表”,最终常用的办法“本地线程缓冲分配(TLAB)”
-
将除对象头外的对象内存空间初始化为 0
-
对对象头进行必要设置
为什么要用元空间替换永久代
-
字符串存在永久代中,容易出现性能问题和内存溢出。
-
类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
-
永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
-
移除永久代是为融合 HotSpot JVM 与 JRockit VM 而做出的努力,因为 JRockit 没有永久代,不需要配置永久代。
原文参考
JVM