类加载过程

类加载是将class文件实例化成Class对象的过程,分三步  Load(载入二进制,初步校验) Link(校验,解析等),Init(初始化)

类加载器 

Bootstrap(JVM启动时创建 C/C++编写,加载Object,System等核心类)

Extension ClassLoader(JDK9)/平台类加载器 加载XML,加密等类

Application ClassLoader 应用类加载器 加载CLASS_PATH路径下的类

双亲委派-低层次的类加载器不能覆盖高级层次类加载器已经加载的类,低层次类加载器加载类之前首先需要询问高层级类加载器是否有该类

自定义类加载器:可用于:隔离加载类/修改类加载方式/扩展加载源/放置源码泄漏 ,自定义类加载器可违反双亲委派模式,如Spring的类加载器

Spring JarLauncher启动过程

MANIFEST.MF中配置了org.springframework.boot.loader.JarLauncher作为Jar包启动类,以及真正的应用的启动类

该类启动后会创建ClassLoader,并载入BOOT-INFO目录下的类和jar包,最后启动主类

内存模型

JVM内存布局分为

虚拟机栈,本地方法栈,程序计数器,堆,元数据区 前三项线程私有,后两者是线程之间共享

堆 

分为新生代(Eden,S1,S2)和老年代,新对象申请流程如下:

1 如果Eden放得下,则直接放在Eden,结束,否则进入2

2 进行YOUNG GC,再次尝试,成功则结束,否则进入3

3 尝试放进老年区,放得下则结束否则进入4

4 进行一次FULL GC,再次尝试,成功则结束,否则抛出OOM

YOUNG GC的流程如下:

旧对象放入一个Survivor,成功则YOUNG GC完成,否则放入老年代

S1/S2转移超过阈值后(默认15)晋升至老年代

配置参数 -Xms -Xmx 生产建议设置成一样大,避免堆调整大小 -XX:HeapDumpOnOutOfMemoryError 输出堆内信息


元空间

存储常量池,方法元信息,类元信息, JDK7以前的HotSpot为Perm区,8以后该区不存在

虚拟机栈

包含局部变量表(保存局部变量),操作栈(指令的执行栈),动态链接和方法地址

本地方法栈

调用本地方法时的内存,不受JVM控制

程序计数器

存放指令的偏移量等信息,线程的执行和恢复需要用到程序计数器


对象引用

强引用 一般赋值为强引用

软引用SoftReference OOM时被回收

弱引用WeakReference gc时即被回收

虚引用PhantomReference 定义完后即不可获取

垃圾回收


垃圾回收过程会清除没有与GC Root引用关系的对象, GC Root常见的有静态属性,常量,虚拟机栈,本地方法栈中引用的对象

垃圾回收常用的方法有 标记清除,标记整理 (解决空间碎片问题),Mark Copy (YOUNG GC中常用)


Serial GC

通常应用于 YOUNG GC过程,其YOUNG GC 采用Mark Copy ,FULL GC采用标记整理,回收过程会产生Stop The World 应用不可用

CMS

采用标记清除方法,分为四个过程,其中1和3会有STW现象:

初识标记

标记GCRoot直接关联的对象

并发标记

与用户线程并发执行,在第一阶段基础上追溯标标记对象

重新标记

重新标记在第二阶段遗漏对象

并发清除

与用户对象并发运行,回收清理

优点:吞吐量大,响应快 但容易产生空间碎片,对性能要求高

G1(Hotspot JDK7,JDK11的默认GC)

具备压缩功能,避免碎片,停顿时间可控

G1将堆空间分为多个相同大小的区域,分为Eden,Survivor,Old,Humongous类型,Humongous放置大对象,G1过程分为5步:

初始标记:标记GC Root直接可达的存活对象

根区域扫描:并发标记存活区中被引用的老年代对象

并发标记:从堆中标记存活对象

重新标记:最终的标记处理

清理:按各区域回收价值顺序进行回收操作


泛型/擦除

泛型代码在编译后会被擦除,变成Object类,对具体的对象进行强制转化(CAST)

泛型好处: 类型安全(编译校验),复用代码,可读性强

List<?> 用来承接泛型,不能添加任何类型对象

List 能赋值给任何参数化的泛型

List<? super T> 适用于消费类型,可放类型为T或者其子类型

List<? extends T> 适用于生产类型(仅取值),取出的值为T类型或者其子类不可以放任何除null以外值

多线程JUC

由于线程的唤醒和阻塞需开销较大,因此锁升级 JDK6 以后针对synchronized加锁方式进行优化,及锁升级机制不再是单纯的切换阻塞状态

无锁 类似于cas的实现,不断重试,对于线程较少的场景性能好

偏向锁 对象头内Markword记录优先获取锁的线程id

轻量级锁 cas 自旋

重量级锁 阻塞


线程状态

NEW(新建状态) RUNNABLE(就绪状态),RUNNING(运行状态),BLOCKED(阻塞状态,睡眠,waiting等),DEAD(终止状态)

线程其它: setUncaugtExceptionHandler,priority ,ThreadGroup

线程池

管理和复用线程,节省开销

ThreadPoolExecutor

核心参数 阻塞队列,corePoolSize, maximumPoolSize, keepAliveTime,workQueue,threadFactory,handler

线程对象封装在Worker中,一旦启动,会循环获取缓冲队列内的任务,有可能会阻塞

拒绝策略 会在缓冲队列达到上限并且活动线程达到设置上限时生效,默认的有 Abort,Discard,DiscardOld(抛弃队列中最老的),CallerRun(自己运行)


工厂类Executors不推荐使用,newCachedThreadPool 最大线程是Integer.MAX_VALUE,newFixedThreadPool,缓冲队列默认的最大容量为Integer.MAX_VALUE都有OOM风险


AQS 

为实现阻塞锁以及信号量同步提供框架,内部依靠一个FIFO队列以及整型的状态值。

ReEntrantLock

可重入锁,实现了公平/非公平锁 ,公平锁会首先判断当前线程是在队列最前

Condition 对象,signal,await等都需要首先获取锁

信号量

Semaphore 资源空闲状态同步 方法有acquire/tryAquire/release

CountDownLatch 倒计时类型 当多个线程调用countDown达到一定数量或者interrupt,await的线程唤醒

CyclicBarier,主要用ReEntrantLock实现, 可重复使用 当线程等待(await)的数量达到制定值时,所有等待线程唤醒


NIO    

核心抽象接口:

Buffer 数据容器

Charsets 字节到字符的转换

Channel 表示到能够进行io操作的实体

selector 多路复用组件

阻塞io 等待空闲/读写均阻塞

NIO 等待空闲不阻塞,读写阻塞

AIO 异步io,均不阻塞,返回Future


集合HashMap,ConcurrentHashMap

ArrayList和HashMap底层都用数组存储数据,因此都需要考虑扩容的问题,尽量设置合理的初始容量减少扩容的开销。ArrayList初识为10,扩容采用位移n+n>>1的方式(近似为1.5倍),HashMap初始为16,扩容翻倍,容量为2的指数幂。

Array.toArray 传入的数组小于元素数量,则返回新的数组,否则将元素复制到传入的数组返回。建议传入数组大小于列表元素一致,性能更好。


Comarator(compare比较两个对象,与自身不相关,更符合开闭原则),Comparable(compareTo,自身与同类比较)

对象覆写equal方法时尽量同时覆写hashCode,因为HashMap 判断key时,首先会判断hashCode,不想等则认为是不同的对象。


fail-fast 机制 

子列(sublist)表遍历时父列表遭修改

列表用foreach遍历时修改

juc包内是fail-safe,不会抛出异常

树结构

平衡二叉树 左右高度不超过1,且递归成立,空树也是平衡二叉树

AVL树是平衡二叉树,查找性能好,修改性能差,与不是平衡二叉树的RB树相反


HashMap

结构为 表+哈希桶(链表或rb树),当达到扩容阈值时(loadFactor)进行2倍扩容(2的幂),所有节点需要重新计算位点,因此扩容开销大。

hash计算为 (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) ,槽位计算为tab[(tabSize- 1) & hash]  tabSize一般比较小,高位可能会被截断,有利于分散。

表大小为2的幂,因为其槽计算公式为&,计算更快,因此需要二进制全部为1

并发死链问题,在扩容的时候,会对槽点上的链表进行重现排列,可能多个线程操作同一条链,导致多个对象互链,形成闭环,最终导致死循环执行。

ConcurrentHashMap

JDK8 以前采用对分段加锁方式保证线程安全,而JDK8以后采用CAS进行元素的插入,槽内无元素->cas 有元素->锁住槽上元素


jstack,jstat

jstac 能够打印当前运行的线程状态

jstat 打印虚拟机状态