Java 虚拟类加载机制
Java 虚拟类加载机制类加载的时机
遇到 new、getstatic、putstatic或 invokestatic
这四条字节码指令时:
使用 new 关键字实例化对象时。
读取或设置一个类型的静态字段时。
调用一个类型的静态方法时。
使用 java.lang.reflect 包的方法对类型进行反射调用时,如果类型没有进行过初始化,则需要先触发其初始化。
当初始化类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类。
当使用 JDK 7 新加入的动态语言支持时。
当一个接口中定义了 JDK 8 新加入的默认方法时。
类加载的过程加载
通过一个类的全限定名来获取定义此类的二进制字节流。
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
并没有指明二进制字节流必须得从某个 Class 文件中获取。
验证
文件格式验证:
检 ...
Java 类文件结构
Java 类文件结构无关性的基石实现语言无关性的基础仍然是虚拟机和字节码存储格式。Java 虚拟机不与包括 Java 语言在内的任何程序语言绑定,它只与“Class 文件”这种特定的二进制文件格式相关联。Class 文件中包含了 Java 虚拟机指令集、符号表以及若干其他辅助信息。
Class 类文件的结构
任何一个 Class 文件都对应着唯一的一个类或接口的定义信息。
Class 文件是一组以 8 个字节为基础单位的二进制流。
Class 文件格式采用一种类似于 C 语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:
“无符号数”和“表”。
无符号数属于基本的数据类型,以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数。
无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码构成的字符串值。
表是由多个无符号数或者其他表作为数据项构成的复合数据类型。
无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式。
顺序为“Big-Endian ...
Java 垃圾收集器与内存分配策略
Java 垃圾收集器与内存分配策略对象已死?引用计数算法
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
单纯的引用计数很难解决对象之间相互循环引用的问题。
可达性分析算法
基本思路:通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”。如果某个对象到 GC Roots 间没有任何引用链相连,或者从 GC Roots 到这个对象不可达时,则证明此对象是不可能再被使用的。
GC Roots 包括:
虚拟机栈(栈帧中的本地变量表)中引用的对象
堆栈中使用到的参数、局部变量、临时变量等
在方法区中类静态属性引用的对象,如 Java 类的引用类型静态变量
在方法区中常量引用的对象,如字符串常量池(String Table)里的引用
在本地方法栈中 JNI(即通常所说的 Native 方法)引用的对象
Java 虚拟机内部的引用:
基本数据类型对应的 Class 对象
常驻的异常对象(如 NullPointerExce ...
Java 内存区域与内存溢出异常
Java 内存区域与内存溢出异常Java 内存区域运行时数据区域
程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。
为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
Java 虚拟机栈
生命周期与线程相同。
每个方法被执行的时候,Java 虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。
局部变量表
存放了编译期可知的各种 Java 虚拟机基本数据类型(boolean、byte、 char、short、int、 float、long、double),对象引用(reference 类型),returnAddress 类型。
数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示。局部变量表所需的内存空间在编译期间完成分配。
一 ...
Java 并发容器和框架
Java 并发容器和框架ConcurrentHashMap 的实现原理与使用
ConcurrentHashMap 是线程安全且高效的 HashMap。
为什么要使用 ConcurrentHashMap:
在并发编程中使用 HashMap 可能导致程序死循环。
使用线程安全的 HashTable 效率非常低下。
锁分段技术
将数据分成一段一段地存储,然后给每一段数据配一把锁。当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
ConcurrentHashMap 的结构
ConcurrentHashMap 里包含一个 Segment 数组。
Segment 数组结构
Segment 是一种可重入锁(ReentrantLock)。
一个 Segment 里包含一个 HashEntry 数组。
当对 HashEntry 数组的数据进行修改时,必须首先获得与它对应的 Segment 锁。
HashEntry 数组结构
存储键值对数据。
ConcurrentHashMap 的初始化
ConcurrentHashMap 初始化方法是通过 initialCapacit ...
Java 并发编程实践
Java 并发编程实践生产者和消费者模式生产者和消费者模式是通过一个容器来解决生产者和消费者之间的强耦合问题。在该模式中,生产者和消费者彼此之间不直接通信,而是通过阻塞队列进行通信。具体工作流程如下:
生产者:生产者生成数据后,不必等待消费者处理数据,而是将数据放入阻塞队列中。
消费者:消费者无需直接向生产者请求数据,而是从阻塞队列中取出数据进行处理。
阻塞队列在这里充当了一个缓冲区,平衡了生产者和消费者的处理能力,避免了生产者和消费者在处理速度上的不匹配问题。
线程池与生产者消费者模式在 Java 中,线程池本质上也是一种生产者和消费者模式的实现。线程池与生产者消费者模式结合,可以有效地管理和调度任务:
任务提交:生产者将任务提交给线程池,线程池会根据其配置创建线程来处理任务。
任务处理:如果提交的任务数量超过了线程池的基本线程数,则任务会被放入阻塞队列中,等待线程资源可用时再进行处理。
应用场景:
系统中可以使用多个线程池来实现多生产者和多消费者模式。例如:
创建多个不同规模的线程池来处理不同类型的任务。比如,一个线程池专门处理 IO 密集型任务(如将数据读取到内存),而 ...
Executor 框架
Executor 框架Java 的线程既是工作单元,也是执行机制。为了更好地管理和调度线程,Java 提供了强大的 Executor 框架。
Executor 框架的两级调度模型在 Java 的 HotSpot VM 线程模型中,Java 线程(java.lang.Thread) 被一对一映射为本地操作系统线程。Java 线程启动时会创建一个本地操作系统线程。
上层调度:应用程序通常将应用分解为若干个任务,并使用用户级的调度器(Executor 框架)将这些任务映射为固定数量的线程。
下层调度:操作系统内核将这些线程映射到硬件处理器上。应用程序通过 Executor 框架控制上层的调度,而下层的调度由操作系统内核控制,不受应用程序的直接控制。
Executor 框架的结构
任务:
任务的实现接口包括 Runnable 接口或 Callable 接口。
任务的执行:
任务执行机制的核心接口是 Executor,其继承接口为 ExecutorService。Executor 框架的两个关键实现类是 ThreadPoolExecutor 和 ScheduledThreadPoolEx ...
Java 中的线程池
Java 中的线程池合理地使用线程池可以带来以下三个主要好处:
降低资源消耗:通过重复利用已创建的线程,减少线程创建和销毁所造成的开销。
提高响应速度:当任务到达时,无需等待线程创建即可立即执行任务。
提高线程的可管理性:线程是稀缺资源,若无限制地创建线程,不仅会消耗系统资源,还会降低系统的稳定性。通过线程池,可以对线程进行统一分配、调优和监控。
线程池的实现原理
处理流程
判断核心线程池的线程是否都在执行任务:
如果没有,则创建一个新的工作线程来执行任务。
如果核心线程池的线程都在执行任务,则进入下一步。
判断工作队列是否已满:
如果工作队列未满,则将新提交的任务存储在工作队列中。
如果工作队列已满,则进入下一步。
判断线程池的线程是否都处于工作状态:
如果没有,则创建一个新的工作线程来执行任务。
如果线程池已满,则交给饱和策略来处理该任务。
ThreadPoolExecutor
execute 方法的执行流程ThreadPoolExecutor 在执行 execute 方法时,会根据以下四种情况进行处理:
如果当前运行的线程少于 corePoolSize,则创建 ...
Java 中的并发工具类
Java 中的并发工具类并发流程控制的手段
CountDownLatch
CyclicBarrier
Semaphore
在线程间交换数据的一种手段
Exchanger 工具类
CountDownLatch
CountDownLatch 允许一个或多个线程等待其他线程完成操作。
计数器必须大于等于 0,当计数器为 0 时,调用 await 方法不会阻塞当前线程。
CountDownLatch 不可能重新初始化或者修改 CountDownLatch 对象的内部计数器的值。
一个线程调用 countDown 方法 happen-before,另外一个线程调用 await 方法。
同步屏障 CyclicBarrier
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。
让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
**CyclicBarrier 还提供一个更高级 ...
Java 中的 13 个原子操作类
Java 中的 13 个原子操作类原子更新基本类型类
AtomicBoolean:原子更新布尔类型。
AtomicInteger:原子更新整型。
int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger 里的 value)相加。
int getAndIncrement():以原子方式将当前值加 1,注意,这里返回的是自增前的值。
void lazySet(int newValue):最终会设置成 newValue,使用 lazySet 设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
int getAndSet(int newValue):以原子方式设置为 newValue 的值,并返回旧值。
AtomicLong:原子更新长整型。
Atomic 包里的类基本都是使用 Unsafe 实现的
Unsafe 只提供了 3 种 CAS 方法:compareAndSwapObject、compareAndSwapInt 和 compareAndSwapLong。
看 AtomicBoolean 源码,发现它是先把 ...