在互联网大厂的面试室里,面试官正严肃地看着面前的求职者王铁牛,准备开始一场关于 Java 技术的面试。
第一轮: 面试官:首先,说说你对 Java 核心知识的理解吧。 王铁牛:Java 的核心知识包括面向对象编程的三大特性:封装、继承、多态。 面试官:不错,那你能举例说明一下封装的好处吗? 王铁牛:封装可以隐藏类的内部实现细节,只对外提供必要的接口,这样可以提高代码的安全性和可维护性。 面试官:很好,那继承呢?继承有什么作用? 王铁牛:继承可以实现代码的复用,子类可以继承父类的属性和方法,避免重复编写代码。
第二轮: 面试官:接着,我们来谈谈 JUC 相关的知识吧。你知道什么是线程安全吗? 王铁牛:线程安全就是多个线程同时访问共享资源时,不会出现数据不一致的情况。 面试官:那你说说如何实现线程安全呢? 王铁牛:可以使用同步代码块或者同步方法来保证线程安全,也可以使用锁来避免资源竞争。 面试官:不错,那你了解线程池吗?线程池有什么优点? 王铁牛:线程池可以提高线程的复用性,减少创建和销毁线程的开销,还可以控制线程的数量,避免资源过度消耗。
第三轮: 面试官:现在,我们来聊聊 JVM 方面的知识吧。你知道 JVM 的内存结构吗? 王铁牛:JVM 的内存结构包括堆、栈、方法区等。堆用于存储对象实例,栈用于存储局部变量和方法调用栈,方法区用于存储类信息和常量池等。 面试官:那堆又分为哪几种呢? 王铁牛:堆分为新生代和老年代,新生代又分为 Eden 区和两个 Survivor 区。 面试官:很好,那你了解垃圾回收机制吗? 王铁牛:垃圾回收机制是自动回收不再被引用的对象所占用的内存空间,常用的垃圾回收算法有标记-清除、复制、标记-整理等。
面试官:今天的面试就到这里吧,你回去等通知吧。
答案:
- Java 核心知识:
- 面向对象编程的三大特性:
- 封装:通过将数据和操作封装在类中,对外提供公共的接口,隐藏内部实现细节,提高代码的安全性和可维护性。例如,在一个银行账户类中,可以将账户余额封装起来,只提供存款、取款等公共方法,而隐藏账户余额的具体存储方式和计算逻辑。
- 继承:子类继承父类的属性和方法,实现代码的复用。子类可以根据需要重写父类的方法,实现多态性。例如,动物类有 eat() 方法,猫类继承动物类,也有 eat() 方法,但猫的 eat() 方法可能与动物的 eat() 方法有所不同。
- 多态:同一操作作用于不同的对象可以有不同的表现形式。多态性可以提高代码的灵活性和可扩展性。例如,一个 print() 方法可以根据不同的对象类型输出不同的内容,如打印字符串、打印整数等。
- 面向对象编程的三大特性:
- JUC(Java 并发工具包):
- 线程安全:在多线程环境下,保证共享资源的一致性和正确性。当多个线程同时访问共享资源时,如果不进行适当的同步处理,就可能出现数据不一致的情况。例如,在一个银行转账系统中,如果两个线程同时对同一个账户进行转账操作,而没有进行同步处理,就可能导致账户余额不正确。
- 实现线程安全的方式:
- 同步代码块:使用 synchronized 关键字包裹需要同步的代码块,确保同一时间只有一个线程可以进入该代码块。例如:
synchronized (lock) {
// 同步代码块
}
其中,lock 可以是任何对象,用于充当锁。 - 同步方法:在方法声明中使用 synchronized 关键字,使得整个方法在同一时间只能被一个线程访问。例如:
public synchronized void method() {
// 同步方法
}
- **锁**:除了使用 synchronized 关键字,还可以使用显式的锁对象,如 ReentrantLock。ReentrantLock 提供了更多的高级功能,如可中断锁、公平锁等。例如:
Lock lock = new ReentrantLock();
lock.lock();
try {
// 同步代码块
} finally {
lock.unlock();
}
- **线程池**:
- **优点**:
- **提高线程的复用性**:线程池中的线程可以重复利用,避免了频繁创建和销毁线程的开销。当有任务需要执行时,直接从线程池中获取一个空闲线程,任务执行完毕后,线程不会被销毁,而是返回线程池中等待下一个任务。
- **减少系统资源消耗**:通过限制线程池中的线程数量,可以避免系统创建过多的线程而导致资源过度消耗,如内存占用过高、CPU 利用率过高等。
- **便于线程管理**:线程池提供了一套线程管理的机制,如线程的创建、销毁、线程状态的监控等,可以方便地对线程进行管理和调度。
- JVM(Java 虚拟机):
- 内存结构:
- 堆:用于存储对象实例,是 JVM 管理内存的主要区域。堆分为新生代和老年代,新生代又分为 Eden 区和两个 Survivor 区。新生代主要用于存储新创建的对象,老年代用于存储经过多次垃圾回收后仍然存活的对象。
- 栈:用于存储局部变量、方法参数和方法调用栈等。每个线程都有自己的栈,栈的大小相对较小,存储的内容随着方法的调用和返回而创建和销毁。
- 方法区:用于存储类信息、常量池、静态变量等。方法区是共享的区域,所有线程都可以访问其中的内容。
- 堆的细分:
- 新生代:
- Eden 区:新创建的对象首先会分配在 Eden 区。
- Survivor 区:有两个 Survivor 区,一般称为 From 区和 To 区。当 Eden 区满了之后,会触发 Minor GC(年轻代垃圾回收),将 Eden 区和 From 区中不再被引用的对象回收,存活的对象会被复制到 To 区。如果 To 区不够用,会将对象转移到老年代。
- 老年代:经过多次 Minor GC 后仍然存活的对象会被移动到老年代。老年代的空间相对较大,存放的是相对持久的对象。
- 新生代:
- 垃圾回收机制:
- 标记-清除:首先标记出所有需要回收的对象,然后统一回收这些对象所占用的内存空间。这种方式简单直接,但存在碎片化的问题,即回收后会产生大量不连续的空闲内存块,不利于后续对象的分配。
- 复制:将内存分为两个相等的区域,每次只使用其中一个区域。当对象分配在一个区域时,另一个区域处于空闲状态。当一个区域满了之后,就将该区域中的存活对象复制到另一个区域,然后清理掉这个区域的所有对象。复制算法的优点是实现简单,没有碎片问题,但需要两倍的内存空间。
- 标记-整理:标记出需要回收的对象,然后将存活的对象向一端移动,最后清理掉边界以外的内存空间。标记-整理算法解决了标记-清除算法的碎片问题,同时也避免了复制算法需要两倍内存空间的缺点。
- 内存结构:
希望以上内容对你有所帮助,祝你面试顺利!