1、int占用几个字节

int 在 Java 中占用 4 个字节,即 32 位。

附上所有基本数据类型的:

数据类型 位数 字节数
byte 8 1
short 16 2
int 32 4
long 64 8
float 32 4
double 64 8
char 16 2
boolean 1 1 (通常)

2、两个 Integer对象,赋值150,是否相等

如果使用 == 比较两个 Integer 对象(如 Integer a = 150; Integer b = 150; a == b),结果通常是 false,因为 Integer 对象在 -128 ~ 127 之间会缓存(享元模式),但 150 不在缓存范围内,会创建新对象。

如果使用 equals() 方法或先拆箱再比较(如 a.intValue() == b.intValue() 或 a == b.intValue()),再比较的是值,结果为 true。

3、对HashMap底层+扩容的了解

底层:数组 + 链表 + 红黑树(JDK 1.8 引入红黑树优化链表过长问题)。

结构:数组中的每个元素称为一个 “桶(bucket)”,每个桶可以存储一个链表或红黑树节点。

hash计算:通过 key 的 hashCode 计算数组下标。

扩容:默认初始容量是 16,负载因子 0.75,当元素数量超过 容量 * 负载因子 时触发扩容,扩容为原来的 2 倍。

重新哈希(rehash):JDK1.8 优化了扩容时的 rehash 过程,不需要重新计算 hash,而是通过高位判断元素应该留在原位置还是移动到 “原位置 + 旧容量” 的位置。

4、对ConcurrentHashMap的了解,并于Hashmap比较

ConcurrentHashMap与HashMap的比较

数据结构差异 HashMap基于数组+链表/红黑树实现,非线程安全。ConcurrentHashMap在JDK8后采用相同结构,但通过CAS和synchronized实现线程安全。

线程安全机制 HashMap不保证线程安全,多线程操作可能导致数据不一致。ConcurrentHashMap通过分段锁(JDK7)或桶级别锁(JDK8+)实现高效并发:

  • JDK7使用Segment分段锁,每个Segment独立加锁

  • JDK8改用Node+CAS+synchronized,锁粒度更细

性能表现 单线程环境下HashMap性能略优。高并发场景下:

  • ConcurrentHashMap的读操作完全无锁

  • 写操作仅锁定单个桶或Node,并发度更高

  • 扩容时支持多线程协同迁移数据

迭代器行为 HashMap的迭代器是快速失败(fail-fast)的。ConcurrentHashMap的迭代器反映创建时的状态,不会抛出ConcurrentModificationException。

Null值处理 HashMap允许null键和null值。ConcurrentHashMap禁止null键值,因null在并发场景中易引发歧义。

典型使用场景

  • 需要线程安全的Map实现

  • 高并发读写场景

  • 需要保证数据一致性但不愿使用全局锁

5、项目里自己实现一个String编译能通过吗?(如何实现继承String)

可以编译通过,但是不能起名就叫 String,因为它是 java.lang 包下的 final 类,无法继承或替换它。如果定义一个类叫比如 MyString 或 MyCustomString,那当然可以编译运行。

在 Java 中,String 类是 final 类,无法被继承。这是 Java 语言设计中的一个限制,目的是保证 String 的不可变性和安全性。如果需要扩展 String 的功能,可以通过以下方法实现:

使用组合而非继承

创建一个新类,将 String 作为其成员变量,并通过方法包装或扩展功能。这种方式更灵活且符合设计原则。

public class CustomString {
    private String value;

    public CustomString(String value) {
        this.value = value;
    }

    // 扩展方法示例
    public String reverse() {
        return new StringBuilder(value).reverse().toString();
    }

    // 直接调用原 String 方法
    public int length() {
        return value.length();
    }
}

使用工具类

通过静态工具类提供扩展方法,避免直接继承 String

public class StringUtils {
    public static String reverse(String str) {
        return new StringBuilder(str).reverse().toString();
    }
}

使用 Java 8 的默认方法(接口)

如果功能与接口相关,可以通过接口的默认方法实现类似扩展。

public interface StringOperations {
    default String reverse(String str) {
        return new StringBuilder(str).reverse().toString();
    }
}

public class CustomString implements StringOperations {
    private String value;

    public CustomString(String value) {
        this.value = value;
    }
}

6、类加载器最高层的那个加载器叫什么

Bootstrap ClassLoader(启动类加载器 / 根加载器)

它是用 C/C++ 实现的,不是 Java 类,负责加载 JRE 核心类库,比如 rt.jar 中的类。

在 Java 中可以通过 ClassLoader.getSystemClassLoader().getParent().getParent() 获取,但通常返回 null(因为是 C 实现的)

7、怎么破坏双亲委派模型

双亲委派模型是Java类加载机制的核心原则:类加载请求优先由父加载器处理,若父加载器无法完成,子加载器才会尝试加载。这种机制保证了核心类库的安全性,避免用户代码覆盖核心类。

1. 自定义类加载器并重写loadClass方法
默认的loadClass方法实现了双亲委派逻辑。重写该方法可直接跳过父加载器,例如:

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // 自定义加载逻辑,例如优先从特定路径加载
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        c = findClass(name); // 直接自行加载
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}

2. 使用线程上下文类加载器(Thread Context ClassLoader)
在SPI(服务提供者接口)场景中,核心库(如JDBC)需加载厂商实现类,但核心类由Bootstrap加载器加载,无法直接访问应用类。通过Thread.currentThread().setContextClassLoader()设置线程级类加载器,可绕过双亲委派。

3. OSGi模块化系统的类加载机制
OSGi每个模块(Bundle)拥有独立的类加载器,模块间通过依赖关系动态委托加载,而非严格的父->子顺序。这种网状结构打破了线性委派模型。

4. 动态代码热替换场景
如热部署工具(JRebel)需重新加载修改后的类,需绕过双亲委派直接替换已加载的类。实现方式通常结合自定义类加载器和Instrumentation API。

8、System.gc()之后强弱引用的对象会被回收吗?

不会立即回收,System.gc() 只是建议 JVM 执行垃圾回收,不保证一定执行。

强引用:只要存在强引用,对象 不会被回收。

软引用(SoftReference):内存不足时会被回收。

弱引用(WeakReference):只要发生 GC,就会被回收。

虚引用(PhantomReference):无法通过它访问对象,主要用于跟踪对象被回收的状态。

9、System.gc()之后一定会发生gc吗?

不一定。System.gc() 只是一个 建议,JVM 可以忽略这个请求。是否执行 GC 取决于具体的垃圾回收器自身算法和当前内存状态。

10、CMS垃圾回收器回收的是老年代还是新生代的,用的是哪种垃圾回收算法

CMS垃圾回收器的回收对象和算法

CMS(Concurrent Mark-Sweep)垃圾回收器主要用于回收老年代的垃圾。其设计目标是减少老年代垃圾回收时的停顿时间,通过并发标记和清理实现低延迟。

使用的垃圾回收算法

CMS采用标记-清除算法(Mark-Sweep)。具体流程包括以下阶段:

  • 初始标记(Initial Mark):短暂停顿,标记老年代中直接可达的对象。

  • 并发标记(Concurrent Mark):与用户线程并发执行,标记所有可达对象。

  • 重新标记(Remark):短暂停顿,修正并发标记期间因用户线程运行导致的标记变动。

  • 并发清除(Concurrent Sweep):与用户线程并发执行,清理未标记的垃圾对象。

与新生代的关系虽然CMS主要处理老年代,但新生代的垃圾回收通常由其他收集器(如ParNew)配合完成。CMS需要与新生代收集器协同工作,因为老年代回收可能触发跨代引用问题。

11、创建线程有哪些方式

1、继承Thread类

通过继承Thread类并重写run()方法创建线程。这种方式简单直接,适合简单任务,但Java不支持多重继承,因此扩展性受限。

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread running by extending Thread class");
    }
}

// 使用方式
MyThread thread = new MyThread();
thread.start();

2、实现Runnable接口

实现Runnable接口并实现run()方法,然后将实例传递给Thread构造函数。这种方式更灵活,允许实现多重继承,适合需要多线程共享资源的场景。

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread running by implementing Runnable");
    }
}

// 使用方式
Thread thread = new Thread(new MyRunnable());
thread.start();

3、使用Callable和Future

通过实现Callable接口创建线程,可以返回结果或抛出异常。配合FutureTask或线程池使用,适合需要获取异步任务结果的场景。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Result from Callable";
    }
}

// 使用方式
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get()); // 获取结果

4、使用线程池(ExecutorService)

通过Executors工具类创建线程池,提交RunnableCallable任务。适合管理大量线程的生命周期和资源分配。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> {
    System.out.println("Task executed by thread pool");
});
executor.shutdown();

5、Lambda表达式(Java 8+)

利用Lambda简化RunnableCallable的实现,代码更简洁,适合函数式编程风格。

// Runnable简化
new Thread(() -> System.out.println("Lambda thread")).start();

// Callable简化
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> "Lambda Callable result");

12、线程池核心参数

corePoolSize(核心线程数)

线程池中保持存活的最小线程数量,即使这些线程处于空闲状态。当新任务提交时,若当前线程数小于corePoolSize,线程池会创建新线程执行任务。核心线程通常不会被回收,除非设置allowCoreThreadTimeOuttrue

maximumPoolSize(最大线程数)

线程池允许创建的最大线程数量。当任务队列已满且当前线程数小于maximumPoolSize时,线程池会创建新线程处理任务。超过corePoolSize的线程在空闲时会根据keepAliveTime被回收。

keepAliveTime(非核心线程空闲存活时间)

当线程数超过corePoolSize时,多余的空闲线程在终止前等待新任务的最长时间。时间单位由unit参数指定。

unit(时间单位)

keepAliveTime的时间单位,常用值为TimeUnit.SECONDSTimeUnit.MILLISECONDS等。

workQueue(任务队列)

用于保存待执行任务的阻塞队列,常见实现包括:

  • LinkedBlockingQueue:无界队列(默认容量为Integer.MAX_VALUE),可能导致资源耗尽。

  • ArrayBlockingQueue:有界队列,需指定固定容量。

  • SynchronousQueue:不存储元素的队列,每个插入操作需等待另一个线程的移除操作。

threadFactory(线程工厂)

可选参数,用于自定义线程创建过程(如线程命名、优先级等)。默认使用Executors.defaultThreadFactory()

handler(拒绝策略)

当线程池和队列已满时,处理新任务的策略。常见策略:

  • AbortPolicy(默认):直接抛出异常。

  • CallerRunsPolicy:由提交任务的线程直接执行任务。

  • DiscardPolicy:静默丢弃任务。

  • DiscardOldestPolicy:丢弃队列中最旧的任务并重新提交新任务。

13、线程池工作流程

  1. 提交任务,首先判断核心线程数corePoolSize是否已满,未满则创建新线程执行。

  2. 如果核心线程数corePoolSize已达到,任务进入工作队列workQueue

  3. 如果工作队列workQueue也满了,判断当前线程数是否小于最大线程数maximumPoolSize,小于则创建新线程执行。

  4. 如果线程数已达最大值,触发拒绝策略

14、ThreadLocal 内存泄漏如何解决

原因:ThreadLocalMap 中的 key 是弱引用(ThreadLocal 对象),但 value 是强引用。

如果 ThreadLocal 对象被回收,但线程依然存活(比如线程池线程),会导致 value 无法访问却无法被回收 → 内存泄漏。

解决方法:使用完 ThreadLocal 后,调用 remove() 方法手动清除。

15、Synchronized修饰静态方法和非静态方法有什么区别

锁的对象不同

  • 静态方法:锁的是类的Class对象(如ClassName.class),属于类级别锁。所有线程共享同一把锁,即使多个实例也互斥。

  • 非静态方法:锁的是当前实例对象(this),不同实例的锁互不影响。

作用范围差异

  • 静态方法:影响所有调用该方法的线程,无论实例是否相同。

  • 非静态方法:仅对同一个实例的调用线程生效,不同实例间不会阻塞。

并发场景示例

// 静态方法示例
public static synchronized void staticSyncMethod() {
    // 临界区代码
}

// 非静态方法示例
public synchronized void instanceSyncMethod() {
    // 临界区代码
}

实际应用场景

  • 静态方法:适合需要全局同步的场景,如操作静态变量或类级别资源。

  • 非静态方法:适合保护实例变量的线程安全,如对象的内部状态修改。

性能影响

  • 类级别锁可能导致更高的竞争概率,需谨慎设计。

  • 实例级别锁粒度更细,通常并发度更高。

补充说明

  • 两者可同时存在,不会互斥(因锁对象不同)。

  • 静态同步块使用ClassName.class作为锁对象时,与静态方法等效。

16、事务隔离级别

  • 脏读:读取未提交的无效数据。
  • 不可重复读:同一数据被其他事务修改后再次读取结果不同。
  • 幻读:查询范围数据因其他事务增删导致行数变化。

1、读未提交(Read Uncommitted):可能读到未提交数据,脏读、不可重复读、幻读都可能发生。

2、读已提交(Read Committed):只能读到已提交数据,避免脏读,但仍有不可重复读和幻读。

3、可重复读(Repeatable Read)【MySQL 默认】:多次读取同一数据结果一致,避免脏读和不可重复读,但仍有幻读(MySQL 通过 MVCC 解决大部分情况)。

4、串行化(Serializable):最高隔离级别,完全串行执行,避免所有问题,但性能最差。

17、可重复读那一层怎么解决幻读的(间隙锁方案)

 MVCC(多版本并发控制) + 间隙锁(Gap Lock)+ 临键锁(Next-Key Lock)

间隙锁的作用

间隙锁锁定的是索引记录之间的间隙,而非具体的数据行。当执行范围查询时,InnoDB会对查询范围内的间隙加锁,阻止其他事务在该间隙中插入新数据。例如:

SELECT * FROM t WHERE id BETWEEN 10 AND 20 FOR UPDATE;

此查询会对id=10id=20之间的所有间隙(如10-11、11-12等)加锁,其他事务无法在此范围内插入id=15的新数据,从而避免幻读。

临键锁的作用

临键锁是间隙锁与记录锁的组合,锁定的是索引记录本身及其之前的间隙。例如:

SELECT * FROM t WHERE id > 10 AND id < 20 FOR UPDATE;

临键锁会锁定id=10之后的间隙到id=20之前的间隙,以及范围内的所有现有记录。其他事务既不能修改这些记录,也不能在锁定的间隙中插入新数据。

解决幻读的关键点

  • 防止新数据插入:间隙锁和临键锁阻止其他事务在已锁定的范围内插入数据,从而确保同一查询多次执行时不会出现新行。

  • 覆盖所有访问路径:锁的范围不仅包括查询条件命中的记录,还包含索引的间隙,确保即使通过其他索引路径也无法插入冲突数据。

  • 与MVCC协同:可重复读隔离级别下,MVCC(多版本并发控制)保证读取的一致性视图,而间隙锁和临键锁保证写入操作的隔离性,二者结合彻底消除幻读。

18、联合索引(A,B,C) A= xxx and C>= xxx问会不会走索引

会部分走索引,即只会用到 A 字段索引,C 不会走索引。因为 最左前缀原则:必须从最左边开始连续匹配。这里用了 A,符合;但 B 没有指定,直接跳到 C,C 不能有效利用索引。

19、A的查询改成 IN xxx还会不会走索引

会走索引,IN 查询在 A 上仍然符合最左匹配原则,只要 A 在最左侧,IN 查询可以使用索引。

20、UNION & UNION ALL的区别

UNION:合并多个 SELECT 结果集,并去重(排序去重,性能较差)。

UNION ALL:合并多个结果集,不去重,性能更高。

21、MySQL分页查询怎么实现

基础分页查询

通过LIMIT指定每页记录数,OFFSET指定偏移量(从第几条开始):

SELECT * FROM table_name  
LIMIT 10 OFFSET 20;  -- 每页10条,跳过前20条(即第3页)  

或简化写法(LIMIT offset, row_count):

SELECT * FROM table_name  
LIMIT 20, 10;  -- 同上  

补充:当遇到追问优化性能的分页查询

大数据量时,避免使用OFFSET(深分页性能差),改用条件过滤:

SELECT * FROM table_name  
WHERE id > 100  -- 假设id是主键且有序  
LIMIT 10;       -- 获取id大于100的10条记录  

结合排序的分页

分页通常需配合ORDER BY

SELECT * FROM table_name  
ORDER BY create_time DESC  
LIMIT 10 OFFSET 0;  -- 按时间降序,获取第1页  

参数化分页(应用层实现)

在应用程序中动态计算偏移量(如PHP示例):

$page = 2;       // 当前页  
$per_page = 10;  // 每页条数  
$offset = ($page - 1) * $per_page;  

$sql = "SELECT * FROM table_name LIMIT $offset, $per_page";  

使用子查询优

复杂查询时,先分页主键再关联数据:

SELECT t.* FROM table_name t  
JOIN (SELECT id FROM table_name LIMIT 100, 10) tmp  
ON t.id = tmp.id;  

22、Spring Bean的生命周期

Spring Bean 生命周期的简化流程:

  1. 实例化 Bean

  2. 填充属性(依赖注入)

  3. Aware 接口方法调用

  4. BeanPostProcessor.postProcessBeforeInitialization

  5. 初始化方法(InitializingBean.afterPropertiesSetinit-method

  6. BeanPostProcessor.postProcessAfterInitialization

  7. Bean 就绪可用

  8. 销毁方法(DisposableBean.destroydestroy-method

实例化 Bean
容器通过构造函数或工厂方法创建 Bean 的实例。这是 Bean 生命周期的第一步,此时 Bean 的属性尚未设置。

填充属性(依赖注入)
容器通过反射机制将配置的属性值或依赖的其他 Bean 注入到当前 Bean 中。例如,通过 @Autowired 或 XML 配置的 <property> 完成依赖注入。

调用 Aware 接口方法
如果 Bean 实现了某些 Aware 接口(如 BeanNameAwareBeanFactoryAwareApplicationContextAware),Spring 会调用相应的方法传递相关信息。

BeanPostProcessor 的前置处理
所有注册的 BeanPostProcessorpostProcessBeforeInitialization 方法会被调用,允许在初始化前对 Bean 进行自定义处理。

初始化方法执行
如果 Bean 实现了 InitializingBean 接口,其 afterPropertiesSet 方法会被调用。如果配置了自定义的 init-method(如 @PostConstruct 或 XML 中的 init-method),该方法也会被执行。

BeanPostProcessor 的后置处理
所有注册的 BeanPostProcessorpostProcessAfterInitialization 方法会被调用,允许在初始化后对 Bean 进行进一步处理。

Bean 就绪可用
此时 Bean 已经完全初始化,可以被应用程序正常使用,通常会被放入单例池中供其他组件依赖。

销毁阶段
当容器关闭时,如果 Bean 实现了 DisposableBean 接口,其 destroy 方法会被调用。如果配置了自定义的 destroy-method(如 @PreDestroy 或 XML 中的 destroy-method),该方法也会被执行。

23、Spring 一个接口加了 拦截器,过滤器,切面,问执行顺序。

  1. 过滤器(Filter):Servlet 容器级别的,最早执行,在请求进入 Spring 前。

  2. 拦截器(Interceptor):属于 Spring MVC,在 DispatcherServlet 之后,Controller 方法调用前后执行。

  3. 切面(Aspect):基于 AOP,根据切入点表达式拦截方法,可以在方法执行前/后/异常等时机切入,通常比拦截器更细粒度。

  4. 执行顺序大致为:Filter → Interceptor → Aspect(Before) → Controller → Aspect(After)

24、JWT会存到redis里面吗

通常不会把 JWT 本身存储到 Redis,因为 JWT 是 无状态的,客户端自己保存 Token。

将失效的JWT(如用户登出后的Token)存入Redis并标记为黑名单,快速实现无效Token的拦截。

25、JWT的token续期怎么操作?

方式一:在每次请求时,若 JWT 快过期(比如剩余 10 分钟),服务端生成 新的 JWT 返回给客户端,客户端替换旧 token。

方式二:使用 refresh token 机制,JWT 设置较短有效期,另配一个 refresh_token 用于获取新的 JWT。

视频讲解:

美团面试题:常见的几种token过期后的续期方案分别是什么?_哔哩哔哩_bilibili

26、项目里面redis咋用的

• 答案(根据你的项目实际情况回答,常见用途包括):

• 缓存热点数据,减轻数据库压力

• 分布式 session

• 分布式锁

• 消息队列(List / Stream)

• 排行榜(ZSET)

• 验证码存储

• 限流、计数等

27、redis实现分布式锁

• 答案:

• 常用命令:SET key value NX PX milliseconds

• 原理:利用 Redis 的 SETNX(SET if Not eXists) 或 SET ... NX PX 实现互斥。

• 关键点:

◦ value 要设置唯一值(如 UUID),用于安全释放锁(判断是当前线程加的锁)。 ◦ 设置过期时间,防止死锁。 ◦ 释放锁时要判断 value 是否匹配,通常用 Lua 脚本保证原子性。

28、网络模型分层次(OSI和TCP/UDP)

OSI 七层模型:

1. 应用层 2. 表示层 3. 会话层 4. 传输层 5. 网络层 6. 数据链路层 7. 物理层

TCP/IP 四层(或五层)模型:

1. 应用层(HTTP、FTP、DNS) 2. 传输层(TCP、UDP) 3. 网络层(IP、ICMP) 4. 数据链路层(以太网等) 5. (可选)物理层

追问:

问:FTP协议是哪层的

答:应用层

问:传输层有哪些协议

答:TCP 和 UDP

问:视频通话主要用的是哪个协议

答:通常是 UDP 协议

(因为视频通话对 实时性要求高,允许少量丢包,而 TCP 有重传机制会导致延迟,所以一般采用 UDP + RTP/RTCP、QUIC 或 WebRTC 等技术。)

问:Http协议是哪层的

答:应用层

29、Https为啥比Http更安全

HTTPS = HTTP + SSL/TLS

SSL/TLS主要功能:

加密传输:使用 对称加密传输数据,非对称加密协商密钥。

身份认证:通过数字证书验证服务器身份,防止中间人攻击。

数据完整性:防止数据被篡改。

工作流程

  1. 握手阶段

    • 客户端发送支持的加密套件列表和随机数。

    • 服务器选择加密套件并返回证书、随机数和公钥。

    • 双方生成会话密钥,后续通信使用对称加密。

  2. 数据传输阶段
    应用数据通过对称加密传输,确保高效和安全。

30、https协议里面的数字签名有啥用

数字签名用于 验证数据的来源和完整性,防止数据被篡改或冒充。

流程简述:

1. 服务端用 私钥 对数据的 hash 值进行加密,生成 数字签名。

2. 客户端用 服务端的公钥 解密签名,得到 hash 值,再与自己计算的 hash 对比。

3. 一致 → 数据未被篡改,且确实来自持有对应私钥的服务器。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐