Java面试题
提示:以下是本篇文章正文内容,下面案例可供参考
一、示例
1.能介绍一下你做过的项目吗?
2.ConcurrentHashMap 是线程安全的吗?为什么是安全的?
ConcurrentHashMap 是线程安全的。ConcurrentHashMap 通过使用分段锁(或更细粒度的 CAS 操作)、Node 节点、线程安全的扩容机制和优化的哈希函数等机制,实现了线程安全性。这使得它能够在高并发环境下提供高效的并发性能。
3.hashmap是如何解决hash冲突的
HashMap 在 Java 中解决哈希冲突的方法主要采用了链地址法(也称为拉链法)。当两个或更多的键根据哈希函数计算出相同的哈希值(即哈希冲突)时,HashMap 不会简单地用一个值替换另一个值,而是将这些具有相同哈希值的键值对存储在一个链表中。
总结来说,HashMap 通过链地址法(即拉链法)和在某些情况下使用红黑树来解决哈希冲突,从而实现了高效的键值对存储和查询。
4.使用过threadlocal吗?使用过程中要注意些什么
ThreadLocal 是 Java 提供的一个类,它提供了线程局部变量。这些变量与其他变量不同,因为每一个访问这个变量的线程都有它自己的独立初始化的变量副本。在多线程环境中,这可以用来存储线程特定的数据,而不需要担心数据不一致或同步问题。
在使用 ThreadLocal 的过程中,需要注意以下几点:
- 内存泄漏:ThreadLocal 的一个常见问题是内存泄漏。当 ThreadLocal 对象不再被引用,并且当前线程试图访问它时,由于 ThreadLocalMap 的键是弱引用(WeakReference),因此 ThreadLocal 对象会被垃圾回收。但是,如果 ThreadLocalMap 的值(即线程局部变量)是强引用,并且这些值很大或者数量很多,那么它们可能不会被及时回收,从而导致内存泄漏。为了避免这种情况,应该在使用完 ThreadLocal 后调用 remove() 方法手动清理。
- 线程池与 ThreadLocal:当使用线程池时,线程的生命周期可能很长,这意味着 ThreadLocal 变量可能会在很长一段时间内都保持活动状态。因此,在使用线程池时,需要特别小心 ThreadLocal 的使用,确保在不再需要时调用 remove() 方法。否则,可能会出现内存泄漏或意外的数据保留。
- 线程安全问题:虽然 ThreadLocal 本身是线程安全的,但它并不能保证线程中其他变量的线程安全性。因此,在编写多线程代码时,仍然需要注意其他变量的线程安全性问题。
- 继承问题:如果一个类继承了使用 ThreadLocal 的类,并且重写了 ThreadLocal 的访问方法(如 get() 或 set()),那么可能会出现意外的行为。因此,在设计和使用 ThreadLocal 时,需要避免这种情况。
- 清理资源:在使用 ThreadLocal 存储资源(如数据库连接、文件句柄等)时,需要确保在不再需要时及时清理这些资源,避免资源泄漏。
- 避免过度使用:虽然 ThreadLocal 可以简化多线程编程中的某些问题,但它并不适用于所有情况。过度使用 ThreadLocal 可能会导致代码变得难以理解和维护。因此,在使用 ThreadLocal 之前,应该仔细考虑是否真的需要它。
- 避免静态 ThreadLocal:尽量避免在静态字段中引用 ThreadLocal 实例,因为这可能导致类加载器泄漏和内存泄漏。相反,应该将 ThreadLocal 实例作为非静态字段创建,并在需要时传递给其他方法或类。
5.能详细说说为什么tcp连接是三次而不是四次?
- 简化连接过程:三次握手已经足够完成连接建立所需的必要操作。通过三次握手,客户端和服务器可以确认彼此的可达性和通信能力,同时同步双方的初始序列号,为后续的数据传输做好准备。四次握手虽然可以提供更多的确认步骤,但在TCP连接建立的过程中并不是必需的。
- 避免历史连接:三次握手的一个重要目的是为了防止旧的重复连接初始化造成混乱。在网络中,可能会出现客户端发送的连接请求因为网络问题而延迟到达服务器的情况。如果服务器只进行两次握手,那么可能会错误地将过时的连接请求当作新的请求来处理,从而建立了一个无效的连接。通过第三次握手,客户端可以确认服务器的响应是针对自己最新的连接请求,从而避免了历史连接的问题。
- 提高效率:使用三次握手可以在保证连接可靠性的同时提高连接建立的效率。在网络通信中,每次通信都需要消耗一定的时间和资源。通过精简连接过程,可以减少不必要的通信开销,提高网络资源的利用率。
6.事务的隔离级别有哪几种
事务的隔离级别主要有四种,它们是:
- 读未提交(Read Uncommitted):这是最低的隔离级别。一个事务可以读取另一个尚未提交的事务的数据。这种级别可能导致脏读、不可重复读和幻读。
- 读已提交(Read Committed):一个事务只能读取已经提交事务所做的数据修改。这种级别可以防止脏读,但可能出现不可重复读和幻读。它是大多数数据库系统的默认隔离级别(例如SQL Server)。
- 可重复读(Repeatable Read):对同一字段的多次读取结果都是一致的。这是MySQL的默认事务隔离级别。它可以防止脏读和不可重复读,但幻读仍有可能发生。
- 可串行化(Serializable):最高的隔离级别。所有的事务依次逐个执行,这样事务之间就不可能产生干扰。这种级别可以防止脏读、不可重复读以及幻读。但是,这将严重影响程序的性能。因为所有的事务依次逐个执行,这样事务之间就不可能产生干扰。
7.数据库的索引你了解吗?什么是非聚簇索引和聚簇索引?
数据库的索引是数据库中一种重要的数据结构,其作用类似于图书的目录,可以加快对表中数据的检索速度。每张表只能建一个聚簇索引,当在InnoDB表中定义一个主键时,该主键默认就会成为聚簇索引。
聚簇索引和非聚簇索引的主要区别在于数据存储方式、唯一性和查询效率。聚簇索引将数据按照索引顺序存储在磁盘上,查询效率高但唯一性强;而非聚簇索引将索引和数据分开存储,可以有多个且查询效率相对较低。在数据库设计和优化时,需要根据实际需求选择合适的索引类型。
8.你了解aop和ioc吗?aop你们在哪些场景使用了
-
AOP(面向切面编程)是一种程序设计范型。AOP的使用场景包括权限验证、事务管理、日志操作记录、异常处理、性能监控等。通过使用AOP,可以将这些横切关注点与业务逻辑代码分离,提高代码的模块化和可重用性。
-
IoC(控制反转)是一种软件设计思想,它将程序的控制权从程序内部转移到外部容器,实现了程序的松耦合和可扩展性。IoC的原理是通过依赖注入和依赖查找来实现的。IoC的应用场景主要涉及以下几个方面:
- 管理对象依赖关系:IoC容器可以自动注入对象之间的依赖关系,避免手动创建对象并传递依赖,减少代码的耦合度,提高代码的可读性和可维护性。
- 管理对象的生命周期:IoC容器可以管理对象的生命周期,当一个对象不再被使用时,IoC容器可以自动销毁它,释放资源,避免内存泄漏等问题。
- 管理配置信息:IoC容器可以将配置信息和代码分离,将配置信息集中管理,降低代码和配置的耦合度,便于维护和修改。
9.缓存你用过吗?你在使用缓存的过程中有遇到过哪些问题,你是怎么解决的呢?
缓存是一种存储数据的技术,通过将经常访问的数据保存在临时存储器中,以便将来需要时可以更快地获取。这样可以减少数据访问的时间和成本,提高系统的性能和响应速度。
在使用缓存的过程中,我遇到过以下一些问题:
- 缓存穿透:这是指查询一个数据库和缓存中都不存在的数据,导致每次查询都会直接落到数据库上,增加了数据库的负载。为了解决这个问题,我采用了布隆过滤器等技术,在查询前先判断查询的数据是否存在于缓存或数据库中,如果不存在则不进行后续查询操作。
- 缓存击穿:当某个热点数据过期或者被删除时,大量的请求同时进入数据库获取数据,导致数据库压力过大。为了解决这个问题,我添加了互斥锁或者使用分布式锁,确保只有一个请求去数据库中查询数据,其他请求则等待并使用缓存中的旧数据。
- 缓存雪崩:在缓存重启或者大量缓存集中在某一个时间段失效时,大量的请求直接访问数据库,导致数据库压力过大。为了应对这种情况,我采用了多种缓存策略,如设置缓存过期时间时加入随机值、设置缓存永不过期、利用Redis的持久化机制等。
10.我看你简历上使用了spring cloud,能聊聊为什么要用微服务架构吗?
使用Spring Cloud和微服务架构的原因有多方面,以下是其中的一些关键点:
- 复杂性管理:随着应用程序的增长和复杂性的增加,传统的单体应用架构变得难以管理和维护。微服务架构通过将大型应用程序拆分为一系列小型、独立的服务,使得每个服务都可以由不同的团队独立开发、测试、部署和扩展,从而简化了复杂性管理。
- 技术异构性:微服务架构允许使用不同的技术栈和语言来构建服务,这使得团队可以根据其专长和项目的需求选择最适合的技术。Spring Cloud作为微服务架构的框架,提供了丰富的工具和库来支持各种技术栈,如Java、Spring Boot等。
- 可扩展性:微服务架构的每个服务都可以独立扩展,这意味着可以根据需要增加或减少特定服务的资源。这种灵活性使得系统能够更好地应对流量峰值和低谷,同时保持较低的总体成本。
- 容错性:在微服务架构中,如果某个服务出现故障,其他服务仍然可以正常运行。此外,通过使用Spring Cloud的断路器(Hystrix)等组件,可以隔离故障服务,防止其影响整个系统。
- 快速迭代和交付:由于微服务架构允许独立开发和部署服务,因此可以更快地迭代和交付新功能。这使得团队能够更快地响应业务需求,并保持竞争优势。
- 服务重用:微服务架构鼓励创建可重用的服务。通过将通用功能封装为服务,可以在多个应用程序之间共享这些功能,从而减少了重复工作并提高了开发效率。
- 便于测试和部署:由于微服务架构的每个服务都是独立的,因此可以单独测试每个服务。此外,使用容器技术(如Docker)和持续集成/持续部署(CI/CD)工具可以更容易地部署和更新服务。
- 支持云原生应用:微服务架构与云原生应用高度契合。通过将服务拆分为更小的单元,可以更轻松地将其部署到云环境中,并利用云的弹性、可扩展性和自动化特性。
综上所述,使用Spring Cloud和微服务架构可以帮助企业更好地管理复杂性、提高可扩展性和容错性、加速迭代和交付速度,并支持云原生应用的发展。这些优势使得微服务架构成为当今许多大型企业和初创公司的首选架构。
二、好题
1、为什么要用 Redis 而不用 map做缓存?
-
内存管理:Redis是专门设计用于内存存储的数据库,它有着高效的内存管理机制。Redis会对存储的数据进行优化和压缩,以提高内存利用率。而使用普通的Map做缓存,需要手动管理内存,容易导致内存泄漏或者内存溢出的问题。
-
持久化支持:Redis支持数据的持久化,可以将内存中的数据保存到磁盘上,以便在重启后恢复数据。而Map只是内存中的数据结构,重启后数据会丢失。
-
多种数据结构支持:Redis不仅仅是一个简单的键值存储,它支持多种数据结构,如字符串、列表、哈希、集合和有序集合等。这使得Redis可以更灵活地处理不同类型的数据,而Map只能存储简单的键值对。
-
分布式支持:Redis可以通过主从复制和集群模式实现数据的分布式存储和高可用性。而使用Map做缓存,需要开发者自行实现分布式缓存机制,增加了复杂性和开发成本。
-
其他功能支持:Redis还提供了其他功能,如发布订阅、事务处理和Lua脚本执行等。这些功能使得Redis在缓存、消息队列和计数器等场景下有更广泛的应用。
2、分布式系统和微服务的区别
分布式系统和微服务是两个相关但不完全相同的概念。
分布式系统(Distributed System)是指由多个独立的计算机节点通过网络连接组成的系统,节点之间相互协作完成共同的任务。分布式系统关注整个系统的架构和通信模型,旨在将一个大型的任务或问题分解为多个子任务,在不同的计算机节点上进行并行处理。分布式系统可以采用各种技术和模型,如消息传递、远程过程调用、分布式数据库等,以实现节点之间的协作与数据交换。
而微服务(Microservices)是一种软件架构风格,将一个复杂的应用程序拆分成多个小型、自治的服务,每个服务都独立运行、部署和扩展。微服务架构强调将应用程序划分为一组松耦合的服务,每个服务都有自己的业务边界和数据存储方式,并通过轻量级的通信机制(如HTTP/REST)进行交互。每个微服务都可以独立开发、测试、部署和维护,从而提高了灵活性和可扩展性。
因此,可以说微服务是一种设计和组织分布式系统的方式,它强调将系统划分为更小的、自治的服务单元,以便更好地构建、扩展和维护。而分布式系统是一种更宏观的概念,涵盖了多个计算机节点之间的通信和协作,可以包含各种不同的架构和模型,微服务只是其中一种实现方式。
总结起来,分布式系统是一个更广泛的概念,指代由多个计算机节点组成的系统;而微服务则是一种面向服务的软件架构风格,用于设计和组织分布式系统中的服务单元。
3、@Autowired()和@Resource的区别
@Autowired 和 @Resource 都是在 Spring 中用于依赖注入的注解,它们有以下区别:
-
来源不同:
@Autowired是 Spring 提供的注解,而@Resource是由 Java EE 提供的注解。因此,@Autowired只能在 Spring 应用中使用,而@Resource可以在任何 Java EE 应用中使用。 -
注入方式不同:@Resource默认按照名称(byName)进行装配。如果没有找到与名称匹配的Bean,则按照类型(byType)进行装配。如果按名称装配失败且按类型装配时有多个匹配的Bean,则会抛出异常。当使用 @Autowired 注解时,Spring 会根据 Bean 的类型自动注入依赖。如果容器中存在多个相同类型的 Bean,Spring 无法确定要注入哪个 Bean,就会抛出异常。当类型匹配的Bean有多个时,可以结合@Qualifier注解来指定要注入的特定Bean。
-
支持的注入对象不同:
@Autowired可以注入任何 Spring 管理的 Bean,包括自定义的 Bean、框架提供的 Bean 等。而@Resource主要用于注入 Java EE 管理的资源,如 EJB、JMS 连接工厂等。
4、MyBatis 中比如 UserMapper.java 是接口,为什么没有实现类还能调用?
在 MyBatis 中,Mapper 接口(例如 UserMapper.java)没有具体的实现类,但仍然可以被调用。这是因为 MyBatis 使用了动态代理来在运行时生成接口的实现类。
在 MyBatis 的配置文件中,我们会将 Mapper 接口与对应的 SQL 语句进行映射。当我们在代码中调用 Mapper 接口的方法时,MyBatis 会通过动态代理机制生成一个代理对象,该代理对象会拦截方法调用,并根据配置文件中的映射关系执行相应的 SQL 语句。
5、super()关键字
在Java中,继承是一种允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法的机制。通过继承,子类可以重用父类的字段和方法,也可以定义自己的特有属性和方法。
super() 是Java中用来调用父类构造器的关键字。在子类的构造器中,如果没有显式调用父类的构造器,Java编译器会自动插入对父类无参构造器的调用(即super();)。如果父类没有无参构造器,或者子类构造器需要通过特定的参数初始化父类,那么就需要显式地使用super(参数列表);来调用父类的带参构造器。
三、知识库(TODO)
1.java

抽象、封装、继承、多态
反射: 运行时获取类的属性和方法。AOP,注解实现,框架拓展
String(char[])不是基础类型,是线程安全的。
集合
- Collection: List(有序), Set(无序,唯一), Queue
- Map:键值对,数组+链表,初始容量16,负载因子0.75
线程安全的集合:
Vector:只要是关键性的操作,方法前面都加了synchronized关键字,来保证线程的安全性
Hashtable:使用了synchronized关键字,所以相较于Hashmap是线程安全的。
ConcurrentHashMap:使用锁分段技术确保线性安全,是一种高效但是线程安全的集合。
Stack:栈,也是线程安全的,继承于Vector。
CopyOnWriteArrayList: 迭代器是弱一致性的, 是通过“写时复制”(Copy-On-Write)策略来保证写时线程安全的
对象引用
jvm
java虚拟机

内存
- 共享
- 堆: 内存最大,存储对象,动态数组
- 方法区: 元数据,类信息,常量,静态变量
- 私有
- 虚拟机栈:局部变量,函数调用上下文
- 本地方法栈:用于调用操作系统的底层接口和硬件资源
- 程序计数器:记录当前线程所执行的字节码指令地址
Java程序运行机制
- 编写-> 编译器编译 -> 类加载器加载内存 -> 执行引擎(解释器)运行
垃圾回收机制(GC)
四种引用类型:强弱软虚
spring
DI(依赖注入)
AOP(面向切面编程)
IOC(控制反转)容器
松耦合,MVC(Model-View-Controller)框架
数据访问,ORM,事务管理
mybatis
MyBatis 是一个基于 Java 的持久层框架,其运行原理可分为以下核心步骤:
- 配置加载阶段
解析配置文件:加载 mybatis-config.xml(数据源、事务管理器、全局设置等)。
映射文件解析:读取 Mapper.xml 文件(SQL 语句、参数映射、结果集映射规则)。
生成 SqlSessionFactory:通过 SqlSessionFactoryBuilder 构建全局唯一的工厂对象 - SQL 执行阶段
(1) 创建 SqlSession
通过 SqlSessionFactory.openSession() 创建会话(封装了数据库连接和事务)。
(2) 获取 Mapper 代理对象
MyBatis 通过 动态代理 为 Mapper 接口生成实现类。
代理逻辑:拦截接口方法,关联到映射文件中的 SQL 语句。
(3) SQL 执行流程
参数处理:将 Java 对象转换为 SQL 参数(通过 ParameterHandler)。
SQL 解析:将 #{} 或 ${} 替换为实际参数值。
执行 SQL:
由 Executor 执行(包含 Simple、Reuse、Batch 三种类型)。
可能触发一级缓存(SqlSession 级别)。
结果映射:
通过 ResultSetHandler 将 ResultSet 转换为 Java 对象 - 事务与资源释放
- 扩展机制
插件(Plugin):通过拦截器修改核心组件行为(如分页插件)。
二级缓存:跨 SqlSession 的缓存(需在映射文件中配置 )。
- 一级缓存(本地): 默认开启,sqlsession
- 二级缓存(全局): 默认关闭,命名空间
MyBatis 的核心优势是 SQL 与代码解耦 + 灵活的映射,通过动态代理和组件协作简化数据库操作。
xxl-job
分布式任务调度平台
EasyExcel
流式处理
2.mysql
开源的关系型数据库管理系统
- 引擎: 默认InnoDB,支持事务
- 索引: 一种数据结构,聚簇索引(唯一),非聚簇索引(多个)
- 事务: 读未提交,读已提交,可重复读(mysql默认),串行化
- ACID(原子性,一致性,隔离性,持久性)
- spring事务的传播行为(默认REQUIRED)
- 锁: 共享锁(读锁),排他锁(写锁),避免死锁
性能优化
分析负载(慢SQL日志)
优化数据库语句(SQL)
优化数据库结构(表结构)
调整硬件资源(扩容)
3.redis
开源的非关系型数据库
- String, List(有序), Set(不可重复), Zset(分数排序), Hash(键值对)
- 单线程,事件驱动机制
- 支持事务,不支持回滚
- 持久化机制:RDB(二进制),AOF(追加写)
- 缓存雪崩(同时失效),缓存击穿(热点失效),缓存穿透(恶意请求)
- 缓存,分布式锁,消息队列,计数器
4.linux
CPU过高的分析步骤:
1.执行“top”命令:查看所有进程占系统CPU的排序。
2.执行“top -Hp 进程号”命令:查看java进程下的所有线程占CPU的情况。
3.执行“printf "%x\n 10"命令 :后续查看线程堆栈信息展示的都是十六进制,为了找到咱们的线程堆栈信息,咱们需要把线程号转成16进制。
4.执行 “jstack 进程号 | grep 线程ID” 查找某进程下线程ID(jstack堆栈信息中的nid)=0xa的线程状态。如果““VM Thread” os_prio=0 tid=0x00007f871806e000 nid=0xa runnable”,第一个双引号圈起来的就是线程名,如果是“VM Thread”这就是虚拟机GC回收线程了
2822

被折叠的 条评论
为什么被折叠?



