(高频问题)41-60 计算机 Java后端 实习 and 秋招 面试高频问题汇总
41.redis中分布式锁提前过期会怎么样
在Redis中使用分布式锁时,如果锁提前过期,可能会引发几个问题,主要取决于锁的使用场景和具体的实现方式。分布式锁通常用于确保在分布式系统中对共享资源的访问是同步的,以避免数据不一致、数据覆盖或其他并发问题。如果锁提前过期,可能会有以下影响:
- 安全性问题:如果一个进程(或线程)持有锁进行操作,而锁突然过期,另一个进程就可能获得锁并开始操作相同的资源。这可能导致数据不一致或数据损坏,因为两个进程可能会同时修改同一资源。
- 性能问题:锁提前过期可能导致更多的竞争和锁争夺,从而增加系统的负载和延迟。
- 死锁:虽然Redis的锁提前过期不直接导致死锁,但如果因为锁提前过期而引发的逻辑错误导致某些进程无法释放其他资源,可能间接导致系统出现死锁状态。
为了减少锁提前过期的风险和影响,可以采用以下策略:
- 合理设置锁的过期时间:确保锁的过期时间足够长,以覆盖预期内的操作时间,同时避免设置过长的过期时间,以减少资源锁定的时间。
- 使用续租机制:对于执行时间较长的操作,可以实现锁的续租机制,即在锁快要过期时,检查当前进程是否仍然需要锁,如果需要,则更新锁的过期时间。
锁的创建与过期时间设置:在Redis中,分布式锁通常使用SET命令加上NX(只在键不存在时设置键)和PX(设置键的过期时间,单位为毫秒)选项来实现。例如,SET lock_key unique_value NX PX 30000这个命令试图获取一个名为lock_key的锁,其中unique_value用于标识锁的拥有者,确保只有锁的持有者才能释放该锁。PX 30000表示这个锁的过期时间是30000毫秒(30秒)。
锁的续租:续租机制的实现需要客户端定期更新锁的过期时间,以防止在持有锁的操作未完成时锁过期。这可以通过重新设置过期时间来完成,比如使用PEXPIRE命令:PEXPIRE lock_key 30000,将lock_key的过期时间再次设置为30000毫秒。续租操作需要在一个独立的线程或者定时任务中执行,频率应该小于锁的过期时间,以确保锁不会因为超时而被误解。PEXPIRE 在英文中可以读作 "P-expire"
安全的续租实现:为了确保续租操作的安全性,即只有锁的持有者才能续租该锁,可以使用Lua脚本来原子性地检查unique_value并更新过期时间。这样做可以避免在检查和设置过期时间之间的时间差导致的安全问题。
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("pexpire", KEYS[1], ARGV[2]) else return 0 end
上述Lua脚本首先检查lock_key对应的值是否为unique_value,即确认当前客户端是否为锁的持有者,如果是,则更新锁的过期时间;如果不是,则不做任何操作。
锁的释放:锁的持有者在操作完成后应该立即释放锁,这同样可以通过一个Lua脚本安全地完成,确保只有锁的持有者才能释放锁。
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end
通过以上步骤,可以实现一个较为安全且具有续租机制的Redis分布式锁,有效防止因操作延时导致的锁自动释放问题,增强了分布式环境下资源访问的控制效率和安全性。然而,需要注意的是,Redis的锁机制并不是完全可靠的,它依赖于网络环境和Redis服务器的状态。在高要求的生产环境中,可能需要结合其他工具和策略来确保分布式锁的可靠性。
同时,一些开源的Redis客户端或框架也提供了现成的分布式锁实现,如Redisson,可以直接使用。 为了确保只有锁的持有者才能续租该锁,我们可以在获取锁时为锁关联一个唯一标识符(如UUID),然后在续租时验证续租请求是否携带了与锁关联的标识符。这样可以防止其他客户端恶意续租锁。
以下是实现步骤:
- 获取锁时,生成一个唯一标识符(如UUID),将其与锁的键一起存储到Redis中。
- 在续租锁时,客户端需要携带这个唯一标识符。
- Redis使用Lua脚本来验证续租请求的标识符与锁关联的标识符是否一致,只有一致时才允许续租。
- 锁释放的幂等性:确保锁的释放操作是幂等的,即多次释放操作和一次释放操作的效果相同,这样即使在锁提前过期的情况下也能安全地释放锁。
- 检查锁的拥有权:在执行操作之前和之后,检查当前进程是否真正拥有锁。这可以通过在Redis中存储一个唯一的锁标识符来实现,只有当进程知道正确的标识符时才能获得或释放锁。
通过上述措施,可以减轻Redis分布式锁提前过期带来的风险和影响,保障分布式系统的稳定性和一致性。
42.jvm内存模型 堆空间的结构?分配策略有哪些?
JVM的内存模型主要分为以下几个区域:
- 方法区:这个区域用于存储已被加载的类信息,常量,静态变量,编译器编译后的代码等数据。
- 堆区:这是Java中最大的一块存储区域,几乎所有的对象都是在这里分配内存的。堆是被所有线程共享的一块区域,可被分为新生代和老年代。新创建的对象首先分配在新生代,经过一次 Minor GC 后,如果有存活的对象,会被移到老年代。
- 栈区:每个线程创建时都会创建一个虚拟机栈,每个方法被执行的时候都会动态创建一块栈帧用于存储局部变量表,操作数栈,方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
- 程序计数器(PC寄存器):这是最小的存储区域,其容量只有几个字宽。可以看作是当前线程所执行的字节码的行号指示器。改变计数器的值来选取下一条需要执行的字节码指令。
- 本地方法栈:和虚拟机栈功能类似,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法 (也就是字节码)服务,本地方法栈则为虚拟机使用到的Native方法服务。
在Java中,堆空间(Heap Space)是由JVM在启动时创建的内存区域,它用于存储运行时数据,主要用于存放对象实例和数组。了解Java堆空间的结构对于优化应用程序和垃圾收集(Garbage Collection, GC)有很大帮助。
堆空间的结构
Java堆空间的结构从JDK 7和JDK 8开始有所变化,这里主要介绍JDK 8及以后版本的结构:
年轻代 (Young Generation):
- Eden空间: 大多数情况下,新创建的对象首先被分配到Eden区。
- 幸存者区 (Survivor Spaces): 包括两个部分,Survivor from/to 1 (S1) 和 Survivor 2 (S2)。它们用来存放从Eden区经过第一次垃圾收集仍然存活的对象。
老年代 (Old Generation):
- 存放长时间存活的对象。当对象在年轻代经过一定次数的垃圾收集后仍然存活,就会被移动到老年代。
永久代/元空间 (PermGen/Metaspace,取决于JDK版本):
- JDK 7之前: 永久代(PermGen)用于存放Java类和方法信息。
- JDK 8之后: 元空间(Metaspace)替代了永久代。Metaspace并不在虚拟机的堆内存中,而是使用本地内存。这一变化是为了避免永久代内存溢出(OutOfMemoryError)的问题。
分配策略
堆空间的分配策略指的是JVM如何在堆空间中分配内存给新创建的对象和数组。一些常见的分配策略包括:
- 对象优先在Eden分配: 新建对象通常会在Eden区分配。当Eden区填满时,会触发一次Minor GC。
- 大对象直接进入老年代: JVM有一个阈值来定义大对象。如果对象超过了这个大小,会直接被分配到老年代,以避免在Eden区和两个Survivor区之间复制产生的开销。
- 长期存活的对象将进入老年代: 每个对象都有一个年龄计数器,当对象在年轻代中存活足够的垃圾收集周期,并且超过了某个年龄阈值后,会被移动到老年代。
- 动态对象年龄判定: 如果Survivor空间中相同年龄所有对象大小的总和超过Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到阈值年龄。 至于为什么这个规则不适用于Eden空间,原因是Eden空间和Survivor空间的使用目标不同。Eden空间主要用于存放新创建的对象,而Survivor空间则用于存放已经存活的对象,因此,对于长时间存活的对象,我们希望它们能够尽快被移到老年区,以保持Survivor空间的流动性。
- 空间分配担保: 在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于年轻代所有对象的总空间,如果不足,则进行Full GC来清理老年代的空间。
43.linux文件有哪些类型?各自的作用是什么?
在Linux操作系统中,文件是存储数据的基本单位。Linux中的文件类型多样,每种类型的文件都有其特定的作用和用途。以下是Linux中常见的几种文件类型及其作用:
- 普通文件(Regular Files):这是最常见的文件类型,用于存储数据,比如文本文件、程序脚本或二进制程序等。普通文件可以包含文本、源代码或程序数据。
- 目录(Directories):目录是一种特殊类型的文件,用于组织文件系统中的文件和其他目录。目录文件包含了它所包含的文件和目录的列表及其相关信息。
- 字符设备文件(Character Device Files):这种文件类型代表一种设备,比如键盘、鼠标或其他输入/输出设备,它们是以字符流的形式进行数据传输的。
- 块设备文件(Block Device Files):块设备文件也代表设备,但与字符设备不同,它们以数据块的形式存储数据,通常用于磁盘驱动器或其他存储设备。
- 链接(Links):链接是指向另一个文件的引用。有两种类型的链接:硬链接(hard link)和符号链接(symbolic link,又称软链接)。硬链接是对文件的另一个引用,它与原文件共享相同的数据。符号链接则是一个特殊的文件,包含了另一个文件的路径。
- 管道(Pipes):管道是一种特殊类型的文件,主要用于进程间通信。它允许一个进程和另一个进程之间的数据流动。
- 套接字(Sockets):套接字也是一种特殊类型的文件,用于在网络中的不同主机间或同一主机上的不同进程间进行通信。
- 设备文件(Device Files):设备文件是系统用来访问硬件设备的接口文件,包括字符设备文件和块设备文件。
每种文件类型在Linux系统中扮演着不同的角色,它们共同协作,使得Linux系统能够高效地管理数据和资源。
44.inode的作用?inode包含哪些内容?给出一个文件名,Linux是如何根据该文件名打开文件的?(文件名->inode->block)文件的访问时间是如何记录的?
在Linux文件系统中,inode(索引节点)是一个非常核心的概念,它用于存储文件的元数据(不包括文件名和文件实际内容)。每个文件或目录都有一个与之对应的inode,其中包含了该文件的几乎所有信息。
inode的作用
- 存储文件元数据:inode存储了关于文件的所有基本信息,除了文件名和实际数据内容。
- 文件系统操作:在执行文件系统操作如打开、读取、写入文件时,系统会使用inode中的信息来进行这些操作。
inode包含的内容
inode包含的信息大致可以分为以下几类:
- 文件类型:文件是普通文件、目录还是链接文件等。
- 权限和所有权:文件的访问权限(读、写、执行)以及文件的所有者和所属组。
- 时间戳:文件的创建时间、最后访问时间、最后修改时间等。
- 文件大小:文件内容的大小(字节为单位)。
- 指向文件数据块的指针:这些指针实际上指向存储文件数据的磁盘块。
从文件名到打开文件的过程
当给出一个文件名时,Linux系统如何根据该文件名打开文件的过程通常遵循以下步骤:
- 查找目录项:系统首先在当前目录下查找文件名对应的目录项。目录本身也是一种文件,其内容是一系列的目录项,每个目录项将一个文件名映射到一个inode号。
- 获取inode:一旦找到文件名对应的目录项,系统就会读取该目录项中的inode号,并通过这个inode号在文件系统的inode表中找到对应的inode。
- 读取文件数据:系统根据inode中存储的文件数据块位置信息,找到文件数据存储在磁盘上的位置,进而可以读取、写入或修改文件数据。
45.零拷贝是什么?用来解决什么问题?有哪些应用场景?实现方式有哪些?
实现方式
零拷贝可以通过多种方式实现,常见的实现方式包括:
- 利用内存映射(mmap):通过内存映射,应用程序可以直接访问磁盘上的文件,而无需将文件内容读入到用户空间的缓冲区中,减少了一次数据复制。
- 直接I/O:直接I/O绕过了操作系统的缓存,允许磁盘I/O操作直接在用户空间缓冲区和磁盘之间传输数据,避免了数据在用户空间和内核空间之间的多次复制。
- sendfile系统调用:sendfile是Linux提供的一种高效的数据传输方式,它可以直接在文件描述符之间传输数据,绕过用户空间,减少数据复制。
- Linux的splice和tee系统调用:splice可以将数据从一个文件描述符移动到另一个文件描述符中,而不需要将数据复制到用户空间。tee用于在两个文件描述符之间复制数据,同时避免了数据复制到用户空间。
46.TCP 和 UDP 可以使用同一个端口吗?
TCP和UDP可以使用同一个端口号,因为它们是两种不同的协议,操作在不同的网络层。在网络中,一个端口号的唯一性是由IP地址、端口号以及协议三者共同决定的。这意味着TCP和UDP各自可以绑定相同的端口号,而不会产生冲突,因为协议类型(TCP或UDP)作为区分。这种机制允许应用程序同时或分别监听相同的端口号上的TCP和UDP流量。
举例来说,DNS服务就是一个典型的例子,它通常同时在UDP和TCP的53端口上监听。对于大多数查询,DNS使用UDP协议,因为它更快,且DNS请求和响应通常都很小,适合UDP。然而,当DNS响应数据较大时,超过了UDP的限制,就会改用TCP协议,以确保数据的完整性和可靠性。
因此,在设计和部署网络应用程序时,可以根据需要,为同一端口号配置TCP和UDP协议,这不会引起端口冲突,只要保证同一协议类型下,端口号的唯一性即可。
47.MySQL 存储引擎特性对比及 InnoDB 的主导地位分析
MySQL 提供了多种存储引擎,用于满足不同的数据存储、性能和功能需求。选择合适的存储引擎对于数据库的效率和稳定性至关重要。其中,InnoDB 和 MyISAM 是最广为人知的两种引擎,但 MySQL 还提供了其他各具特色的引擎。
InnoDB 是 MySQL 的默认存储引擎(自 5.5.5 版本起),其核心优势在于全面支持 ACID 事务,包括提交、回滚和崩溃恢复机制,极大地保障了数据的完整性和一致性。它支持行级锁定,在高并发场景下能提供更好的性能表现,并支持外键约束来维护数据间的引用完整性。这些特性使得 InnoDB 非常适用于需要高可靠性、事务支持和并发处理的在线事务处理(OLTP)系统。
MyISAM 曾是 MySQL 的默认引擎,它不支持事务和外键,采用表级锁定,这在高并发写入场景下可能导致性能瓶颈。然而,MyISAM 在读取密集型操作、数据检索以及全文搜索方面表现出色,因此常被用于只读或读远多于写的应用,如数据仓库或某些 Web 应用场景。
除了 InnoDB 和 MyISAM,MySQL 还提供其他专用引擎: MEMORY 引擎将数据存储在内存中,提供极快的访问速度,适用于缓存或临时数据存储,但数据会在服务重启后丢失; Archive 引擎专为存储大量归档数据(如日志)设计,具有高压缩比,但仅支持插入和查询操作; CSV 引擎以逗号分隔的文本文件形式存储数据,便于数据的导入导出; Federated 引擎允许用户访问远程 MySQL 服务器上的表; BLACKHOLE 引擎则是一个“黑洞”,它接受写入但不存储数据,常用于数据复制中继或性能测试场景。
InnoDB 之所以成为最广泛使用的存储引擎,主要是因为它提供了一套关键的企业级特性。对事务(ACID)的完整支持确保了数据操作的原子性、一致性、隔离性和持久性,这对于金融、电商等关键业务系统至关重要。其强大的崩溃恢复能力和通过行级锁定实现的高并发性能,进一步提升了数据库的稳定性和响应速度。同时,外键约束有助于在数据库层面强制实施数据间的关联关系,保证了数据的引用完整性。综合这些优点,InnoDB 成为了大多数现代应用开发的首选。
48.spring配置加载顺序
Spring Framework 在启动时加载配置文件的顺序遵循一定的规则,以确保配置的灵活性和可覆盖性。Spring Boot 进一步增强了这一机制,提供了一个多层次、有序的配置加载方式,允许从不同的来源读取配置,包括属性文件、YAML文件、环境变量和命令行参数等。以下是Spring Boot中配置加载的一般顺序,从高优先级到低优先级:
- 命令行参数:任何直接在命令行上传递的参数(例如,使用--name=value的形式)。
- 来自Java系统属性(System Properties)的配置:通过System.getProperties()设置的属性。
- 操作系统环境变量:系统级别的环境变量。
- JNDI属性:在Java命名和目录接口(JNDI)下的java:comp/env中查找的属性。
- Java配置类:通过@Configuration注解标注的类,内部使用@Bean定义的配置信息。
- Spring Boot应用程序属性位于application.properties或application.yml文件中的配置,这些文件可以位于多个位置,优先级按照以下顺序:
- file:./config/
- file:./
- classpath:/config/
- classpath:/ 对于上述位置,优先加载位于./config/下的配置文件,其次是位于当前目录下的,然后是类路径/config/下的,最后是类路径根下的。
- 在配置服务器上的配置(如果使用Spring Cloud Config Server)。
- 打包在应用程序内的配置:应用内部的application.properties或application.yml文件。
- 通过@PropertySource注解指定的属性文件:在配置类中,可以使用@PropertySource注解指定要加载的属性文件。
- 默认属性:通过SpringApplication.setDefaultProperties指定的默认属性。
49.spring源码的设计模式有啥在哪用到了
Spring Framework 的设计和架构中广泛使用了设计模式,以提供灵活、高效和可扩展的开发框架。以下是一些在Spring源码中常见的设计模式及其应用示例:
单例模式(Singleton Pattern):
- 用于Sprin
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
曾获多国内大厂的 ssp 秋招 offer,且是Java5年的沉淀老兵(不是)。专注后端高频面试与八股知识点,内容系统详实,覆盖约 30 万字面试真题解析、近 400 个热点问题(包含大量场景题),60 万字后端核心知识(含计网、操作系统、数据库、性能调优等)。同时提供简历优化、HR 问题应对、自我介绍等通用能力。考虑到历史格式混乱、质量较低、也在本地积累了大量资料,故准备从头重构专栏全部内容