面试官:第一轮面试开始,首先问你一个关于Java核心知识的问题。在Java中,什么是多态?
王铁牛:多态就是同一个行为具有多个不同表现形式或形态。
面试官:回答得不错。那接口和抽象类有什么区别?
王铁牛:接口里全是抽象方法,不能有具体实现,而且一个类可以实现多个接口;抽象类可以有抽象方法也可以有具体方法,一个类只能继承一个抽象类。
面试官:很好。再问一个,简述一下Java中的异常处理机制。
王铁牛:就是try catch finally,try里放可能出现异常的代码,catch捕获异常并处理,finally不管有没有异常都会执行。
面试官:第一轮面试结束,表现还不错。接下来是第二轮面试,关于JUC的问题。请说一下什么是AQS?
王铁牛:嗯……AQS就是那个……那个啥,好像是抽象队列同步器吧。
面试官:回答得不太清晰。那说说CountDownLatch的作用。
王铁牛:这个……不太记得了。
面试官:再问你,CyclicBarrier和CountDownLatch有什么区别?
王铁牛:啊?这……我不太清楚。
面试官:第二轮面试结束,接下来进入第三轮面试,关于JVM的问题。JVM的内存区域分为哪几个部分?
王铁牛:嗯……有堆、栈、方法区……好像还有别的。
面试官:回答得很不完整。那什么是垃圾回收机制?
王铁牛:就是回收那些不再使用的内存呗。
面试官:最后一个问题,简述一下类加载机制。
王铁牛:这个……我不太会。
面试官:三轮面试结束了,整体表现不太理想。回去等通知吧。
答案:
- 多态:多态是同一个行为具有多个不同表现形式或形态。在Java中,多态主要体现在方法重写和方法重载上。方法重写是子类对父类中已有的方法进行重新实现,以满足不同的业务需求。方法重载是在同一个类中定义多个同名方法,但参数列表不同,通过参数的不同来区分调用哪个方法。多态的好处在于提高代码的可扩展性和可维护性,比如可以定义一个父类类型的引用,指向不同的子类对象,通过这个引用来调用不同子类的相同方法,实现不同的行为。
- 接口和抽象类的区别:
- 定义方式:接口使用interface关键字定义,里面全是抽象方法,不能有具体实现;抽象类使用abstract class定义,可以有抽象方法也可以有具体方法。
- 实现方式:一个类可以实现多个接口;一个类只能继承一个抽象类。
- 作用:接口主要用于实现多继承的功能,比如一个类可以同时实现多个接口来获取不同的行为;抽象类主要用于定义一些公共的属性和方法,让子类继承并扩展。
- Java异常处理机制:Java的异常处理机制主要通过try catch finally语句来实现。try块中放置可能会抛出异常的代码,如果try块中的代码抛出了异常,程序会立即跳转到对应的catch块中进行异常处理。catch块用于捕获并处理异常,可以根据不同的异常类型进行针对性的处理。finally块不管try块中的代码是否抛出异常都会执行,通常用于释放资源等操作。例如:
try {
// 可能抛出异常的代码
int result = 10 / 0;
} catch (ArithmeticException e) {
// 处理算术异常
System.out.println("发生了算术异常: " + e.getMessage());
} finally {
// 一定会执行的代码
System.out.println("finally块执行");
}
- AQS(抽象队列同步器):AQS是Java并发包中构建锁和同步器的基础框架。它使用一个FIFO队列来管理等待获取同步状态的线程。AQS通过一个int类型的变量来表示同步状态,子类可以通过继承AQS并重写相关方法来实现自己的同步逻辑。比如ReentrantLock就是基于AQS实现的,它通过AQS来管理锁的获取和释放,线程在获取锁时会尝试获取同步状态,如果获取失败则会被放入队列中等待。
- CountDownLatch的作用:CountDownLatch是一个同步辅助类,它允许一个或多个线程等待其他一组线程完成操作之后再继续执行。它通过一个计数器来实现,在构造CountDownLatch时传入一个初始计数值,每当一个线程调用countDown方法时,计数器的值就会减1,当计数器的值减到0时,所有等待在CountDownLatch上的线程都会被唤醒。例如,在一个多线程任务中,主线程需要等待其他几个子线程都完成任务后再进行汇总操作,就可以使用CountDownLatch。
CountDownLatch latch = new CountDownLatch(3);
Thread thread1 = new Thread(() -> {
// 子线程任务
latch.countDown();
});
Thread thread2 = new Thread(() -> {
// 子线程任务
latch.countDown();
});
Thread thread3 = new Thread(() -> {
// 子线程任务
latch.countDown();
});
thread1.start();
thread2.start();
thread3.start();
latch.await();
// 所有子线程都完成后执行这里的代码
- CyclicBarrier和CountDownLatch的区别:
- CountDownLatch:它是一个倒计数的同步器,计数器的值只能减不能加,当计数器的值减到0时,等待的线程被唤醒。它主要用于一个线程等待其他多个线程完成任务后再继续执行。
- CyclicBarrier:它是一个可循环使用的同步屏障,它允许一组线程互相等待,直到到达某个公共屏障点。当所有线程都到达屏障点时,屏障会被打开,所有线程可以继续执行。它可以通过reset方法重置屏障,使得可以再次使用。例如,在一个多线程计算任务中,多个线程分别计算一部分数据,当所有线程都计算完成后,再进行汇总操作,就可以使用CyclicBarrier。
CyclicBarrier barrier = new CyclicBarrier(3);
Thread thread1 = new Thread(() -> {
try {
// 子线程任务
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
// 子线程任务
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
Thread thread3 = new Thread(() -> {
try {
// 子线程任务
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
thread3.start();
// 所有线程都到达屏障点后执行后续代码
- JVM内存区域分为哪几个部分:JVM内存区域主要分为以下几个部分:
- 程序计数器:是一块较小的内存空间,它记录着当前线程执行的字节码指令的地址。每个线程都有自己独立的程序计数器。
- 虚拟机栈:每个方法执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。虚拟机栈是线程私有的,它的生命周期与线程相同。
- 本地方法栈:与虚拟机栈类似,只不过它是为本地方法服务的。本地方法是用C或C++实现的方法,通过JNI(Java Native Interface)与Java代码进行交互。
- 堆:是JVM中最大的一块内存区域,它被所有线程共享,用于存放对象实例和数组。堆是垃圾回收的主要区域。
- 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区也被所有线程共享,在Java 8及以后,方法区被元空间(MetaSpace)取代。
- 垃圾回收机制:垃圾回收机制是Java自动内存管理的一部分,用于回收不再使用的内存空间。Java中的垃圾回收器会自动检测哪些对象不再被引用,然后将这些对象占用的内存空间回收。常见的垃圾回收算法有标记清除算法、标记整理算法、复制算法、分代收集算法等。
- 标记清除算法:先标记出所有需要回收的对象,然后统一回收被标记的对象。这种算法会产生大量不连续的内存碎片。
- 标记整理算法:在标记清除算法的基础上,在清除对象后,将存活的对象向一端移动,然后直接清理掉边界以外的内存。
- 复制算法:将内存空间分为两块,每次只使用其中一块,当这一块内存用完后,将存活的对象复制到另一块内存中,然后清除原来的那块内存。这种算法适用于对象存活率较低的场景。
- 分代收集算法:根据对象的存活周期将内存划分为不同的区域,一般分为新生代、老年代和永久代(Java 8后为元空间)。针对不同区域采用不同的垃圾回收算法,比如新生代采用复制算法,老年代采用标记清除或标记整理算法。
- 类加载机制:类加载机制是Java程序运行的基础,它负责将类的字节码文件加载到内存中,并将其转化为JVM能够识别的Class对象。类加载机制主要分为以下几个步骤:
- 加载:通过类的全限定名来获取定义此类的二进制字节流,然后将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,最后在内存中生成一个代表这个类的Class对象。
- 验证:确保加载的类的字节码文件符合JVM规范,不会危害虚拟机安全。
- 准备:为类的静态变量分配内存,并设置默认初始值。
- 解析:将类的常量池中的符号引用替换为直接引用。
- 初始化:执行类构造器方法的过程,为类的静态变量赋予正确的初始值。例如,一个类中有静态变量
static int a = 10;,在准备阶段a被初始化为0,在初始化阶段才会被赋值为10。