Skip to content

Latest commit

 

History

History
520 lines (236 loc) · 17.1 KB

JAVA.md

File metadata and controls

520 lines (236 loc) · 17.1 KB

JAVA知识整理学习

一:JVM

1.JMM

​ JAVA内存模型,主要是cpu的三级缓存和主存的数据交换。涉及到volatile指令保证的内存屏障和防止指令重排序。

1.1重排序

​ cpu为了优化执行的效率,会将线程内不影响执行结果的指令进行重排序,在多线程环境下需要考虑到相关影响,避免造成损失。在单例里的懒汉模式里会使用volatile来保证单例在初始化过程中,不会因重排序导致实例未被初始化而直接使用。(编译器优化

1.2原子性

​ cpu在执行过程中,对于单线程的指令需要满足happensbefore的原则,基本类型如long和double需要通过volatile来保证原子性,主要原因是数据分配在64位的空间中,高位和低位修改都需要进行原子操作。JUC中的部分类也具有原子性。

1.3内存可见性

​ cpu的三级缓存和内存主存之间数据同步存在延迟,通过volatile可以强制将数据更新至主存区域,可以利用这个特性对直接的赋值操作boolean类型进行修改,但是volatile保证的是可见性而不是原子性,所以无法避免类似i++这种多线程下的读写不一致的影响。内存可见性在JDK11以后的ZGC中也会利用这一特性实现读屏障,从而保证工作线程和垃圾回收线程互不影响,可以有效减少标记和stoptheworld的时间。(通过happens-before可以描述内存可见性的相关问题,比如单线程、锁、volatile、juc、interrupt、线程生命规则)

1.4内存屏障

volatile可以保证屏障两侧指令的重排序,通过mesi协议,总线嗅探到变量的变化。

2.概念

​ java虚拟机,满足相关规范,保证java类文件可以加载成字节class的文件,通过可以通过jvm转换成机器码,实现一次编译多次运行的目的。

2.1组成

2.1.1java虚拟机栈

​ 用于保存java指令的操作码和操作数栈、局部变量、返回值**(八大基本类型,一旦编译就无法改变空间大小),会跑出栈深StackOverflowError和栈容OutOfMemory的异常**,以及对象的引用

2.1.2 native本地虚拟机栈

​ 用于执行native程序(如c、c++)语言相关文件

2.1.3 程序计数器

​ 用于保存线程上线文切换、跳转所必须的内存指针信息(由执行引擎负责读取下一条命令

2.1.4 堆

​ 用于保存java实例对象、常变量和数组(开启逃逸分析后,会在栈上分配),线程共享的区域,也是垃圾回收的主要区域。

2.1.4.1年轻代和永久代

​ 年轻代中eden和survivor的比例是8:1:1,每次eden和from通过标记、复制的方式进行内存交换,同时将对象的生命+1,达到系统指定阈值后,将其迁移至老年区。对象较大超过阈值或者年轻代没有可用空间时,对象的分配也可直接进入老年代。老年代和新生代大小是2:1.

2.1.5方法区

​ 用于保存方法常量值、静态变量,JIT即时编译后的代码

​ 2.1.5.1运行时常量池

​ 保存编译产生的直接常量(基本类型和String)和符号引用等

2.1.6解释引擎

​ JIT即时编译,会动态的将已经加载的类文件的流信息直接加载到缓冲区,缩短加载的时间。

2.2 类加载过程

2.2.1加载

​ 通过bootStrap-ext-app-customer依次向上委托加载,双亲委派主要是指,加载的过程中,子类的加载器都会首先将类加载的请求委托父类进行,只有在父类无法完成时,才会自己进行加载。主要是为了保证类加载的安全,例如rt.jar中的相关类文件会通过native加载器来验证其正确性,保证不被串改。(通过重写findClass和LoadClass方法可以实现打破限制,例如tomcat的多app调用顺序会变更)

​ 加载的方式:getInstance,classForName,new,clone,调用子类、调用类的静态方法

image-20201123095128354

2.2.2链接

2.2.2.1验证

​ 通过机器码中的魔数、版本号等信息来验证类文件的有效性

2.2.2.2准备

​ 分配静态变量函数内存区域,默认值在此赋值

2.2.2.3解析

​ 将符号引用转换为直接引用

2.2.3初始化

​ 对静态变量、代码块进行初始化

2.2.4卸载

2.3对象

​ 一个类的对象包括,对象头(锁指针、锁标识、分代年龄、hash)、对象数据、hotspot要求的8倍补齐数据。

2.3.1引用类型

2.3.1.1强引用

​ 对象引用,常量,静态代码块,gc时不会被回收

2.3.1.2软引用

​ 对象关联,只在发生内存溢出时回收

2.3.1.3弱引用

​ 对象探测,有用但不必须,gc时会被回收

​ 2.3.1.4虚引用

​ 对象确认,垃圾回收中二次确认时产生,可有可无,优先回收

2.4GC

2.4.1存活算法

​ 引用计数法:调用+1,出栈-1,无法解决循环引用,但是效率高

​ 可达性分析法:通过gcroot出发,将引用链上的对象进行标记,其余对象进行回收,对于存在finalize函数的对象,将其放入F-queue进行处理,第一次将会处于带死亡的状态。

2.4.2垃圾回收器

​ 新生代:串行、并行young、并行

​ 老年代:串行old、并行old、cms

​ CMS 分代、标记、清除、复制

​ G1 分区、标记、复制、整理

​ ZGC 着色笔加快标记、读屏障解决GC和应用之间并发导致的stw

2.4.3垃圾回收算法

​ 标记-清除、内存碎片

​ 标记-复制、浪费空间

​ 标记-整理、性能损耗

3JVM命令

​ jps -q -m -l路径 -v详细

​ jstat 查看线程生命周期

​ jinfo查看JVM参数,通过+、-可以修改

​ jmap查看堆信息

​ jhat 和jmap统一使用可视化查看

​ jstack查看栈信息、排查死锁信息

​ jstack pid|grep tid(tid为十六进制)

​ 可以使用jconsole和jvisualVM进行查看

二:多线程

​ 进程是计算机资源分配的最小单位,线程是计算机cpu调度的最小单位,现在有了协程,可以比线程更细的粒度,但是还不太普及。

1.线程池

1.1线程创建的几种方式

1.1.1通过线程池创建

​ 优点:提高资源使用率、加快响应速度、便于管理

​ newFixedThreadPool(core n) 底层linkedBlockingQueue

​ newSingleThreadPool(core 1,max 1)底层 SynchronousQueue

​ newCachedThreadPool) 底层linkedBlockingQueue

​ newScheduleThreadPool 底层DelayedWorkQueue

​ newSingleScheduleThreadPool 底层DelayedWorkQueue

​ newWorkStealingPool 底层Deque

1.1.2通过命令创建

​ runable、callable、future本质都是thread.run

​ callable对比runable可以返回结果以及接受抛出的异常。

​ future特点:返回结果、查看状态、中断执行

1.1.3多线程优缺点及场景

​ 优点:速度快、并发高

​ 缺点:cpu上下文切换、内存资源占用、线程安全的复杂度

​ 场景:数据库连接池、netty工作线程、异步请求任务、分布式并行计算

2.线程状态(6种)

​ new

​ new Thread(new Runable)

​ new Thread(new Callable)

​ future(completableFuture)全部线程完成后进行操作

​ runable

​ 不间断获取cpu并执行

​ blocked

​ 由于资源竞争进入的阻塞状态

​ waited

​ 通过wait信号显式的等待,通过notify或者notifyall进行唤醒

​ sleep不释放锁、wait释放锁,可以通过join来继续线程的执行。

​ time-waited

​ 有最长等待时间的等待,

​ terminated

​ 线程终止状态,由stop命令,进行线程中断响应的逻辑。

​ 优雅终止、线程会将等待队列中的任务执行完毕再结束

​ 暴力终止、强制结束进程

3.线程安全

3.1安全的因素

​ 原子性(i++)

​ 执行顺序happenBefore

​ 共享资源竞争

3.2锁的分类

3.2.0偏向锁|轻量级锁|重量级锁

​ 偏向锁

​ 如果自始至终,对于这把锁都不存在竞争,那么其实就没必要上锁,只需要打个标记就行了,这就是偏向锁的思想。一个对象被初始化后,还没有任何线程来获取它的锁时,那么它就是可偏向的,当有第一个线程来访问它并尝试获取锁的时候,它就将这个线程记录下来,以后如果尝试获取锁的线程正是偏向锁的拥有者,就可以直接获得锁,开销很小,性能最好。

​ 轻量级锁

​ synchronized 中的代码是被多个线程交替执行的,而不是同时执行的,也就是说并不存在实际的竞争,或者是只有短时间的锁竞争,用 CAS 就可以解决,这种情况下,用完全互斥的重量级锁是没必要的。轻量级锁是指当锁原来是偏向锁的时候,被另一个线程访问,说明存在竞争,那么偏向锁就会升级为轻量级锁,线程会通过自旋的形式尝试获取锁,而不会陷入阻塞。

​ 重量级锁

​ 重量级锁是互斥锁,它是利用操作系统的同步机制实现的,所以开销相对比较大。当多个线程直接有实际竞争,且锁竞争时间长的时候,轻量级锁不能满足需求,锁就会膨胀为重量级锁。重量级锁会让其他申请却拿不到锁的线程进入阻塞状态。

3.2.1重入锁|不可重入锁

​ reentrantLock、synchronized、信号量都是可重入锁,lock会递增

​ 不可重入锁:例如自旋锁

可重入锁指的是线程当前已经持有这把锁了,能在不释放这把锁的情况下,再次获取这把锁。不可重入锁指的是虽然线程当前持有了这把锁,但是如果想再次获取这把锁,也必须要先释放锁后才能再次尝试获取。

3.2.2共享锁|独占锁

​ CAS读锁可共享、写锁也叫互斥锁在重入的过程中可以降级为共享锁

​ 读锁因为可能会导致饥饿不允许插队,写锁因为插队困难,所以允许插队

​ 允许写锁降级为读锁、不允许读锁升级为写锁

3.2.3乐观锁|悲观锁

​ 乐观锁:总是认为修改不会导致竞争,CAS,有问题再解决

​ 悲观锁:不管怎样,先抢占锁再说,只有占有锁才能进入临界区

3.2.4公平锁|非公平锁

​ 非公平锁:新产生处于runable状态的线程会主动去抢占一次锁,抢占失败后才会进度阻塞队列,jdk默认是非公平锁,可以提高性能,但是也会产生线程饥饿的状态。tryLock是显式的非公平锁。

3.2.5自旋锁|非自旋锁

​ 自旋锁:通过循环等待,juc中的原子类都是自旋锁,适用于锁冲突较小、临界区较小的场景

​ 非自旋锁:lock直接放弃或者去执行其他事情,或者进入阻塞队列排队。

JDK 1.6 中引入了自适应的自旋锁来解决长时间自旋的问题

3.2.6中断锁|不可中断锁

​ reentrantLock是可中断锁

​ synchronized是不可中断锁

3.2.7synchronized

​ 锁升级(偏向锁-轻量级锁-重量级锁)

3.2.8AQS

​ 通过复写状态原子state、状态变更queue、队列管理Synchronizer实现方式实现各种线程同步

3.3死锁

3.3.1条件

​ 请求且保持

​ 资源互斥

​ 不剥夺

​ 循环等待

3.3.2定位

​ jstack命令top -Hd、jstack -pid|grep nid 找到deadlock

​ 数据库的话 show engine innodb status|grep DEADLOCK

3.3.3解决

​ 破坏四个条件

​ 代码里尽量使用juc包中的变量代替手写锁

​ 降低锁的粒度、设置超时时间、尽量按顺序加锁

3.4ThreadLocal

​ 定义:线程本地变量,用空间换时间,底层是map

​ 场景:数据库连接和session管理

​ simpleDateFormat

​ 注意:需要手动remove防止泄露

4.线程协作AQS

抽象队列同步器,里面记录了加锁

​ 状态原子性管理state

​ 加锁的线程信息

​ 队列的管理BlockingQueue

需要用户去实现

​ 阻塞与解除阻塞acquire&release方法

拿reenterantLock举例,每次加锁先判断是不是自己,是的话+1

其他线程CAS失败后进入等待队列,只有state变为0以后,才能重新获取到锁

通过不同的接口实现,可以封装不同的锁的类型,如:countDownLanch和cryclebarrie

三:中间件

5微服务案例

物业上有“物业大屏”微服务,当小区开通物业大屏服务之后,会根据已订购的相关业务自动组装相关的页面数据。其中有一个操作是:为指定小区开启一个定时任务,通过小区的auth_token,根据业务的订购关系,去统计微服务中通过服务调用的方式,获取历史的统计数据以及社区的概要信息。同时,通过消息总线获取(例如车辆、门禁、告警)的通行记录、告警等实时数据。

Spring经典问题

发现自己的配置总是不生效:properties实现了beanFactorypostProcessor,并重写了postProcessBeanFactory的方法,其中对于全部的properties进行合并使用新的配置文件覆盖老的实现去重。

SpingCloud框架经典问题

Eureka服务发现慢

后台上线一个新的业务,消费者无感知

后台下线一个新的业务,消费者无感知

EurekaClient 30s Ribben 30s

EurekaServer 两级缓存(只读缓存 30s + 读写缓存 30s)+内存hashMap

本来一致性就很差,一级缓存30s可以直接关掉或者缩小

SpringCloud组件超时

Feign的话可以不用管

Hystrix的超时时间需ribbon超时时间(timeout*retry)

组件经典问题

Nginx普通扩容时会导致原有的session不可用,扩展安装一致性hash模块,consistent_hash

Redis容错

项目经典问题

1:告警记录合并提示

我们首先要搭建一个统一的安防平台,经常会有这个需求,平台需要连接多个硬件产商做云云对接,然后不管是通行记录还是告警信息,我们都需要通过物业大屏的形式来展现。由于每个厂商都有自己的通行记录和告警的鉴权接口,我们需要统一获取到以后再进行聚合。然后在大屏幕上按时间进行展示。如果用串行的方式,厂商一多,通行记录还能接受,但是告警的时间就不准确了(考虑网络延迟一个延迟5秒,最后可能就延迟一分钟了。)

第一个想到的解决方式是并行的方式,即开启多个线程然后聚合起来。然后在收集结果的时候延迟一段时间,等到全部结果。但是这种情况对于网络特别好的时候,用户体验又很差,

所以当时我引入了completableFuture,好处就是可以在超时等待的同时,全部收到回复以后,可以立即返回结果。与这个功能类似的还有forkjoin、countDownLanch的实现,但是不管是awaitall还是join都是1.6之前比较局限的实现,但是对比了以后发现JDK1.8以后引入completableFuture可以不仅可以实现allof还可以实现anyof等扩展的功能。

2: webSocket断线重连

发送心跳包,判断超时时间和重试未响应次数,来判断断线剔除假死状态,由客户端发起重连。

Lua应用场景:游戏开发、独立应用脚本、Web应用脚本、扩展和数据库插件。

OpenRestry:一个可伸缩的基于Nginx的Web平台,是在nginx之上集成了lua模块的第三方服务器 OpenResty是一个通过Lua扩展Nginx实现的可伸缩的Web平台,内部集成了大量精良的Lua库、第三方 模块以及大多数的依赖项。 用于方便地搭建能够处理超高并发(日活千万级别)、扩展性极高的动态Web应用、Web服务和动态网 关。 功能和nginx类似,就是由于支持lua动态脚本,所以更加灵活,可以实现鉴权、限流、分流、日志记 录、灰度发布等功能。 OpenResty通过Lua脚本扩展nginx功能,可提供负载均衡、请求路由、安全认证、服务鉴权、流量控 制与日志监控等服务。 类似的还有Kong(Api Gateway)、tengine(阿里)

附录:相关流程

HashMap:put

put !(https://s0.lgstatic.com/i/image3/M01/73/D9/CgpOIF5rDYmATP43AAB3coc0R64799.png) img

HashMap:resize

img

线程状态

img

任务执行流程

img

非公平锁(CAS)

img

bean的注册

img