1. Java类加载,如何打破双亲委派?
-
创建自定义类加载器 :创建一个新的类加载器类,继承自
ClassLoader
或其任何已有的子类。 -
重写
loadClass
方法 :在你的自定义类加载器中重写loadClass
方法。 -
加载类数据 :在
loadClass
方法中,首先尝试使用当前类加载器来加载类数据。你可以从文件系统、网络或其他源获取类的二进制数据。 -
定义类 :如果在步骤 3 中成功加载了类的数据,使用
defineClass
方法将二进制数据转换为Class<?>
对象。 -
委派给父类加载器:如果步骤 3 和 4 失败,作为回退机制,将加载任务委派给父类加载器。
-
测试自定义类加载器:创建一个测试程序,使用你的自定义类加载器来加载某个类,验证它是否可以正确地工作
2. 线程池使用场景,参数
可以复用线程,减少线程创建和销毁的开销。应用场景
- Web 服务器:用于处理高并发的 HTTP 请求。
- 大数据处理:用于并行处理大量的数据,例如 MapReduce。
- 实时应用:在实时应用中用于处理多用户的并行请求或操作。
- 文件上传和下载:在文件上传和下载服务中,可以使用线程池来并行处理多个文件传输任务。
- 后台任务执行:用于定时或周期性的执行后台任务,例如日志清理,数据同步等。
线程池参数:
-
corePoolSize:
- 描述:线程池的基本大小。
- 作用:当线程池中的线程数少于 corePoolSize 时,即使线程是空闲的,也不会被销毁。
-
maximumPoolSize:
- 描述:线程池的最大线程数。
- 作用:当线程池中的线程数超过 corePoolSize 并且队列已满时,线程池可以扩展到 maximumPoolSize。
-
workQueue:
- 描述:用于存放待处理的任务的队列。
- 作用:当线程池中的线程数达到 corePoolSize 时,新的任务将被放在队列中等待。
-
threadFactory:
- 描述:用于创建新线程的工厂。
- 作用:可以用来设置线程的名称,优先级等。
-
handler:
- 描述:拒绝策略,当线程池和队列都满了之后,用于处理新来的任务。
- 常见策略:
ThreadPoolExecutor.AbortPolicy
:直接抛出异常。ThreadPoolExecutor.CallerRunsPolicy
:将任务回退给调用者。ThreadPoolExecutor.DiscardPolicy
:忽略新来的任务。ThreadPoolExecutor.DiscardOldestPolicy
:抛弃队列中最老的任务。
-
keepAliveTime:
- 描述:空闲线程的保持时间。
- 作用:当线程池中的线程数量超过 corePoolSize 时,多余的空闲线程将在等待新任务的时候最多保持 keepAliveTime 时间。
-
timeUnit:
- 描述:keepAliveTime 的时间单位
3. 线程池异常处理
- 使用 Future :如果你通过
submit
方法提交任务(而不是execute
),你会得到一个Future
对象,可以用它来捕获异常 - 在任务内部捕获异常:你可以在每个任务内部捕获和处理异常
- 使用 uncaughtExceptionHandler :你可以为线程池中的线程设置一个
UncaughtExceptionHandler
来捕获未捕获的异常
有哪些异常:
-
RejectedExecutionException:
- 描述:当线程池已关闭或线程池和工作队列都已满时,尝试提交新任务将抛出此异常。
- 解决方案:可以考虑调整线程池的大小或队列长度,或者更改拒绝策略来处理这种情况。
-
OutOfMemoryError:
- 描述:如果线程池中的线程数或队列大小配置得太大,或者任务创建了太多大对象,你可能会遇到内存溢出错误。
- 解决方案:优化代码以减少内存使用,或增加JVM的最大堆大小。
-
NullPointerException:
- 描述:当尝试提交的任务为null时,将抛出此异常。
- 解决方案:确保提交到线程池的任务不是null。
-
IllegalStateException:
- 描述:当线程池处于不正确的状态时(例如,尝试多次关闭线程池),可能会抛出此异常。
- 解决方案:检查代码以确保线程池的正确使用。
-
RuntimeException 或 ExecutionException:
- 描述 :当任务抛出运行时异常时,如果你使用了
Future.get()
方法来获取结果,将包装为ExecutionException
抛出。 - 解决方案:捕获这些异常并适当处理
- 描述 :当任务抛出运行时异常时,如果你使用了
4. Redis 基本数据结构
-
**字符串(String):**可以存储文本或二进制数据。
-
**哈希表(Hashes):**存储字段和字段值的映射,适合用来存储对象。
-
**列表(Lists):**有序的字符串列表,常用于实现队列。
-
**集合(Sets):**无序的字符串集合,可以快速查找、添加、删除元素。
-
**有序集合(Sorted Sets):**与集合类似,但每个元素都会关联一个分数,元素间有序。
-
**位图(Bitmaps):**通过位操作来存储和查询数据。
-
**超日志(HyperLogLogs):**用于统计唯一值的数量,但会有一定的误差。
-
**流(Streams):**用于实现消息队列的功能,支持多个消费者。
5. 如何保证数据库缓存一致性
- 事务性:尽可能将缓存操作和数据库操作包含在一个事务中,这样可以确保要么都成功,要么都失败。
- **使用队列:**使用消息队列来缓冲数据库的写操作,并按顺序处理它们,以保证缓存和数据库的一致性。
- **一致性哈希:**在分布式缓存场景中,可以使用一致性哈希来保证数据的一致性。
- **数据库触发器:**利用数据库触发器,在数据变更时自动更新或清除缓存。
6. mysql底层数据结构,为什么选用B+树?
-
磁盘I/O效率:
- 数据块的读写:B+树将数据存储在叶子节点,而非叶子节点仅存储键值和指向孩子节点的指针。这意味着磁盘I/O操作通常能够一次读取更多的数据。
- 顺序扫描优化:B+树的叶子节点之间通过指针相连,这为范围查询和顺序扫描提供了高效的路径,减少了磁盘I/O次数。
-
空间使用效率:
- 空间局部性:B+树的层级更少,更多的节点可以被加载到内存中,这提高了空间局部性和缓存命中率。
- 节约空间:由于B+树的内部节点不存储数据,这可以更有效地利用空间来存储索引信息,从而节约存储空间。
-
查询效率:
- 查询时间可预测:B+树保证了所有叶子节点都在同一层,这使得所有的查找操作路径长度相等,提供了可预测的查询时间。
- 范围查询优势:通过顺序访问叶子节点,可以更快地完成范围查询。
-
插入和删除操作效率:
- 高效的插入和删除:B+树提供了高效的插入和删除操作,因为它只需在叶子节点进行修改,而不需要频繁地重新平衡树结构。
-
支持事务和并发控制:
- 易于实现锁机制:B+树结构便于实现各种锁机制(如行锁),这对于支持事务和并发控制是非常重要的
7.幻读了解吗?
幻读(Phantom Reads)是数据库事务管理中的一个概念,是一种并发控制现象。在一个事务的执行过程中,如果由于另一个事务的插入或删除操作,导致第一个事务重新执行一个相同的查询时返回的结果集不同,那么就出现了幻读
幻读通常可以通过使用更高级别的事务隔离来避免,比如可串行化(Serializable)隔离级别。在此级别,事务是完全隔离的,意味着在一个事务开始到结束的过程中,不会看到其他事务所做的任何修改。
8.索引什么情况下索引会失效?
-
不使用索引列的前缀:
- 如果查询条件没有包括索引的前缀列(即索引中的第一列),索引可能不会被使用。
-
使用函数或表达式:
- 当在查询条件中对索引列使用函数、表达式、类型转换等操作时,索引可能不会被使用。例如,
WHERE UPPER(column) = 'VALUE'
。
- 当在查询条件中对索引列使用函数、表达式、类型转换等操作时,索引可能不会被使用。例如,
-
使用通配符前缀:
- 当查询条件中使用通配符前缀(如
%value
)时,索引可能不会被使用,因为通配符前缀无法利用索引的有序性。
- 当查询条件中使用通配符前缀(如
-
对列进行计算:
- 如果查询中对索引列进行计算或运算,索引可能会失效。例如,
WHERE column * 2 = 10
。
- 如果查询中对索引列进行计算或运算,索引可能会失效。例如,
-
不等于(!=)操作符:
- 在某些情况下,使用不等于操作符(
!=
或<>
)可能导致索引失效,因为它不适用于某些类型的索引优化。
- 在某些情况下,使用不等于操作符(
-
OR条件:
- 使用OR条件连接多个不同的索引列时,优化器可能无法有效地使用索引
9.线程安全,如何实现?
-
互斥锁:
- 使用互斥锁(Mutex)或信号量来保护共享资源,确保一次只有一个线程可以访问它。
- Java中的
synchronized
关键字和ReentrantLock
类可以用来创建互斥锁。
-
原子操作:
- 利用原子操作来执行多个步骤的操作,确保它们在多线程环境下是不可中断的。
- 在Java中,可以使用
java.util.concurrent.atomic
包中的原子类(如AtomicInteger
、AtomicLong
)来实现原子操作。
-
线程安全的数据结构:
- 使用线程安全的数据结构,如
ConcurrentHashMap
、CopyOnWriteArrayList
等,来代替传统的集合类。 - 这些数据结构内部使用了锁或其他机制来确保线程安全。
- 使用线程安全的数据结构,如
-
不可变对象:
- 设计不可变对象,这些对象在创建后不能被修改,因此可以被多个线程安全地共享。
- 使用
final
关键字来标记字段,确保它们不可变。
-
线程局部存储:
- 使用线程局部存储(Thread-Local Storage,TLS)来为每个线程提供独立的变量副本,从而避免竞争条件。
- 在Java中,可以使用
ThreadLocal
类来管理线程局部变量
10. 负载均衡算法?
(1)RandomLoadBalance:根据权重随机选择(对加权随机算法的实现)。这是Dubbo默认采用的一种负载均衡策略。
(2)LeastActiveLoadBalance:最小活跃数负载均衡。
Dubbo 就认为谁的活跃数越少,谁的处理速度就越快,性能也越好,这样的话,我就优先把请求给活跃数少的服务提供者处理。
(3)ConsistentHashLoadBalance:一致性Hash负载均衡策略。
ConsistentHashLoadBalance 中没有权重的概念,具体是哪个服务提供者处理请求是由你的请求的参数决定的,也就是说相同参数的请求总是发到同一个服务提供者。另外,Dubbo 为了避免数据倾斜问题(节点不够分散,大量请求落到同一节点),还引入了虚拟节点的概念。通过虚拟节点可以让节点更加分散,有效均衡各个节点的请求量。
(4)RoundRobinLoadBalance:加权轮询负载均衡。
轮询就是把请求依次分配给每个服务提供者。加权轮询就是在轮询的基础上,让更多的请求落到权重更大的服务提供者上。
11. java设计模式了解过哪些?
12. spring循环依赖,为什么需要三级缓存,两级缓存不行吗
Spring的三级缓存是为了解决循环依赖问题而引入的。在Spring容器中,如果两个Bean相互依赖,那么在创建Bean时就会出现循环依赖问题。为了解决这个问题,Spring使用了三级缓存1。
三级缓存包括:
- singletonObjects: 一级缓存,存储单例对象,Bean已经实例化并初始化完成。
- earlySingletonObjects: 二级缓存,存储singletonObject,这个Bean实例化了,但还没有初始化。
- singletonFactories: 三级缓存,存储singletonFactory。
当一个Bean被创建时,Spring会首先从一级缓存中获取Bean实例。如果一级缓存中不存在该Bean实例,则Spring会从二级缓存中获取该Bean实例。如果二级缓存中也不存在该Bean实例,则Spring会从三级缓存中获取该Bean实例的工厂对象,并调用工厂方法创建该Bean实例。
-
tcp粘包和拆包
-
CP和AP的区别
-
Java线程通信方式?
-
CMS和C1区别?
17. JVM调优的手段?
- 调整JVM参数:例如,可以设置堆大小、新生代比例、GC算法等参数,以及启用垃圾回收器的Ergonomics机制。
- 分析和定位当前系统的瓶颈:对于JVM的核心指标,我们的关注点和常用工具如下:
CPU指标
JVM内存指标
JVM GC指标
- 选择合适的GC收集器:串行收集器、并行收集器、并发收集器。
- 架构调优和代码调优:架构调优是对系统影响最大的。性能调优基本上按照以下步骤进行:明确优化目标、发现性能瓶颈、性能调优、通过监控及数据统计工具获得数据、确认是否达到目标
- 新生代频繁Minor GC原因,解决办法?
- 新生代空间设置过大:如果新生代空间设置过大,会导致每次Minor GC的时间变长,从而影响系统的性能。因此,需要根据应用程序的实际情况来调整新生代的大小。
- 对象引用链较长:如果对象引用链较长,进行可达性分析时间较长,也会导致Minor GC的频繁发生。因此,需要尽量减少使用全局变量和大对象。
- 新生代survivor区设置的比较小:如果新生代survivor区设置的比较小,清理后剩余的对象不能装进去需要移动到老年代,造成移动开销。因此,需要根据应用程序的实际情况来调整survivor区的大小。
19. 频繁Full GC(老年代GC)的原因?解决办法(参考17)?
- System.gc()方法的调用:虽然只是建议而非一定,但很多情况下它会触发Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+DisableExplicitGC来禁止RMI调用System.gc。
- 老年代空间不足:老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象。为避免此种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
- 永生区空间不足:当系统中要加载的类、反射的类和调用的方法较多时,永生区可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。
- CMS GC时出现promotion failed和concurrent mode failure:对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。措施为:增大survivor space、老年代空间或调低触发并发GC的比率。
- 统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间:这是一个较为复杂的触发情况。Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。
-
Java内存区域,内存模型?
-
Java运行一个程序的过程?
-
静态变量在什么阶段分配内存?
-
TCP和UDP的原理区别
- TCP是面向连接的,UDP是无连接的
- TCP是可靠的,UDP是不可靠的
- TCP是面向字节流的,UDP是面向数据报文的
- TCP只支持点对点通信,UDP支持一对一,一对多,多对多
- TCP报文首部20个字节,UDP首部8个字节
- TCP有拥塞控制机制,UDP没有
- TCP协议下双方发送接受缓冲区都有,UDP并无实际意义上的发送缓冲区,但是存在接受缓冲区
24. 平时用SpringBoot经常会用注解,注解开发是怎么实现的?你提到了AOP,AOP和OOP是什么关系呢?
- Spring Boot中的注解开发是通过Java的反射机制实现的。
- AOP与OOP(Object Oriented Programming)是面向不同领域的两种设计思想。OOP针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果
25. Java默认的垃圾收集器是哪个?GC的过程是由谁来调度的?GC线程是谁启动的?
- Java默认的垃圾收集器是 G1 垃圾收集器
- Java的垃圾收集过程是由 JVM 来调度的。JVM会在程序运行时,根据内存使用情况自动触发垃圾回收
- GC线程是由 JVM 启动的。在JVM启动时,会启动一个或多个GC线程,用于执行垃圾回收
- 平时使用MySQL增加索引可以提高查询效率,如何理解?
27. 查询第50条到100条记录
在SQL中,可以使用 LIMIT
和 OFFSET
子句来查询指定范围内的记录。假设你要查询第50条到100条记录,可以使用以下语句:
SELECT * FROM table_name LIMIT 50, 50;
28.InnoDB的索引类型
29. 主键索引、唯一索引和联合索引
- 在MySQL中,主键索引是一种特殊的唯一索引,用于标识一条记录。主键索引的值必须唯一,且不能为NULL。如果表中没有定义主键,MySQL会自动创建一个名为PRIMARY的主键索引
- 唯一索引是指索引列的值必须唯一,但是可以为NULL。如果一个表中有多个唯一索引,那么每个唯一索引都可以保证索引列的值唯一.
- 联合索引是指将多个列组合在一起创建的索引。联合索引可以包含多个列,但是要注意,联合索引的顺序非常重要。如果查询条件中只涉及到联合索引中的第一个或前几个列,则该查询可以使用联合索引进行优化;否则,该查询无法使用联合索引进行优化
30. 一张表用a,b,c三个字段作为联合索引,一条SQL命中了a和b是否会走索引
在MySQL中,如果一张表使用a,b,c三个字段作为联合索引,一条SQL命中了a和b,那么这条SQL会走索引。但是,如果SQL命中了a和c或者b和c,则不会走索引
31. mysql的事务特性、隔离级别
- 读未提交 (READ UNCOMMITTED)
在这个隔离级别中,一个事务可以读取另一个未提交事务的修改。
问题:这种级别可能会导致"脏读",即一个事务读取到另一个事务还未提交的数据,如果那个事务最终回滚,那么读取的数据就是无效的。
这是最低的隔离级别,锁定的需求最少。
- 读提交 (READ COMMITTED)
在这个隔离级别中,一个事务只能读取其他事务已经提交的修改。
问题:尽管可以避免"脏读",但是可能会发生"不可重复读",即在同一个事务中,后续的查询可能会看到前一个查询中未看到的行或列的不同值。
这是大多数数据库系统的默认隔离级别。
- 可重复读 (REPEATABLE READ)
在这个隔离级别中,其他事务不能在事务执行期间插入新行,从而防止了"幻读"。
问题:该隔离级别可以避免"脏读"和"不可重复读",但是可能会导致"幻读"。"幻读"是指当某个事务在读取某个范围内的所有行时,另一个事务又在该范围内插入了新行,导致前一个事务再次读取时看到了额外的、早先不存在的行。
在MySQL的InnoDB存储引擎中,这是默认的隔离级别。
- 串行化 (SERIALIZABLE)
这是最严格的隔离级别。当一个事务选择了该隔离级别后,其他事务就不能并发执行,它们必须等待该事务完成。
问题:这种级别可以避免"脏读"、"不可重复读"和"幻读",但是性能开销最大,因为事务之间完全是串行执行的。
-
mysql的锁机制,悲观锁和乐观锁的区别
-
讲一下collection和map
-
list和set有什么区别?set里面可以有null值吗?list是不是可以有多个null值?
-
hashmap和hashtable有什么区别?hashmap的底层原理?如何解决hash冲突?
36. concurrentHashMap和hashmap有什么区别?
- 线程安全性:HashMap是线程不安全的,当出现多线程操作时,会出现安全隐患;而ConcurrentHashMap是线程安全的。
- 并发操作:HashMap不支持并发操作,没有同步方法;ConcurrentHashMap支持并发操作。
- 锁机制:ConcurrentHashMap采用锁分段技术,将整个Hash桶进行了分段segment,每个小的片段segment上面都有锁存在,这样只要保证每个Segment是线程安全的,也就实现了全局的线程安全。而HashMap没有采用锁分段技术
- IOC和AOP的概念、IOC的实现机制即依赖注入的方式
38.spring bean 的生命周期
Bean 的生命周期指的是 Bean 在 Spring(IoC)中从创建到销毁的整个过程。Bean 的生命周期主要包含以下 5 个流程:
(1)实例化:为 Bean 分配内存空间;
(2)设置属性:将当前类依赖的 Bean 属性,进行注入和装配;
(3)初始化:
执行各种通知。
执行初始化的前置方法。
执行初始化方法。
执行初始化的后置方法。
(4)使用 Bean:在程序中使用 Bean 对象;
(5)销毁 Bean:将 Bean 对象进行销毁操作。
39. springboot配置文件的加载顺序?yml和properties
在 Spring Boot 中,当同时存在 .properties 和 .yml 配置文件时,Spring Boot 会优先使用 yaml 格式的配置文件,而 properties 格式的配置文件时使用
在加载配置文件时,Spring Boot 会按照以下顺序加载:
- 优先加载 application.yml 或者 application.properties 文件。
- 其次加载 classpath:/config/application.yml 或者 classpath:/config/application.properties 文件。
- 最后加载 classpath:/application.yml 或者 classpath:/application.properties 文件。