面试官:第一轮面试开始,先问你几个Java核心知识的问题。在Java中,什么是多态?它有什么作用?
王铁牛:多态就是同一个行为具有多个不同表现形式。它的作用嘛,能提高程序的可扩展性和可维护性,比如一个父类引用可以指向不同的子类对象。
面试官:回答得不错。那说说final关键字在Java中有哪些应用场景?
王铁牛:final可以修饰类,被修饰的类不能被继承;还能修饰方法,方法不能被重写;也能修饰变量,变量一旦赋值就不能再重新赋值。
面试官:很好。再问一个,接口和抽象类有什么区别?
王铁牛:接口里全是抽象方法,不能有具体实现,而且可以多实现;抽象类可以有抽象方法也可以有具体方法,只能单继承。
面试官:第一轮面试结束,整体表现还不错。接下来进入第二轮,关于JUC相关问题。什么是CAS操作?它有什么优缺点?
王铁牛:CAS就是比较并交换,优点是效率高,缺点是会出现ABA问题。
面试官:如何解决ABA问题?
王铁牛:可以加版本号,每次比较的时候不仅比较值,还比较版本号。
面试官:那说说AQS框架,它的原理是什么?
王铁牛:AQS就是一个同步器框架,通过一个FIFO队列来管理等待的线程,利用CLH锁实现。
面试官:第二轮面试也结束了,表现还可以。现在进入第三轮,关于JVM的问题。JVM的内存结构分为哪几个部分?
王铁牛:有堆、栈、方法区、程序计数器、本地方法栈。
面试官:那垃圾回收主要针对哪些区域?
王铁牛:主要针对堆和方法区。
面试官:如何判断一个对象是否可以被回收?
王铁牛:可以通过引用计数法和可达性分析算法。
面试官:三轮面试结束了。你整体对一些基础问题回答得还可以,但对于一些稍微复杂点的问题,回答得不是特别清晰。回家等通知吧。
答案:
- 多态:
- 多态就是同一个行为具有多个不同表现形式。在Java中,多态的实现主要依赖于继承和方法重写。比如有一个父类Animal,它有一个方法叫move(),然后有子类Dog和Cat继承自Animal,并重写了move()方法。当我们创建一个Animal类型的引用,然后让它指向Dog或Cat对象时,调用move()方法,就会根据实际指向的对象类型来执行不同的行为,这就是多态。
- 多态的作用:
- 提高程序的可扩展性:当需要增加新的子类时,只需要继承父类并重写相应方法,而不需要修改大量使用父类的代码。
- 提高程序的可维护性:代码结构更加清晰,不同子类的行为可以通过重写父类方法来实现,便于管理和维护。
- final关键字的应用场景:
- 修饰类:被final修饰的类不能被继承。例如String类就是被final修饰的,所以不能有类继承String类。
- 修饰方法:被final修饰的方法不能被重写。比如在一些工具类中,有些方法不希望子类去修改它的实现,就可以用final修饰。
- 修饰变量:被final修饰的变量一旦赋值就不能再重新赋值。如果是基本数据类型,值不能改变;如果是引用类型,引用不能再指向其他对象,但对象内部的属性值是可以改变的。例如final int a = 10; a不能再被赋值;而final List list = new ArrayList<>(); list.add("new value"); 是可以的,因为只是改变了list中元素的值,而list本身的引用没有变。
- 接口和抽象类的区别:
- 接口:
- 接口里全是抽象方法,不能有具体实现。
- 一个类可以实现多个接口。例如class MyClass implements Interface1, Interface2 {... }
- 主要用于实现多行为,比如一个类既可以实现Serializable接口用于对象序列化,又可以实现Comparable接口用于对象比较。
- 抽象类:
- 抽象类可以有抽象方法也可以有具体方法。抽象方法没有实现,具体方法有具体的代码实现。
- 一个类只能继承一个抽象类。例如class MySubClass extends MyAbstractClass {... }
- 抽象类可以作为一些子类的公共父类,提供一些通用的属性和方法,子类可以在此基础上进行扩展。
- 接口:
- CAS操作:
- CAS即比较并交换(Compare And Swap),它是一种无锁的原子操作。在Java中,它通过Unsafe类来实现。
- 以AtomicInteger为例,它有一个getAndIncrement()方法,底层就是通过CAS操作实现的。它会先比较当前值和预期值,如果相等,就将值更新为新值,并返回旧值;如果不相等,就重新获取当前值再进行比较,直到成功更新或者达到一定的重试次数。
- 优点:
- 效率高:因为是无锁操作,避免了锁竞争带来的开销。
- 缺点:
- 会出现ABA问题:比如一个值原来是A,被改成了B,然后又被改回了A,在CAS比较时可能会认为值没有变化,但实际上已经被修改过了。
- 解决ABA问题:
- 可以加版本号。比如在AtomicStampedReference类中,它不仅可以比较值,还可以比较版本号。每次对值进行修改时,版本号也会增加。当进行CAS操作时,会同时比较值和版本号,如果值相同但版本号不同,也不会认为是相等的,从而解决了ABA问题。
- AQS框架:
- AQS(AbstractQueuedSynchronizer)是一个同步器框架,它是构建锁和同步器的基础框架。
- 它通过一个FIFO队列来管理等待的线程。当一个线程尝试获取锁失败时,它会被加入到这个队列中。
- 利用CLH锁实现(Craig, Landin, and Hagersten locks),它是一种基于链表的自旋锁。每个节点代表一个等待线程,当一个节点的前驱节点释放锁时,该节点会尝试获取锁。
- JVM的内存结构:
- 堆:是JVM中最大的一块内存区域,用于存储对象实例。所有的对象实例以及数组都在堆上分配内存。它是垃圾回收的主要区域。
- 栈:每个线程都有自己独立的栈,用于存储局部变量、方法调用等。栈中的数据是线程私有的,并且随着方法的调用和返回而自动入栈和出栈。
- 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区在JDK 8及以后被称为元空间(MetaSpace),它不再在堆中分配,而是使用本地内存。
- 程序计数器:是一块较小的内存区域,它记录了当前线程执行的字节码指令的地址。每个线程都有自己独立的程序计数器,它是线程私有的。
- 本地方法栈:与虚拟机栈类似,用于执行本地方法。它也是线程私有的。
- 垃圾回收主要针对区域:
- 主要针对堆和方法区。在堆中,对象在使用完毕后,如果没有被引用指向,就会成为垃圾对象,等待垃圾回收器进行回收。在方法区中,一些不再使用的类信息等也会被垃圾回收。例如,如果一个类被加载到方法区后,所有对它的引用都被释放,那么这个类所占用的方法区空间就可能被回收。
- 判断一个对象是否可以被回收:
- 引用计数法:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1。当计数器值为0时,就认为这个对象可以被回收。但这种方法不能解决循环引用的问题,比如对象A引用对象B,对象B又引用对象A,此时它们的引用计数器都不为0,但实际上它们都不再被其他有效代码所使用,无法被回收。
- 可达性分析算法:从一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时(即从GC Roots到这个对象不可达),则证明此对象是不可用的,可以被回收。GC Roots包括:虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(即一般说的Native方法)引用的对象等。