JVM垃圾收集器 垃圾收集算法是内存回收的方法论,垃圾收集器是内存回收的具体实现。 并发与并行(垃圾回收器) 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个 CPU上。 垃圾回收器分类 串行回收,Serial回收器,单线程回收,全程“Stop The World”。 并行回收,名称以Parallel开头的回收器,多线程回收,全程“Stop The World”。 并发回收,cms与G1,多线程分阶段回收,只有某阶段会“Stop The World”。 Serial收集器 Serial是一个单线程的垃圾收集器。采用“复制”算法,适用于新生代特点如下: “Stop The World”,它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。在用户不可见的情况下把用户正常工作的线程全部停掉。 使用场景:多用于桌面应用,Client端的垃圾回收器。 桌面应用内存小,进行垃圾回收的时间....
为什么要垃圾回收 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解。由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存。 对象存活算法之引用计数法 堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。(局部变量表的生命周期跟随方法的运行和结束) 优点 引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。 缺点 无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0. 在代码中分析JVM是否采用引用计....
对象创建流程 虚拟机遇到一条new指令时,首先检查这个对应的类能否在常量池中定位到一个类的符号引用 判断这个类是否已被加载、解析和初始化(类会被加载到方法区中) 为这个新生对象在Java堆中分配内存空间,其中Java堆分配内存空间的方式主要有以下两种:指针碰撞、空闲列表。 将分配到的内存空间都初始化为零值(引用null,int 0,boolean false) 设置对象头相关数据(GC分代年龄、对象的哈希码 hashCode、元数据信息) 执行对象方法(init方法) 指针碰撞(缺点:内存碎片) 分配内存空间包括开辟一块内存和移动指针两个步骤 非原子步骤可能出现并发问题,Java虚拟机采用CAS配上失败重试的方式保证更新操作的原子性 空闲列表(缺点:无法精确的分配内存空间,导致浪费内存空间) 分配内存空间包括开辟一块内存和修改空闲列表两个步骤 非原子步骤可能出现并发问题,Java虚拟机采用CAS配上失败重试的方式保证更新操作的原子性 Java对象内存布局 对象头 用于存储对象的元数据信息:包括对象运行时数据和类型指针 Mark Word 部分数据的长度在32位和64位....
JVM运行时数据区分布图讲解 线程共享数据区:方法区、堆 线程隔离数据区:虚拟机栈、本地方法栈、程序计数器 程序计算器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。 内存区域中唯一一 个没有规定任何 OutOfMemoryError 情况的区域。 JAVA虚拟机栈 用于作用于方法执行的一块Java内存区域。 每个方法在执行的同时都会创建一个栈帧(Stack Framel):用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 特点 栈的执行顺序是后进先出。 局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、s....
线程池 单个请求处理的时间很短,海量请求的情况下,频繁的创建线程,销毁线程所带来的系统开销是巨大的。 降低频繁创建、销毁线程的开销、线程的创建和销毁需要 JVM 进行大量的辅助操作。(内存的分配与回收、还会给垃圾回收器带来压力) “池”的概念可以很好的防止资源不足。过多线程会占用大量内存,导致OOM。 加快响应速度(复用池中的线程) 合理利用CPU和内存。 统一管理资源。 线程池适用的场景 服务器接受到大量请求时,使用线程池技术是非常适合的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率。 5个以上的线程就可以使用线程池来管理。 不使用线程池(海量任务) 线程的创建和销毁需要 JVM 进行大量的辅助操作。(内存的分配与回收、还会给垃圾回收器带来压力) 在Java语言中每创建一个线程直接对应操作系统中的一个线程。在操作系统中频繁创建、销毁大量线程会造成很大的系统开销。操作系统支持创建的线程数是有上限的。(线程数量无法与未知的任务数量一一对应) 无法作用于C10K场景,会引发OOM异常。 public class EveryTaskOneThread { publi....
CountDownLatch 创建 CountDownLatch 实例的时候需要传入线程数,await()操作进入等待状态,每个线程执行完毕调用 countDown(),计数器减一,当计数器为 0 的时候处于 WAITING 状态的线程会被唤醒。 应用场景:启动三个线程计算,需要每个线程的计算结果进行累加。 CountDownLatch import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { public static void main(String[] args) { CountDownLatch countDownLatch = new CountDownLatch(8); for (int i = 0; i < 8; i++) { int finalI = i; new Thread(()->{ try { Thread.sleep(finalI * 1000L); System.out.println(Thread.currentThread().getName....
原子类 原子是不可分割的最小单位,故原子类可以认为其操作都是不可分割。 一个操作是不可中断的,即便是在多线程情况下也可以保证。 java.uti.concurrent.aomic包下有很多具有原子特性的类。 原子类 VS 锁 粒度更细:原子变量可以把竞争范围缩小到变量级别,这是我们可以获得的最细粒度的情况,锁的粒度通常大于原子类。 效率更高:通常,使用原子类的效率会比使用锁的效率更高,除了高度竞争的情况下。 6类原子类纵览 基本类型原子类 以 AtomicInteger 为例,本质是对 Integer 的封装,提供原子的访问和更新操作,其本质是基于 CAS 技术。 AtomicInteger 常用方法 * get() 获取当前的值 * getAndSet() 获取当前的值,并设置新的值 * getAndIncrement() 获取当前的值,并自增 * getAndDecrement() 获取当前的值,并自减 * getAndAdd() 获取当前的值,并加上预期的值 * compareAndSet() 如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update....