回答重点使用弱引用作为 ThreadLocal 的键可以防止内存泄漏。若 ThreadLocal 实例被不再需要的线程持有为强引用,那么当该线程结束时,相关的 ThreadLocal 实例及其对应的数据可能无法被回收,导致内存持续占用。
而弱引用允许垃圾回收器在内存不足时回收对象。这样,当没有其他强引用指向某个 ThreadLocal 实例时,它可以被及时回收,避免长时间占用内存。
扩展知识...
回答重点1)不要滥用 ThreadLocal:
ThreadLocal 适用于需要为每个线程维护独立副本的场景,例如:数据库连接、用户会话、事务上下文、临时缓存等。
对于能通过参数传递的上下文信息,不应该使用 ThreadLocal 来处理,避免设计不合理和代码可读性差的问题。
2)避免内存泄漏:
ThreadLocal 中的 key 是弱引用,但 value 是强引用,因此需要在适当...
回答重点InheritableThreadLocal 是 ThreadLocal 的一个扩展,用于在线程创建时将父线程的 ThreadLocal 变量副本传递给子线程,使得子线程可以访问父线程中设置的本地变量。它解决了 ThreadLocal 无法在子线程中继承父线程本地变量的问题。
工作原理:
当创建子线程时,InheritableThreadLocal 的值会被自动拷贝到子线程中。
子...
回答重点正常情况下使用 ThreadLocal 是没什么问题的,但是如果极端情况下,数据比较多,则可能会出现以下几个问题:
内存泄露问题
ThreadLocal 中的 ThreadLocalMap Hash 冲突用的是线性探测法,效率低
ThreadLocal 主动清理的开销问题
扩展知识内存泄漏问题ThreadLocal 的生命周期和线程的生命周期绑定,线程池中的线程可能被复用,但 T...
我们先来思考下 ThreadLocal 有哪些不够好的地方,如果要改都需要改哪些实现。
ThreadLocal hash 冲突的线性探测法不好,还有 Entry 的弱引用可能会发生内存泄漏,这些都和 ThreadLocalMap 有关,所以需要搞个新的 map 来替换 ThreadLocalMap。
而这个 ThreadLocalMap 又是 Thread 里面的一个成员变量,这么一看 Th...
看起来 Thread.sleep(0) 很奇怪,让线程睡眠 0 毫秒?那不是等于没睡眠吗?
是的,确实没有睡眠,但是调用了 Thread.sleep(0) 当前的线程会暂时出让 CPU ,这使得 CPU 的资源短暂的空闲出来别的线程有机会得到 CPU 资源。
所以,在一些大循环场景,如果害怕这段逻辑一直占用 CPU 资源,则可以调用 Thread.sleep(0) 让别的线程有机会使用 CP...
回答重点这三个方法都属于 Object 内种定义的方法,主要用于线程之间的通信和同步,且需要在 synchronized 修饰的方法或同步块中使用。
wait() 使得当前线程进入等待状态,且会释放锁。
notify() 会顺序(在 hotspot 的实现中)唤醒一个调用 wait 后等待的线程。
notifyAll 会唤醒所有调用 wait 等待的线程。
扩展知识在 Object 源码中 ...
重点回答默认情况下,线程池不会直接报告哪个线程发生了异常,但是可以采取以下几种方法:
1)**自定义线程池的 ThreadFactory**:
通过自定义 ThreadFactory,为每个线程设置一个异常处理器(UncaughtExceptionHandler),在其中记录发生异常的线程信息。
2)**使用 Future**:
提交任务时使用 submit() 方法,而不是 exec...
这玩意是阿里开源的一个组件,原生的 ThreadLocal 不支持在线程池中传递本地变量,所以弄了个这个来实现下这个需求
接下来我们来详细探讨一下
缘由任何一个组件的出现必有其缘由,知其缘由背景才能更深刻地理解它。
我们知道 ThreadLocal 的出现就是为了本地化线程资源,防止不必要的多线程之间的竞争。
在有些场景,当父线程 new 一个子线程的时候,希望把它的 ThreadLoca...
重点回答shutdown() 和 shutdownNow() 都用于关闭线程池,但工作方式有所不同:
1)**shutdown()**:
启动线程池的平滑关闭。它不再接受新的任务,但会继续执行已经提交的任务(包括在队列中的任务)。
线程池会进入 SHUTDOWN 状态,所有已执行和正在执行的任务都会继续完成,只有所有任务完成后,线程池才会完全终止。
2)**shutdownNow()**:
...
回答重点Thread.sleep() 和 Thread.yield() 都是用于控制线程行为的两个方法。
**Thread.sleep()**:
使当前线程进入休眠状态(TIMED_WAITING 状态),暂停执行指定的时间(以毫秒为单位)。在休眠期间,线程不会占用 CPU 时间片。休眠结束后,线程会尝试重新获取 CPU 时间片,进入可运行状态。
休眠时间取决于操作系统的计时器精度,可能会...
回答重点常见有以下五种方式创建使用多线程:
1)实现 Runnable 接口:
实现 Runnable 接口的 run() 方法,使用 Thread 类的构造函数传入 Runnable 对象,调用 start() 方法启动线程。
例子:Thread thread = new Thread(new MyRunnable()); thread.start();
2)继承 Thread 类:
...
回答重点可以动态修改的。Java 的 ThreadPoolExecutor 提供了动态调整核心线程数和最大线程数的方法。
1)修改核心线程数的方法:
使用 ThreadPoolExecutor.setCorePoolSize(int corePoolSize) 方法可以动态修改核心线程数。corePoolSize 参数代表线程池中的核心线程数,当池中线程数量少于核心线程数时,会创建新的线程...
回答重点线程同步是指在多线程环境下,为了避免多个线程对共享资源进行同时访问,从而引发数据不一致或其他问题的一种机制。它通过对关键代码段加锁,使得同一时刻只有一个线程能够访问共享资源。
当多个线程共享同一资源(如变量、对象或文件)时,若没有同步机制,可能会导致竞态条件,即线程对共享资源的操作是非原子性的,多个线程之间可能会同时修改数据,导致结果不符合预期。
扩展知识Java 中常见的同步方式s...
回答重点1)**使用 Thread.join()**:
主线程通过调用 join() 方法等待子线程执行完毕。子线程正常结束,说明执行成功,若抛出异常则需要捕获处理。
2)**使用 Callable 和 Future**:
通过 Callable 创建可返回结果的任务,并通过 Future.get() 获取子线程的执行结果或捕获异常。Future.get() 会阻塞直到任务完成,若任务...
回答重点Java 对象在虚拟机中的存储由以下几个部分组成:
对象头(Header):包含对象的元信息和运行时数据。主要由以下三部分构成:
Mark Word:用于存储运行时数据,例如对象的哈希码(HashCode)、GC 标记信息、锁状态标志等。它是一个多功能字段,会根据对象的状态动态变化。
类型指针(Class Pointer):指向对象对应的类的元数据,用于确定该对象的类型。
数...
回答重点Java 程序的执行流程经历了从编译到字节码的生成,再到类加载和 JIT 编译的过程,最终在 JVM 中执行。并且在程序运行过程中,JVM 负责内存管理、垃圾回收和线程调度等工作。
主要流程如下:
源代码:编写 .java 文件。
编译:使用 javac 编译器生成 .class 字节码文件。
类加载:JVM 的类加载器加载 .class 文件到内存中。
解释执行:JVM 将字节码...
回答重点JIT(Just-In-Time)编译后的机器码通常存放在 Code Cache(代码缓存区,不在堆内) 中。
JVM 提供参数用于调整 Code Cache 的大小和行为:
-XX:InitialCodeCacheSize:初始大小。
-XX:ReservedCodeCacheSize:最大大小。
-XX:+PrintCodeCache:打印 Code Cache 信息。
扩展...
回答重点可以从以下四个方面来回答:
从内存管理机制来看:
G1 将整个堆划分为多个大小相等的Region(默认最多2048个),物理上不再严格区分新生代和老年代,而是根据回收价值动态调整各区域的回收。这样避免了CMS因固定分代导致的内存碎片问题,提升了内存利用率。
从回收算法来看:
CMS 采用标记-清除算法,容易产生内存碎片,可能导致Full GC提前触发
而 G1 基于标记-整理算...
回答重点Java 程序在编译后生成字节码(.class 文件),而不是直接生成特定于某一操作系统的机器代码。
在不同操作系统上都有各自实现的 JVM,负责将字节码翻译为特定平台的机器代码并执行。这使得同一份 Java 字节码可以在任何支持 JVM 的平台上运行。
扩展知识进一步理解跨平台所谓的跨平台主要指的是在不同的硬件或操作系统上,Java 代码都可以运行,不需要针对不同平台做对应的修改。...
回答重点 编译执行:是指程序在执行之前,首先通过编译器将源代码编译为机器代码,然后直接在 CPU 上运行。常见的编译语言如 C、C++。
优点:编译后的程序运行速度快,因为机器代码是针对目标平台直接生成的,且不需要在运行时再进行翻译。
缺点:程序必须针对每个平台重新编译,跨平台性差;另外,编译后生成的机器代码难以调试和逆向工程。
解释执行:解释执行是指源代码不经过编译器的预先编译,而是在...
回答重点Java 虚拟机运行时数据区分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。
1)方法区(Method Area):
存储类信息、常量、静态变量和即时编译器(JIT)编译后的代码。
属于线程共享区域,所有线程共享方法区内存。
在 JDK 8 之前,HotSpot 使用永久代(PermGen)来实现方法区,JDK 8 之后被元空间(Metaspace) 取代,元空间使用的是本地内存...
回答重点栈(Stack):主要用于存储局部变量和方法的调用信息(如返回地址、参数等)。在方法执行期间,局部变量(包括引用变量,但不包括它们引用的对象)被创建在栈上,并在方法结束时被销毁。
堆(Heap):用于存储对象实例和数组。每当使用 new 关键字创建对象时,JVM 都会在堆上为该对象分配内存空间。
扩展知识从其他方面进一步区分
生命周期:我们知道JVM里面的垃圾回收主要是对堆空间的处理...
回答重点Java 中的直接内存(Direct Memory)是由操作系统分配的内存区域,它不受 JVM 堆内存管理限制(是堆外内存)。直接内存是通过 java.nio 包中的 ByteBuffer.allocateDirect() 方法分配的,它可以绕过 JVM 垃圾回收机制,直接与本地系统内存交互。
扩展知识直接内存的优势 由于直接内存不需要在堆上进行分配和复制数据,因此与操作系统的 ...
回答重点 Java 中的常量池(Constant Pool)是一块存储用于运行时的常量或符号的区域。它主要存在于两种地方:
运行时常量池:在每个类或接口的 Class 文件中存储编译时生成的常量信息,并在类加载时进入 JVM 方法区(Java 8 之后是 metaspace)。
字符串常量池:用于存储字符串字面量,位于堆内存中的一块特殊区域。通过 String 类中的 intern() 方...
回答重点Java 的 AOT(Ahead-Of-Time,预编译)是一种在程序运行之前,将 Java 字节码直接编译为本地机器码的技术。
JIT 是在 Java 运行时将一些代码编译成机器码,而 AOT 则是在代码运行之前就编译成机器吗,也就是提前编译。
提前编译的好处是减少运行时编译的开销,且减少程序启动所需的编译时间,提高启动速度。
扩展知识AOT 的工作原理:AOT 编译是在构建阶段对...
回答重点Java 中的 JIT(Just-In-Time,即时编译)编译器是一种在程序运行时将字节码转换为机器码的技术。因为这种转换是在程序运行时即时进行的,因此得名“Just-In-Time”。
它在 Java 程序运行的时候,发现热点代码(频繁执行的代码段)时,就将这段代码编译成机器码,减少解释执行的开销,使得 Java 代码接近本地代码的性能。
扩展知识热点代码(Hotspot Cod...
回答重点逃逸分析(Escape Analysis)是 Java 编译器的优化技术,用于确定一个对象的作用范围,即分析对象是否会逃逸出当前方法或线程的作用范围。如果对象不会逃逸,JVM 可以实现栈上分配、同步消除、标量替换等优化,减少内存分配开销和同步开销,从而提高应用的性能。
逃逸的两种类型:
方法逃逸:如果一个对象在方法内部创建,并作为返回值或通过参数传递给其他方法,则该对象会逃逸到方法...
回答重点分为新生代收集器和老年代收集器来看,常见的垃圾收集器包括:
新生代垃圾收集器:1)Serial 收集器:
单线程收集器,适合小型应用和单处理器环境。
触发 Stop-The-World(STW)操作,所有应用线程在 GC 时暂停。
适用场景:适用于单线程应用和客户端模式。
2)ParNew 收集器:
是 Serial 收集器的多线程版本,能够并行进行垃圾收集。
与 CMS 收集...
回答重点主要通过以下两种方式来实现:
1. 引用计数法(Reference Counting):
每个对象维护一个引用计数器,引用计数增加时,计数器加 1,减少时,计数器减 1。当引用计数器为 0 时,说明该对象不再被引用,可以被回收。
优点:实现简单,实时性好。
缺点:无法处理循环引用的问题,两个对象互相引用时,引用计数器永远不会为 0。
2. 可达性分析算法(Reachability ...