JavaSE 面试题及答案集锦

一、Java基础

1. 什么是Java?

答案:Java是一种面向对象的、跨平台的高级编程语言。它具有”一次编写,到处运行”的特点,因为Java代码被编译成字节码,可以在任何安装了Java虚拟机(JVM)的平台上运行。

2. JDK、JRE、JVM三者之间的区别是什么?

答案

  • JVM (Java Virtual Machine): Java虚拟机,负责执行字节码,是Java跨平台的核心
  • JRE (Java Runtime Environment): Java运行环境,包含JVM和运行Java程序所需的核心类库
  • JDK (Java Development Kit): Java开发工具包,包含JRE和开发工具(编译器、调试器等)

3. Java中的基本数据类型有哪些?

答案:Java有8种基本数据类型:

  • 整数类型:byte(1字节)、short(2字节)、int(4字节)、long(8字节)
  • 浮点类型:float(4字节)、double(8字节)
  • 字符类型:char(2字节,Unicode字符)
  • 布尔类型:boolean(1位,只有true/false)

4. 什么是自动装箱和拆箱?

答案

  • 自动装箱:将基本数据类型自动转换为其对应的包装类对象,例如:Integer i = 10;
  • 自动拆箱:将包装类对象自动转换为对应的基本数据类型,例如:int n = i; Java 5引入了这一特性,以简化基本类型和对象之间的转换。

5. ==和equals()方法的区别是什么?

答案

  • == 比较的是两个对象的引用(内存地址)是否相同

  • equals()
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382

    方法比较的是两个对象的内容是否相同

    - Object类中的equals()默认实现就是使用==比较引用
    - 通常需要重写equals()方法来实现对象内容的比较
    - 重写equals()时通常也需要重写hashCode()方法

    ## 二、面向对象

    ### 1. 面向对象的四大特性是什么?

    **答案**:

    - **封装**:将数据和方法包装在一个单元中(类),对外隐藏内部实现细节,只提供公共访问接口
    - **继承**:子类可以继承父类的属性和方法,实现代码复用
    - **多态**:同一操作作用于不同对象,可以有不同的解释和执行结果
    - **抽象**:提取对象的共同特征,忽略与当前目标无关的细节

    ### 2. 什么是方法重载(Overload)和重写(Override)?

    **答案**:

    - **重载(Overload)**:
    - 在同一个类中,方法名相同但参数列表不同
    - 返回类型可以不同,但不能仅靠返回类型区分重载
    - 编译时多态
    - **重写(Override)**:
    - 子类重新定义父类中已有的方法
    - 方法名、参数列表、返回类型必须相同
    - 访问权限不能比父类中被重写的方法更严格
    - 不能重写被final、static修饰的方法
    - 运行时多态

    ### 3. 什么是抽象类和接口?它们有什么区别?

    **答案**:

    - **抽象类**:
    - 使用abstract关键字修饰
    - 可以包含抽象方法(没有实现)和具体方法
    - 可以有构造器、成员变量、静态方法
    - 单继承,一个类只能继承一个抽象类
    - **接口**:
    - 使用interface关键字定义
    - Java 8之前只能包含抽象方法,Java 8+可以包含默认方法(default)和静态方法,Java 9+可以包含私有方法
    - 所有字段默认是public static final
    - 一个类可以实现多个接口
    - **区别**:
    - 抽象类强调"是什么"的关系,接口强调"能做什么"的关系
    - 抽象类适合代码复用,接口适合定义规范
    - Java 8+后接口可以有默认实现,但仍有本质区别

    ### 4. 什么是内部类?有哪些类型?

    **答案**:内部类是定义在另一个类内部的类,主要有四种类型:

    - **成员内部类**:作为外部类的成员,可以访问外部类的所有成员
    - **静态内部类**:使用static修饰,只能访问外部类的静态成员
    - **局部内部类**:定义在方法内,只能在该方法内使用
    - **匿名内部类**:没有名字的内部类,通常用于实现接口或继承类并立即创建实例

    ## 三、集合框架

    ### 1. Java集合框架的主要接口有哪些?

    **答案**:Java集合框架的核心接口包括:

    - Collection

    :集合层次结构的根接口

    - **List**:有序集合,允许重复元素(如ArrayList, LinkedList, Vector)
    - **Set**:不允许重复元素(如HashSet, TreeSet, LinkedHashSet)
    - **Queue**:队列接口(如PriorityQueue, LinkedList)

    - **Map**:键值对集合,不是Collection的子接口(如HashMap, TreeMap, LinkedHashMap, Hashtable)

    ### 2. ArrayList和LinkedList的区别是什么?

    **答案**:

    - **ArrayList**:

    - 基于动态数组实现
    - 随机访问(通过索引)效率高(O(1))
    - 插入、删除元素效率低(O(n)),特别是在列表中间
    - 内存占用较小,只需存储元素和少量额外空间

    - **LinkedList**:

    - 基于双向链表实现

    - 随机访问效率低(O(n))

    > 插入、删除元素效率高(O(1)),只需修改相邻节点的引用

    - 内存占用较大,每个元素需要额外存储前后节点的引用

    ### 3. HashMap的工作原理是什么?

    **答案**:HashMap基于哈希表实现,工作原理如下:

    1. 当添加键值对时,首先调用key的hashCode()方法计算哈希值
    2. 通过哈希函数将哈希值映射到桶(bucket)数组的索引位置
    3. 如果该位置为空,直接放入;如果已有元素,则处理哈希冲突
    4. Java 8之前使用链表处理冲突,Java 8+当链表长度超过8且数组长度大于64时,链表会转换为红黑树
    5. 获取值时,同样通过key的哈希值找到桶位置,然后遍历链表/树找到对应元素

    HashMap允许一个null键和多个null值,是非线程安全的。线程安全的替代方案是ConcurrentHashMap或使用Collections.synchronizedMap()包装。

    ### 4. HashMap和Hashtable的区别是什么?

    **答案**:

    - **线程安全**:Hashtable是线程安全的(synchronized),HashMap不是
    - **null值**:HashMap允许一个null键和多个null值,Hashtable不允许null键和null值
    - **性能**:HashMap性能更好,Hashtable因为同步导致性能下降
    - **迭代器**:HashMap的迭代器是fail-fast的,Hashtable的枚举器不是
    - **继承关系**:Hashtable继承自Dictionary类,HashMap继承自AbstractMap

    ### 5. ConcurrentHashMap的工作原理是什么?

    **答案**:ConcurrentHashMap是线程安全的HashMap实现,其工作原理在不同JDK版本有所不同:

    - **JDK 1.7**:使用分段锁(Segment),将整个哈希表分成多个段,每个段有自己的锁,实现并发访问
    - **JDK 1.8+**:取消了Segment,采用Node数组+链表/红黑树结构,使用synchronized锁住单个节点和CAS操作实现线程安全,提高了并发性能

    ## 四、异常处理

    ### 1. Java中的异常体系是怎样的?

    **答案**:Java异常体系以Throwable为根类,主要分为两大类:

    - **Error**:表示JVM无法处理的严重问题,如OutOfMemoryError,应用程序通常不捕获

    - Exception

    :表示程序可以处理的异常

    - **RuntimeException**:运行时异常,非检查异常,如NullPointerException、ArrayIndexOutOfBoundsException
    - 其他Exception:检查异常,必须显式处理,如IOException、SQLException

    ### 2. throw和throws的区别是什么?

    **答案**:

    - **throw**:用于方法体内,用于抛出一个具体的异常对象,如`throw new IllegalArgumentException("Invalid argument");`
    - **throws**:用于方法声明,声明该方法可能抛出的异常类型,由调用者处理,如`public void readFile() throws IOException`

    ### 3. finally块一定会执行吗?

    **答案**:在大多数情况下,finally块会执行,但有几种情况不会:

    - 在try/catch块之前执行了System.exit()
    - JVM崩溃或被杀死
    - 执行try/catch块的线程被中断或杀死
    - 无限循环或死锁导致程序无法继续运行

    ## 五、多线程与并发

    ### 1. 创建线程的几种方式?

    **答案**:Java中创建线程主要有三种方式:

    1. 继承Thread类,重写run()方法
    2. 实现Runnable接口,实现run()方法
    3. 实现Callable接口,实现call()方法,可以返回结果和抛出异常,通常与FutureTask结合使用
    4. 使用线程池(ExecutorService)

    ### 2. 线程的生命周期有哪些状态?

    **答案**:Java线程有六种状态(在Thread.State枚举中定义):

    - **NEW**:新建状态,线程被创建但尚未启动
    - **RUNNABLE**:可运行状态,正在JVM中执行或等待CPU时间片
    - **BLOCKED**:阻塞状态,等待监视器锁
    - **WAITING**:等待状态,等待其他线程显式唤醒,如调用wait()、join()、LockSupport.park()
    - **TIMED_WAITING**:计时等待状态,有超时时间的等待,如sleep()、wait(timeout)
    - **TERMINATED**:终止状态,线程执行完毕

    ### 3. synchronized和ReentrantLock的区别?

    **答案**:

    - **synchronized**:
    - Java关键字,JVM层面实现
    - 自动释放锁,使用简单
    - 不可中断,不支持超时
    - 不支持公平锁
    - 无法知道是否获取到锁
    - **ReentrantLock**:
    - JDK API层面实现,需要显式加锁和解锁
    - 更灵活,支持公平锁/非公平锁
    - 支持可中断、超时获取锁
    - 可以尝试获取锁(tryLock)
    - 可以获取锁的状态,知道是否持有锁

    ### 4. volatile关键字的作用是什么?

    **答案**:volatile关键字主要有两个作用:

    - **可见性**:保证不同线程对该变量的修改对其他线程立即可见,每次读取都从主内存中读取
    - **禁止指令重排序**:防止编译器和处理器对volatile变量相关的指令进行重排序
    - 注意:volatile不能保证原子性,对于复合操作(如i++)不是线程安全的

    ### 5. 什么是线程池?为什么要使用线程池?

    **答案**:线程池是管理一组工作线程的池化技术。

    - **优点**:
    - 降低资源消耗:重复利用已创建的线程,减少线程创建和销毁的开销
    - 提高响应速度:任务到达时,无需等待线程创建即可执行
    - 提高线程的可管理性:可以控制最大并发数,避免资源耗尽
    - 提供丰富的功能:定时执行、定期执行、单线程执行等
    - Java提供了Executor框架,常用的线程池:
    - newFixedThreadPool:固定大小的线程池
    - newCachedThreadPool:可缓存的线程池,空闲线程会被回收
    - newSingleThreadExecutor:单线程的线程池
    - newScheduledThreadPool:支持定时及周期性任务执行的线程池

    ## 六、JVM与内存管理

    ### 1. JVM的内存结构是怎样的?

    **答案**:JVM内存主要分为以下几个区域:

    - **方法区(Method Area)**:存储类信息、常量、静态变量等,JDK 8后由元空间(Metaspace)替代永久代
    - **堆(Heap)**:存储对象实例,是垃圾回收的主要区域,分为新生代(Eden、Survivor)和老年代
    - **虚拟机栈(VM Stack)**:存储局部变量、操作数栈、方法出口等,每个线程有独立的栈
    - **本地方法栈(Native Method Stack)**:为Native方法服务
    - **程序计数器(Program Counter Register)**:记录当前线程执行的字节码指令地址

    ### 2. 什么是垃圾回收(GC)?常见的垃圾回收算法有哪些?

    **答案**:垃圾回收是JVM自动管理内存的机制,回收不再使用的对象占用的内存空间。

    - **判定对象是否存活的算法**:
    - 引用计数法
    - 可达性分析算法(主流)
    - **垃圾回收算法**:
    - 标记-清除(Mark-Sweep):先标记存活对象,再清除未标记对象,会产生内存碎片
    - 复制(Copying):将内存分为两块,每次只使用一块,用完后将存活对象复制到另一块,适用于新生代
    - 标记-整理(Mark-Compact):先标记存活对象,然后将所有存活对象向一端移动,再清理边界外的内存
    - 分代收集(Generational Collection):根据对象存活周期不同将堆分为新生代和老年代,采用不同算法

    ### 3. 强引用、软引用、弱引用、虚引用的区别?

    **答案**:

    - **强引用(StrongReference)**:最常见的引用,如`Object obj = new Object()`,只要强引用存在,垃圾回收器永远不会回收被引用的对象
    - **软引用(SoftReference)**:在内存不足时,会被回收,适合实现内存敏感的缓存
    - **弱引用(WeakReference)**:无论内存是否充足,下一次GC时都会被回收,常用于避免内存泄漏
    - **虚引用(PhantomReference)**:不影响对象生命周期,主要用于跟踪对象被回收的状态,必须与引用队列(ReferenceQueue)联合使用

    ## 七、I/O流

    ### 1. Java I/O流的分类?

    **答案**:Java I/O流可以按不同方式分类:

    - **按数据流向**:输入流(InputStream/Reader)、输出流(OutputStream/Writer)

    - **按处理数据单位**:字节流(8位,InputStream/OutputStream)、字符流(16位,Reader/Writer)

    - 按功能



    - 节点流(直接与数据源/目标连接):FileInputStream、FileReader等
    - 处理流(对其他流进行包装):BufferedReader、ObjectOutputStream等

    ### 2. BIO、NIO、AIO的区别?

    **答案**:

    - **BIO (Blocking I/O)**:同步阻塞I/O,传统的I/O模型,每个连接需要一个线程处理,适用于连接数少且固定的场景
    - **NIO (Non-blocking I/O)**:同步非阻塞I/O,JDK 1.4引入,通过Channel、Buffer、Selector实现多路复用,一个线程可以处理多个连接
    - **AIO (Asynchronous I/O)**:异步非阻塞I/O,JDK 1.7引入,由操作系统完成I/O操作后通知应用程序,适用于连接数多且连接时间长的场景

    ## 八、新特性

    ### 1. Java 8的新特性有哪些?

    **答案**:Java 8的重要新特性包括:

    - **Lambda表达式**:使代码更简洁,支持函数式编程
    - **Stream API**:提供函数式操作集合的能力,支持串行和并行操作
    - **接口的默认方法和静态方法**:接口可以有方法实现
    - **Optional类**:解决空指针异常问题
    - **新的日期时间API**:java.time包,如LocalDate、LocalTime、LocalDateTime
    - **方法引用**:通过::符号引用方法
    - **重复注解**:允许同一注解在同一元素上使用多次
    - **类型注解**:可以在任何类型使用注解

    ### 2. 什么是函数式接口?

    **答案**:函数式接口是只包含一个抽象方法的接口,可以使用@FunctionalInterface注解标识。Java 8提供了几个内置的函数式接口:

    - **Consumer**:接受一个参数,不返回结果
    - **Supplier**:不接受参数,返回一个结果
    - **Function<T, R>**:接受一个参数,返回一个结果
    - **Predicate**:接受一个参数,返回boolean
    - **BiFunction<T, U, R>**:接受两个参数,返回一个结果

    ### 3. Stream API的特点是什么?

    **答案**:Stream API具有以下特点:

    - **不可变性**:不会修改原始数据源
    - **惰性求值**:中间操作(如filter、map)不会立即执行,只有遇到终端操作(如collect、forEach)才会执行
    - **链式调用**:可以链式组合多个操作
    - **函数式风格**:使用Lambda表达式简化代码
    - **并行处理**:可以通过parallel()方法轻松实现并行处理

    ## 九、反射与注解

    ### 1. 什么是Java反射?有什么作用?

    **答案**:反射是Java在运行时检查和修改自身结构和行为的能力,主要作用:

    - 动态创建对象和调用方法
    - 访问私有成员
    - 动态代理
    - 框架开发(如Spring、Hibernate)
    - 注解处理

    反射的主要API在java.lang.reflect包中,包括Class、Field、Method、Constructor等类。

    ### 2. 什么是注解?Java内置注解有哪些?

    **答案**:注解(Annotation)是Java 5引入的元数据形式,用于为代码提供说明信息,不影响程序逻辑但可被编译器或运行时读取。

    **内置注解**:

    - @Override:标记方法覆盖
    - @Deprecated:标记过时API
    - @SuppressWarnings:抑制编译器警告
    - @FunctionalInterface:标记函数式接口(Java 8+)
    - @SafeVarargs:标记安全可变参数(Java 7+)

    ### 3. 什么是元注解?

    **答案**:元注解是用于注解其他注解的注解,Java提供了几种元注解:

    - @Retention:指定注解的保留策略(RetentionPolicy.SOURCE/CLASS/RUNTIME)
    - @Target:指定注解可应用的元素类型(ElementType.TYPE/METHOD/FIELD等)
    - @Documented:指定注解是否包含在JavaDoc中
    - @Inherited:指定注解是否可被继承
    - @Repeatable:指定注解是否可重复(Java 8+)

    ## 十、其他重要知识点

    ### 1. 什么是序列化?如何实现?

    **答案**:序列化是将对象转换为字节序列的过程,反序列化是将字节序列恢复为对象的过程。

    实现方式:

    - 实现Serializable接口,标记接口,无需实现方法
    - 实现Externalizable接口,需要实现writeExternal()和readExternal()方法,提供更精细的控制
    - 使用第三方库如JSON、Protobuf等

    注意事项:

    - 序列化版本UID(serialVersionUID)的重要性
    - transient关键字标记不序列化的字段
    - 静态变量不会被序列化

    ### 2. 单例模式的实现方式有哪些?

    **答案**:单例模式确保一个类只有一个实例,并提供全局访问点。实现方式:

    1. **饿汉式**:类加载时就创建实例,线程安全

    ```java
    public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
    return instance;
    }
    }
  1. **懒汉式(线程不安全)**:
1
2
3
4
5
6
7
8
9
10
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
  1. **懒汉式(线程安全,同步方法)**:
1
2
3
4
5
6
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
  1. **双重检查锁定(DCL)**:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
  1. 静态内部类
1
2
3
4
5
6
7
8
9
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
  1. 枚举方式
1
2
3
4
public enum Singleton {
INSTANCE;
// 其他方法
}

3. String、StringBuffer、StringBuilder的区别?

答案

  • String:不可变字符序列,每次修改都会创建新对象,适合少量字符串操作
  • StringBuffer:可变字符序列,线程安全(synchronized),适合多线程下大量字符串操作
  • StringBuilder:可变字符序列,非线程安全,性能优于StringBuffer,适合单线程下大量字符串操作

4. 什么是类加载机制?类加载的时机?

答案:类加载机制是JVM将.class文件加载到内存,并对数据进行校验、解析和初始化的过程。

类加载的时机(必须立即初始化的情况):

  • 使用new、getstatic、putstatic或invokestatic指令
  • 使用反射调用类的方法
  • 初始化一个类时,其父类未初始化
  • 虚拟机启动时,指定的主类
  • 使用JDK 7+的动态语言支持

类加载过程

  1. 加载:获取类的二进制字节流,转换为方法区的数据结构,创建Class对象
  2. 验证:确保字节码的安全性
  3. 准备:为静态变量分配内存并设置初始值
  4. 解析:将符号引用转换为直接引用
  5. 初始化:执行类构造器()方法,包括静态变量赋值和静态代码块

类加载器

  • **启动类加载器(Bootstrap ClassLoader)**:加载JVM核心类库(rt.jar)
  • **扩展类加载器(Extension ClassLoader)**:加载JRE扩展目录(lib/ext)的类
  • **应用程序类加载器(Application ClassLoader)**:加载用户类路径(ClassPath)上的类
  • 自定义类加载器:继承ClassLoader类

5. 什么是泛型?类型擦除是什么?

答案

  • 泛型:允许在定义类、接口、方法时使用类型参数,提高代码复用性和类型安全性
  • 类型擦除:Java泛型在编译后会擦除类型参数,替换为边界类型(通常是Object),这是为了兼容Java 5之前的代码。编译器会插入必要的类型转换代码,确保类型安全。

例如:

1
2
3
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);

编译后会变成:

1
2
3
List list = new ArrayList();
list.add("hello");
String s = (String)list.get(0);

十一、设计模式

1. 什么是设计模式?常见的设计模式有哪些?

答案:设计模式是解决特定问题的可重用方案,是软件设计经验的总结。常见的设计模式分为三类:

创建型模式

  • 单例模式(Singleton):确保一个类只有一个实例
  • 工厂方法(Factory Method):定义创建对象的接口,子类决定实例化哪个类
  • 抽象工厂(Abstract Factory):提供创建一系列相关对象的接口
  • 建造者模式(Builder):将复杂对象的构建与表示分离
  • 原型模式(Prototype):通过复制现有对象创建新对象

结构型模式

  • 适配器模式(Adapter):将一个类的接口转换成客户期望的另一个接口
  • 装饰器模式(Decorator):动态地给对象添加职责
  • 代理模式(Proxy):为其他对象提供代理以控制访问
  • 桥接模式(Bridge):将抽象与实现分离,使它们可以独立变化
  • 组合模式(Composite):将对象组合成树形结构表示”部分-整体”
  • 享元模式(Flyweight):通过共享技术有效支持大量细粒度对象

行为型模式

  • 策略模式(Strategy):定义一系列算法,封装每个算法,使它们可以互相替换
  • 观察者模式(Observer):定义对象间的一对多依赖,当一个对象状态改变时,所有依赖者都会收到通知
  • 责任链模式(Chain of Responsibility):将请求沿着处理链传递,直到被处理
  • 命令模式(Command):将请求封装为对象,使发出请求的对象与执行请求的对象解耦
  • 模板方法(Template Method):定义算法骨架,子类实现具体步骤
  • 迭代器模式(Iterator):提供一种方法顺序访问集合元素而不暴露内部表示
  • 状态模式(State):允许对象在内部状态改变时改变其行为

2. Spring框架中用到了哪些设计模式?

答案:Spring框架广泛使用了多种设计模式:

  • 工厂模式:BeanFactory、ApplicationContext
  • 单例模式:默认的Bean作用域
  • 代理模式:AOP实现(如JDK动态代理、CGLIB代理)
  • 模板方法模式:JdbcTemplate、RestTemplate等
  • 适配器模式:HandlerAdapter、AdvisorAdapter
  • 观察者模式:ApplicationEvent和ApplicationListener
  • 装饰器模式:BeanWrapper
  • 策略模式:ResourceLoader、Resource等

十二、性能优化与故障排查

1. 如何排查Java内存泄漏?

答案:排查Java内存泄漏的步骤和工具:

  1. 监控工具
    • jstat:监控JVM内存和GC情况
    • jmap:生成堆转储快照
    • jstack:生成线程快照
    • VisualVM:图形化监控工具
    • MAT (Memory Analyzer Tool):分析堆转储文件
  2. 排查步骤
    • 观察内存使用趋势,确定是否存在内存泄漏
    • 获取堆转储文件(heap dump)
    • 使用MAT分析内存占用最大的对象
    • 查看对象的引用链,确定为什么对象没有被回收
    • 修复代码,如解除不必要的引用、使用弱引用等
  3. 常见内存泄漏场景
    • 静态集合类持有对象引用
    • 未关闭的资源(如数据库连接、文件流)
    • 不正确的equals和hashCode实现
    • 内部类持有外部类引用
    • 缓存未设置过期策略

2. 如何优化Java应用性能?

答案:Java应用性能优化可以从多个层面进行:

代码层面

  • 避免创建不必要的对象
  • 使用合适的数据结构和算法
  • 减少同步范围,使用并发替代同步
  • 避免过度使用反射
  • 优化字符串操作(StringBuilder vs String)

JVM层面

  • 调整堆大小(-Xms, -Xmx)
  • 选择合适的垃圾回收器
  • 调整新生代和老年代比例
  • 调整线程栈大小

架构层面

  • 使用缓存减少重复计算
  • 采用异步处理提高吞吐量
  • 服务拆分和水平扩展
  • 数据库优化和读写分离

工具

  • JVM性能监控工具(jstat, jconsole, VisualVM)
  • 线程分析工具(jstack, Thread Dump Analyzer)
  • CPU和内存分析工具(YourKit, JProfiler)
  • APM工具(Pinpoint, SkyWalking)

以上是JavaSE相关的主要面试题及答案,涵盖了从基础到高级的多个知识点。实际面试中可能会根据应聘者的经验和职位要求有选择地提问,建议根据自身情况重点准备。