0109鹅厂面经
设计模式
- 常见设计模式及应用场景
- 单例模式:确保一个类只有一个实例,并提供一个全局访问点。应用场景如数据库连接池,整个应用中只需要一个连接池实例来管理数据库连接,避免频繁创建和销毁连接,提高性能和资源利用率。
- 工厂模式:定义一个创建对象的接口,让子类决定实例化哪一个类。例如在日志记录系统中,根据不同的日志级别(如 INFO、ERROR 等)创建不同的日志记录器对象,将对象的创建与使用分离,提高代码的可扩展性和可维护性。
- 观察者模式:定义了对象之间的一对多依赖,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知并自动更新。比如在股票交易系统中,当股票价格发生变化时,通知所有关注该股票的投资者(观察者),使他们能够及时获取最新信息并做出相应决策。
TCP 协议为什么不能只进行两次握手而需要进行三次握手
- 原因
- 两次握手可能导致已失效的连接请求报文段突然又传送到了服务器,服务器会误认为是客户端发起的新连接请求,从而建立一个无效的连接。而三次握手可以避免这种情况,客户端收到服务器对连接请求的确认后,会再次给服务器发送一个确认报文,服务器只有收到这个确认报文才会认为连接建立成功。
请描述一下 HTTP 和 HTTPS 的区别是什么
- 区别
- 安全性:HTTP 是明文传输,数据在传输过程中容易被窃取和篡改;HTTPS 在 HTTP 的基础上加入了 SSL/TLS 加密协议,对数据进行加密传输,保证了数据的安全性和完整性。
- 端口:HTTP 默认使用 80 端口;HTTPS 默认使用 443 端口。
- 证书:HTTPS 需要向权威机构申请数字证书,用于验证服务器的身份,确保客户端连接到的是真实的服务器;HTTP 没有此要求。
- 性能:HTTPS 由于需要进行加密和解密操作,会消耗更多的服务器资源,导致性能略低于 HTTP,但随着硬件性能的提升和加密算法的优化,这种性能差距在逐渐缩小。
你能否详细描述一下 HTTPS 的加密过程
- 加密过程
- 客户端发起请求:客户端向服务器发送 HTTPS 请求,请求连接到服务器的 443 端口。
- 服务器发送证书:服务器收到请求后,将自己的数字证书发送给客户端。证书包含服务器的公钥、证书颁发机构(CA)的信息等。
- 客户端验证证书:客户端收到证书后,使用本地存储的 CA 根证书来验证服务器证书的合法性。包括检查证书是否过期、证书的域名是否与请求的域名匹配、证书是否被吊销等。如果验证通过,客户端会生成一个随机的对称加密密钥(如 AES 密钥),并使用服务器的公钥对该密钥进行加密。
- 客户端发送加密密钥:客户端将加密后的对称密钥发送给服务器。
- 服务器解密密钥:服务器使用自己的私钥解密收到的加密密钥,得到客户端生成的对称加密密钥。
- 数据加密传输:此后,客户端和服务器之间的数据传输都使用这个对称加密密钥进行加密和解密,保证了数据的安全性和完整性。
对称加密和非对称加密算法的原理是什么?它们分别适用于哪些使用场景
-
对称加密原理
加密和解密使用相同的密钥。例如 DES(数据加密标准)算法,将明文和密钥通过特定的算法进行加密得到密文,解密时使用相同的密钥和算法将密文还原为明文。
- 适用场景:适用于对大量数据进行加密的场景,因为其加密和解密速度快,效率高。比如在本地磁盘上对文件进行加密存储,或者在网络中传输大量数据时,为了保证数据的保密性,可以使用对称加密算法对数据进行加密。
-
非对称加密原理
使用一对密钥,即公钥和私钥。公钥可以公开,用于加密数据;私钥由持有者保密,用于解密数据。例如 RSA 算法,客户端使用服务器的公钥对数据进行加密,服务器收到加密后的数据后,使用自己的私钥进行解密。
- 适用场景:主要用于密钥交换、数字签名等场景。在 HTTPS 中,客户端和服务器通过非对称加密算法交换对称加密密钥,保证了密钥交换的安全性;数字签名可以确保数据的来源和完整性,发送方使用自己的私钥对数据进行签名,接收方使用发送方的公钥进行验证。
请举例说明一个可能导致死锁的场景
- 示例
- 假设我们有两个线程 Thread A 和 Thread B,以及两个资源 Resource1 和 Resource2。
- 线程 Thread A 持有 Resource1,同时请求 Resource2。
- 线程 Thread B 持有 Resource2,同时请求 Resource1。
- 这就造成了循环等待,从而导致死锁。
你有哪些解决死锁的方法或思路
- 解决方法或思路
- 预防死锁:通过破坏死锁产生的四个必要条件(互斥条件、请求与保持条件、不可剥夺条件、循环等待条件)中的一个或多个来预防死锁的发生。例如,采用资源静态分配策略,在进程运行前一次性分配其所需的全部资源,这样就破坏了请求与保持条件。
- 避免死锁:在资源分配过程中,采用某种算法来动态地检测并避免系统进入不安全状态。常见的算法有银行家算法,系统在分配资源之前,先计算资源分配的安全性,只有在确保分配后系统仍处于安全状态时才进行分配。
- 检测死锁:允许系统在运行过程中产生死锁,然后通过死锁检测算法来检测死锁的发生,并确定与死锁有关的进程和资源,然后采取相应的措施来解除死锁。
- 解除死锁:当检测到死锁后,采取一定的措施来解除死锁。常用的方法有剥夺资源,将某个资源从一个进程强行剥夺分配给另一个进程;撤销进程,强制撤销部分甚至全部死锁进程,并剥夺这些进程的资源。
请解释一下什么是虚拟内存?
- 解释
- 虚拟内存是内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存(即虚拟地址空间),而实际上这些内存可能并不全部存储在物理内存中,部分数据会被存储在外部磁盘存储器上,在需要时进行数据交换。
- 当程序运行时,操作系统会将程序的一部分数据加载到物理内存中,当物理内存不足时,操作系统会将暂时不用的数据从物理内存交换到磁盘上的虚拟内存区域(称为页面交换),当需要这些数据时,再将其从磁盘交换回物理内存。这样可以让多个程序同时运行,并且每个程序都可以使用比实际物理内存更大的内存空间,提高了内存的利用率和系统的多任务处理能力。
你是如何检测和解决内存泄漏问题的?
- 检测内存泄漏的方法
- 使用工具
- Java VisualVM:它是 JDK 自带的一款可视化工具,可以实时监控 Java 应用程序的内存使用情况、线程状态、GC 活动等。通过查看堆内存的使用趋势、对象实例数量等信息,可以发现内存泄漏的迹象。例如,如果发现某个对象的实例数量在不断增加,且没有被正常回收,可能就存在内存泄漏。
- Eclipse Memory Analyzer Tool (MAT):可以对 Java 堆转储文件(heap dump)进行分析,找出占用内存较大的对象以及它们之间的引用关系,帮助定位内存泄漏的根源。它可以生成内存泄漏报告,显示可能导致内存泄漏的对象路径和相关信息。
- 代码审查
- 检查是否存在对象创建后没有正确释放的情况,比如打开了文件、数据库连接、网络连接等资源,但没有在使用完毕后及时关闭。
- 查看是否有集合类被添加了大量对象,但没有及时清理不再使用的对象,导致集合占用的内存越来越大。
- 检查静态变量的使用,静态变量的生命周期与应用程序相同,如果静态变量引用了大量对象,且这些对象没有被正确管理,可能会导致内存泄漏。
- 使用工具
- 解决内存泄漏的方法
- 对于资源未释放的情况,确保在 finally 块中关闭文件、数据库连接、网络连接等资源,例如:
try {
// 打开文件
FileInputStream fis = new FileInputStream("file.txt");
// 其他操作
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis!= null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
- 对于集合类导致的内存泄漏,及时清理不再使用的对象。例如,如果一个 List 中存储了一些临时数据,在使用完毕后,可以调用
list.clear()
方法来清理集合。 - 对于静态变量引用导致的内存泄漏,需要谨慎使用静态变量,确保静态变量引用的对象在不再需要时能够被正确释放。可以通过将静态变量设置为 null,或者在适当的时候清理静态变量引用的对象来解决。
Java 中的双亲委派模型
- 模型介绍
- 在 Java 中,类加载器主要分为以下几种:
- 启动类加载器(Bootstrap ClassLoader):它是最顶层的类加载器,由 C++ 语言实现,负责加载 Java 核心类库(如 java.lang 包下的类),加载路径是 JDK 安装目录下的 jre/lib 目录(如 rt.jar 等)。
- 扩展类加载器(Extension ClassLoader):它是由 Java 语言实现的,继承自 ClassLoader 类,负责加载 Java 的扩展类库,加载路径是 JDK 安装目录下的 jre/lib/ext 目录,或者通过系统属性
java.ext.dirs
指定的路径。 - 应用程序类加载器(Application ClassLoader):也称为系统类加载器,它也是由 Java 语言实现的,继承自 ClassLoader 类,负责加载应用程序的类路径(classpath)下的类,这是 Java 程序中默认的类加载器。
- 自定义类加载器(Custom ClassLoader):用户可以根据自己的需求自定义类加载器,继承自 ClassLoader 类,用于加载特定位置的类或实现特殊的加载逻辑。
- 这些类加载器之间形成了一个层次结构,启动类加载器是最顶层的父加载器,扩展类加载器是启动类加载器的子加载器,应用程序类加载器是扩展类加载器的子加载器,自定义类加载器是应用程序类加载器的子加载器(如果有)。这种层次结构和双亲委派机制一起构成了 Java 的类加载体系,保证了 Java 类的加载具有一定的安全性和稳定性,避免了类的重复加载和核心类库被篡改等问题。例如,如果用户自定义了一个名为
java.lang.String
的类,由于双亲委派机制,这个类不会被应用程序类加载器加载,而是会由启动类加载器加载真正的java.lang.String
类,从而保证了 Java 核心类库的安全性和一致性。
- 在 Java 中,类加载器主要分为以下几种: