1、在浏览器地址栏键入url,按下回车之后经历的流程
1)DNS解析
2)TCP连接
3)发送HTTP请求
4)服务器处理请求并返回HTTP报文
5)浏览器解析渲染页面
6)连接结束
HTTP状态码
1xx : 指示信息,表示请求已接收,继续处理
2xx : 成功,表示请求已被成功接收、理解、接受
3xx : 重定向,要完成请求必须进行更进一步的操作
4xx : 客户端错误,请求有语法错误或请求无法实现
400:客户端请求语法错误,不能被服务器锁理解
401:请求未经授权,这个状态码必须和WWW-Authenticate报头域一起使用
403:服务器收到请求,但是拒绝提供服务
404:请求资源不存在,eg,输入错误的URL
5xx : 服务端错误,服务端未能实现合法请求
500:服务器有有发生不可预期的错误
503:服务器当前不能处理客户端的请求,一段时间后可能恢复正常
2、GET请求与POST请求的区别
1)HTTP报文层面:GET将请求信息放在URL,POST放在报文体中
2)数据库层面:GET符合幂等性和安全性,POST不符合
3)其他层面:GET可以被缓存、被存储,而POST不行
3、Cookie 和 Session 的区别
Cookie:
1)是由服务器发给客户端的特殊信息,以文本的形式发送到客户端
2)客户端再次请求的时候,会把Cookie回发
3)服务器收到后,回解析Cookie生成与客户端相对应的信息
Session:
1)服务器的机制,在服务器上保存的信息
2)解析客户端请求并操作session id,并按需保存状态信息
#数据库
1、慢查询
show variables like '%quer%'
show status like '%slow_queries%'
2、分析慢查询
explain 参数
explain select * from tables
关键字段 :type
system > const > eq_ref > ref > fulltext > ref_or_null
>index_merge > unique_subquery > index_subquery > range
> index > all
需要优化 : (index all 都是全表扫描)
关键字段 :extra
Using filesort : 外部排序
Using tempordery : 使用临时表
3、优化方案:
1)走索引
1、最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(> 、 < 、between、 like)就停止匹配,比如 a = 3 and b = 4 and c > 5 and d = 6
如果建立(a、b、c、d)组合索引,d是用不到索引的,如果建立(a、b、c、d)的索引则都可以用到,a,b,d的顺序可以任意调整。
2、= 和 in 可以
乱序,比如a = 1 and b = 2 and c = 3 建立(a、b、c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。
innoDb B + Tree
#缓存Redis
Redis Qps 10W+ QPS(QPS 即Query per second,每秒内查询次数)
1)完全基于内存,绝大部分请求时纯粹的内存操作,执行效率高
2)数据结构简单,对数据操作也简单
3)采用单线程,单线程也能处理高并发请求,想多核也可启动多实例
Redis 采用的I/O多路复用函数:epoll / kqueue / evport / select?
1) 因地制宜
2)优先选择时间复杂度为O(1) 的I/O多路复用函数作为底层实现
3)以时间复杂度为 O(n) 的select作为保底
4)基于react设计模式监听I/O事件
供用户使用的数据类型
1)String : 最基本的数据类型,二进制安全
2)Hash : String元素组成的字典,适合用于存储对象
hmset hget
3)List : 列表,按照String元素插入顺序排序
lpush lrange xxx 0 10
4)Set : String 元素组成的无序集合,通过哈希表实现,不允许重复
sadd myset 111
smembers myset
5)Sorted Set : 通过分数来为集合中的成员进行从小到大的排序
zadd myzset 3(分数) aaa
zrangebyscore myzset 0 10
6) 用于计数的HyperLogLog,用于支持存储地理位置信息的Geo
底层数据类型基础
1、简单动态字符串
2、链表
3、字典
4、跳跃表
5、整数集合
6、压缩列表
7、对象
#从海量key李查询出某一固定前缀的Key
使用keys对线上的业务影响
1)keys patterm : 查找所有符合给定模式pattern的key
a)keys指令一次性返回所有匹配的key
b) 键的数量过大会使服务卡顿
2)Scan cursor [MATCH pattern] [COUNT count] 无阻塞
a)j基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程
b)以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历
c)不保证每次执行都返回某个给定数量的元素,支持模糊查询
d)一次返回的数量不可控,只能是大概率符合count参数
scan 0 match ckl* count 10
#Redis 实现分布式锁
分布式锁需要解决的问题
1)互斥性
2)安全性
3)死锁
4)容错
SETNX key value : 如果key不存在,则创建并赋值
a)时间复杂度 : O(1)
b)返回值: 设置成功,返回1; 设置失败, 返回 0
EXPIRE key seconds
a)设置key的生存时间,当key过期时(生存时间为0),会被自动删除
b)缺点: 原子性得不到满足
SET key value [EX seconds] [PX milliseconds] [NX|XX]
a) EX seconds : 设置键的过期时间为seconds秒
b)PX millisecond : 设置键的过期时间为 millisecond 毫秒
c) NX : 只在键不存在时, 才对键进行设置操作
d) XX : 只在键已经存在时,才对键进行设置操作
e)SET 操作成功完成时,返回ok,否则返回nil
大量的key同时过期的注意事项
集中过期,由于清楚大量key很耗时间,会出现短暂的卡顿现象
a) 解决方案: 在设置key的过期时间的时候,给每个key加上随机值
实现消息队列
a)利用List的 rpush(生产信息) lpop(消费信息)
1)缺点:没有等待队列里有值就直接消费
2)弥补:可以通过在应用层引入Sleep机制去调用LPOP重试
b)BLPOP key [key ...] timeout: 阻塞知道队列有信息或者超时
1)缺点:只能提供一个消费者消费
c)pub/sub: 发送订阅者模式
1)发送者(bub)发送消息,订阅者(sub)接收消息
2)订阅者可以订阅任意数量的频道
subscribe myTopic(订阅主题)
publish myTopic “Hello” (发布主题)
消息的发布时无状态的,无法保证可达
#Redis持久化
1)RDB(快照)持久化: 保存某个时间点的全量数据快照
a)save : 阻塞Redis的服务器进程,直到RDB文件被创建完毕
b)BGSAVE : fork出一个子进程来创建RDB文件,不阻塞服务器进程
c)根据redis.conf 配置里的save m n 定时触发(用的是BGSAVE)
d)主从复制时,主节点自动触发
e)执行Debug Reload
f)执行Shutdown 且没有开启AOF持久化
g) 缺点:内存数据的全量同步,数据量大会由于I/O而严重影响性能
可能会因为Redis挂掉而丢失从当前至最近一次快照期间的数据
2)AOF(Append-Only-File) 持久化:保存写状态
a) 记录下除了查询以外的所有变更数据库状态的指令
b) 已append的形式追加保存到AOF文件中(增量)
日志重写解决AOF文件大小不断增大的问题,原理如下:
a)调用fork(), 创建一个子进程
b)子进程吧新的AOF写到一个临时文件里,不依赖原来的AOF文件
c)主进程持续将新的变动同时写到内存和原来的AOF里
d)主进程获取子进程重写AOF的完成信号,往新AOF同步增量变动
f)使用新的AOF文件替换掉就的AOF文件
3)RDB 和 AOF 文件中共存情况下的恢复流程 (重启redis就可以了,先检查是否存在AOF,若有则恢复AOF;若AOF不存在则检查RDB)
4)RDB 和 AOF 的优缺点
a) RDB优点: 全量数据快照,文件小,恢复快
b) RDB缺点: 无法保存最近一次快照之后的数据
c) AOF优点: 可读高,适合保存增量数据,数据不易丢失
d) AOF缺点: 文件体积大,恢复时间长
5) RDB-AOF 混合持久化方式
BGSAVE 做镜像全量持久化,AOF做增量持久化
使用Pipeline的好处
a)Pipeline 和 Linux 的管道类似
b)Redis 基于请求/响应模型,单个请求处理需要一一应答
c)Pipeline批量执行指令,节省多次IO往返时间
d)有顺序依赖的指令建议分批发送
Redis的同步机制
主从同步原理
a) 全同步过程
1)Salve发送sync命令道master
2)Master启动一个后台进程,将Redis中的数据快照保存到文件中
3)Master将保存数据快照期间接收到的命令缓存起来
4)Master完成写文件操作后,将文件发送给Salve
5)使用新的AOF文件替换掉旧的AOF文件
6)Master将这期间收集的增量写命令发送给Salve
b)增量同步过程
1)Master接收到用户的操作命令,判断是否需要传播到Slave
2)将操作记录追加到AOF文件
3)将操作传播到其他Slave: 1、对齐主从库; 2、往响应缓存写入指令
4)将缓存中的数据发送给Slave
c)Redis Sentinel(哨兵模式,当master宕机之后,选出一个slave 做master)
1)解决主从同步Master宕机后的主从切换问题:
a)监控: 检查主从服务器是否运行正常
b)提醒: 通过API向管理员或者其他应用程序发送故障通知
c)自动故障迁移: 主从切换
d) 流言协议Gossip:在杂乱无章中寻求一致
1)每个节点都随机地与对方通讯,最终所有节点的状态达成一致
2)种子节点定期随机向其他节点发送节点列表以及需要传播的消息
3) 不保证信息一定会传递给所有节点,但是最终会趋于一致
Redis的集群原理
1)分片:按照某种规则去划分数据,分散存储在多个节点上
2)常规的按照哈希划分无法实现节点的动态增减
一致性哈希算法: 对2^32取模,将哈希值空间组织成虚拟的圆环
##Linux
#如何查找特定的文件
find path [options] params
find -name "ckl.java" 精确查找文件
find ~ -name "ckl*" 模糊查找文件
find ~ -iname "ckl*" 忽略大小写模糊查找文件
man find
检索文件内容
grep
grep [option] pattern file
查找文件里符合条件的字符串
grep “ckl” ckl* 在含有ckl文件名的文件里面是否存在ckl
管道操作符 |
可将指令连接起来,前一个指令的输出作为后一个指令的输入
find ~ -name “ckl*” 等价于 find ~ | grep “ckl”
sed、awk、grep、cut、head、top、less、more、wc、join、sort、split
常用
grep 'partial\[true\]' bsc-plat-al-data.info.log
grep -o 'engine\[0-9a-z]*\'
grep -v 'grep'
awk
awk [options] 'cmd' file
一次读取一行文本,按输入分隔符进行切片,切成多个组成部分
awk '{print $1, $4}' netstat.txt 打印第一行第四行
awk '$1 == "tcp" && $2 == 1{print $0}' netstat.txt
批量替换文件内容
sed -i 's/\.$/;/' ckl.java
##JVM
平台无关性
1)编译时
2)运行时
java源码首先被编译成字节码,再由不同平台的JVM进行解析,
Java语言在不同的平台上运行时不需要进行重新编译,Java虚拟机在执行字节码的时候,
把字节码转换成具体平台上的机器指令。
Jvm如何加载.class文件
1)Class Loader : 依据特定格式,加载class文件到内存
2)Execution Engine : 对命令进行解析
3) Native Interface : 融合不同开发语言的原生库为Java所用
4)Runtime Data Area : Jvm内存空间结构模型
反射
java反射机制是运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意方法和属性;
这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
Class class = Class.forName("com.Ljava.ckl.fs");
Test r = (Test) class.newInstance();
Method getTest = class.getDeclareMethod("hello", String.class); //不能获取继承 和 接口方法
getTest.setAccessible(true);//权限
Object str = getTest.invoke(r, "ckl"); //传入参数: ckl
ClassLoader
ClassLoader 在java中有着非常重要的作用,它主要工作在Class装载的加载阶段,其主要是从系统外部
获得Class二进制数据流。它是Java的核心组件,所有的Class都是由ClassLoader进行加载的,
ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等操作。
ClassLoader的种类
1)BootStarpClassLoader : C++编写,加载核心库Java.*;
2) ExtClassLoader : Java编写,加载扩展库javax.*
3) AppClassLoader : Java编写,加载程序所在的目录
4)自定义ClassLoader : Java编写,定制化加载
为什么要使用双亲委派机制去加载类
1) 避免多份同样字节码的加载
类的加载方式
1)隐式加载: new
2)显示加载: loadClass 、 forName 等
loadClass 、 forName的区别
类的装载过程
1)加载:通过ClassLoader加载class文件字节码,生成Class对象
2)链接:a) 校验: 检查加载的class的正确性和安全性
b)准备: 为类变量分配存储空间并设置类变量初始化值
c)解析: JVM将常量池内的符号引用转换为直接引用
3)初始化: 执行类变量赋值和静态代码块
地址空间划分
1)内核空间
2)用户空间 (java使用)
JVM内存模型 -- JDK8
1)线程私有:程序计算器【字节码指令 no OOM】、虚拟机栈【java方法 SOF && OOM】、本地方法栈【native方法SOF && OOM】
2)所有线程共享: MetaSpace【(元空间)类加载信息OOM】、堆【数组和类对象OOM (常量池(字面量和符号引用量OOM)】
程序计数器:
1)当前线程所执行的字节码行号指示器(逻辑)
2)改变计数器的值来选取下一条需要执行的字节码指令
3) 和线程是一对一的关系即“线程私有”
4) 对Java方法计数,如果是Native方法则计数器值为Undefined
5)不会发生内存泄漏
Java虚拟机栈(Stack)
1)Java方法执行的内存模型
2)包含多个栈帧(帧:局部变量表、操作栈、动态连接、返回地址)
a) 局部变量表:包含方法执行过程中的所有变量
b) 操作数栈:入栈、出栈、复制、交换、产生消费变量
递归为什么会引发Java.lang.StackOverflowError异常
递归过深,栈帧数超出虚拟栈深度
虚拟机栈过多会引发java.lang.OutOfMemoryError异常
public void stackLeakByThread(){
while(true){
new Thread(){
public void run(){
while(true){}
}
}.start();
}
}
本地方法栈
1)与虚拟机栈相似,主要作用于标注了native的方法
## 内存模型
元空间(MetaSpace)与 永久代(PermGen)的区别
1)元空间使用本地内存,而永久代使用的是Jvm的内存
MetaSpace 相比 PermGen 的优势
1)字符串常量池存在永久代中,容易出现性能问题和内存溢出
2)类和方法的信息大小难以确定,给永久代的大小指定带来困难
3)永久代会为GC带来不必要的复杂性
4)方便HotSpot与其他JVM如Jrockit的集成
##java堆(Heap) java管理最大的一块内存,管理对象
1)对象实例的分配区域: OS and C-runtime | JVM | NativeHeap | java heap(s)
2) GC管理的主要区域 :1、Eden | 2、Survivor | 3、Survivor | 4、renured
==> 新生代123, 老年代4
##JVM三大性能调优参数 -Xms -Xmx -Xss 的含义
1) -Xss: 规定了每个线程虚拟机栈(堆栈)的大小
2)-Xms: 堆的初始值
3)-Xms: 堆能达到的最大值 (达到最大值后还需要扩容就会发生内存抖动)
##java内存模型中堆和栈的区别 -- 内存分配策略
1)静态存储: 编译时确定每个数据目标在运行时的存储空间需求
2)栈式存储: 数据区需求在编译时未知,运行时模块入口前确定
3)堆式存储: 编译时或运行时模块入口都无法确定,动态分配
4)联系:引用对象、数组时,栈里定义变量保存堆中目标的首地址
在创建一个对象的时候,先在堆开辟一块空间,然后将堆空间的首地址保存到栈中
答案:
a)管理方式: 栈自动释放,堆需要GC
b)空间大小: 栈比堆小
c)碎片相关: 栈产生的碎片远小于堆
d)分配方式: 栈支持静态分配(系统分配)和动态分配,而堆仅支持动态分配
e)效率: 栈的效率比堆高
##元空间、堆、线程独占部分空间的联系 -- 内存角度
public class HelloWorld{
private String name;
public void sayHello(){
System.out.pritln("Hello " + name);
}
public void setName(String name){
this.name = name;
}
public static void main(String[] args){
int a = 1;
HelloWorld hw = new HelloWorld();
hw.setName("test");
hw.sayHello();
}
}
各部分内存的保存情况:
元空间: Class : HelloWrold - Method: sayHello\setName\main -Field:name
Class : System
Java堆: Object : String("test")
Object : HelloWorld
线程独占: Parmeter reference : “test” to String object
Variable reference : "hw" to HelloWorld object
Local Variable : a with 1 , lineNo
##Java垃圾回收机制
对象被判定为垃圾得标准
1)没有被其他对象引用
判定对象是否为垃圾得算法
1)引用计数算法
a) 通过判断对象的引用数量来决定对象是否可以被回收
b) 每个对象实例都有一个引用计数器,被引用则 +1, 完成引用则 -1
c) 任何引用计数为0的对象实例可以被当作垃圾收集
优点:执行效率高,程序执行影响较小
缺点:无法检测出循环引用的情况,导致内存泄漏
2)可达性分析算法
a) 通过判断对象的引用链是否可达来决定对象是否可以被回收
b) 可以作为GC Root 的对象
虚拟机栈中的引用对象(栈帧中的本地变量表)
方法区中的常量引用的对象
方法区中的类静态属性引用的对象
本地方法栈中JNI(Native方法)的引用对象
活跃线程的引用对象
垃圾回收算法
1)标记-清除算法(Mark and Sweep)
a)标记 : 从根集合进行扫描,对存活的对象进行标记
b)清除 : 对堆内存从头到尾进行线性遍历,回收不可达对象内存
内存碎片化
2)复制算法(Copying)
a) 分为对象面和空闲面
b) 对象在对象面上创建
c) 存活的对象被从对象面复制到空闲面
d) 将对象面所有对象内存清除
解决碎片化问题
顺序分配内存,简单高效
适用于对象存活率低的场景
目前一般商业化用于 年轻代的回收,因为年轻代回收的存活率在 10% 左右
3)标记-整理算法(Compacting)
a)标记: 从根集合进行扫描,对存活的对象进行标记
b)清除: 移动所有存活的对象,且按照内存地址次序依次排列,
然后将末端内存地址以后的内存全部回收。
避免内存的不连续行
不用设置两块内存互换
适用于存活率高的场景
4)分代收集算法(Generational Controllor)
a) 垃圾回收算法的组合拳
b) 按照对象生命周期的不同划分区域以采用不同的垃圾回收算法
c) 目的: 提高JVM的回收效率
jdk 1.6、1.7 存在永久代
jdk 1.8以后,移除了永久代
GC的分类
1)Minor GC (标记清除算法 -- 年轻代)
2) Full GC (-- 老年代)
Eden from to
8/10 1/10 1/10
|____Young____| Old区
新生代(1/3堆空间) 老年代(2/3堆空间)
年轻代:尽可能快速地收集掉那些生命周期短的对象
1)Eden区
2)两个Survicor(幸存者空间 )区
Eden被存满后触发Minor GC,
将 Eden 和 其中一个 Survicor区 的幸存者存放到另一个 Survicor区,幸存者年龄 + 1。
—XX:MaxTenuringThreshlod 参数设置 Survicor区的对象达到一定的年龄(默认15),
就可以复制到老年代,但是 15 不是一定就会被复制到老年代的
对象如何晋升到老年代
1)经历一定Minor次数依然存活的对象
2)Survivor区中存放不下的对象
3)新生成的大对象(-XX:+PretenuerSizeThreshold)
常用的调优参数
-XX:SurvicorRatio :Eden 和 Survivor 的比值,默认 8 : 1
-XX:NewRatio :老年代和年轻代内存大小的比例
—XX:MaxTenuringThreshlod : 对象从年轻代晋升到老年代经过GC次数的最大阈值
老年代:存放生命周期较长的对象(对象存活率高)
1)标记-清理算法
2)标记-整理算法
Full GC 和 Major GC
Full GC 比 Minor GC 慢, 但执行频率低
触发Full GC 的条件
1)老年代的空间不足
2)永久代空间不足(jdk7以前,包括7),主要是为了减低Full GC 的频率,所以用MateSpace(元空间取代 永久代)
3)CMS GC 时出现 Promotion failed, concurrent mode failure
4) Minor GC 晋升到老年代的平均大小 大于 老年代的剩余空间
5)调用 System.gc() (虚拟机来决定执不执行GC)
6)使用RMI来进行RPC 或 管理的JDK应用,每小时执行一次Full GC
Stop-the-world
1) JVM 由于要执行GC而停止了应用程序的执行
2)任何一种GC算法中都会发生
3) 多数GC优化通过减少 Stop-the-world 发生时间来提高程序性能
Safepoint(JVM 安全点)
1)分析过程中对象引用关系不会变化的点
2)产生Safepoint的地方:方法调用;训话跳转;异常跳转等
3)安全点数量得适中
常见垃圾收集器
JVM的运行模式:
1)Server :启动慢,但是长期运行,稳定性比Client强
2)Client :启动快,但是长期运行,稳定性较Server差
垃圾收集器之间的联系
年轻代常见垃圾收集器:
Serial收集器(-XX:+UseSerialGC, 复制算法)
1)单线程收集,进行垃圾收集时,必须暂停所有工作线程
2)简单高效,Client模式下默认的年轻代收集器
ParNew收集器(-XX:UseParNewGC,复制算法)
1)多线程收集,其余行为、特点和Serial收集器一样
2)单核执行效率不如Serial,在多核下执行才有优势
Parallel Scavenge收集器(-XX:+UseParallelGC, 复制算法)
1)吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
2)比起关注用户线程停顿时间,更关注系统吞吐量
3)在多核下执行才有优势,Server模式下默认年轻代收集器
可以通过 该参数 -XX:+UseAdaptiveSizePolicy 将内存管理调优交由虚拟机去完成
老年代常见的来及收集器:
Serial Old 收集器(-XX:+UseSerialOldGC, 标记-整理算法)
1)单线程收集,进行垃圾收集时,必须暂停所有工作线程
2)简单高效,Client模式下默认的老年代收集器
Parallel Old ( -XX: UseParallelOldGC, 标记 - 整理算法)
1)多线程, 吞吐量优先
CMS收集器(-XX:+UseConcMarkSweepGC, 标记 - 清除算法)
1)初始标记:stop-the-world
2) 并发标记:并发追溯标记,程序不会停顿
3)并发预清理: 查找执行并发标记阶段从年轻代晋升到年老代的对象
4)重新标记:暂停虚拟机,扫描CMS堆中的剩余对象
5)并发处理: 清理垃圾对象,程序不会卡顿
6)并发重置:重置CMS收集器的数据结构
G1收集器(-XX: +UseG1GC, 复制 + 标记-整理算法)
Garbage First收集器的特点:
1)并行和并发
2)分代收集
3)空间整合
4)可预测的停顿
5)将整个Java堆内存划分成多个大小相等的Region
6)年轻代和老年代不再物理隔离
##GC 面试题
1)Object的finalize() 方法的作用是否与C++的析构函数作用相同
a) 与C++的析构函数不同,虚构函数调用确定,而它的是不确定的
b) 将未被引用的对象放置于F-Queue队列
c) 方法执行随时可能会被终止
d) 给予对象最后一次重生的机会
2)java中的强引用,软引用,弱引用,虚引用有什么用
强引用:
a) 最普遍的引用: Object obj = new Onject();
b)抛出OutOfMemoryError 终止程序也不会回收具有强引用的对象
c) 通过将对象设置为null来弱化引用,使其被回收
软引用:
a) 对象处在有用但非必须的状态
b) 只有当内存空间不足时,GC会回收该引用的对象的内存
c) 可以用来实现高速缓存
String str = newString("ckl"); //强cccccx引用
SoftReference softRef = new SoftReference(Str); // 软引用
弱引用:
a) 非必须对象,比软引用更弱一些
b) GC 时会被回收
c) 被回收的概率也不大,因为GC线程优先级比较低
d) 适用于引用偶尔被使用且不影响垃圾收集的对象
String str = newString("ckl"); //强引用
WeakReference softRef = new WeakReference(Str); // 弱引用
虚引用:
a) 不会决定对象的生命周期
b) 任何时候都可能被垃圾收集器回收
c) 跟踪对象被垃圾收集器回收的活动,起哨兵作用
d) 必须和引用队列ReferenceQueue联合使用
String str = newString("ckl"); //强引用
ReferenceQueue queue = new ReferenceQueue();
PhantomReference<> ref = new PhantomReference(str, queue); // 虚引用
引用类型 被垃圾回收时间 用途 生存时间
强引用 从来不会 对象的一般状态 JVM停止运行时终止
软引用 在内存不足时 对象缓存 内存不足时终止
弱引用 在线程回收时 对象缓存 gc运行后终止
虚引用 Unknown 标记、哨兵 Unknown
## java线程
Thread 中的start 和 run 方法的区别
1)调用start() 方法会创建一个新的子线程并启动
2)run() 方法只是Thread的一个普通方法的调用
Thread 和 Runnable 是什么关系
1)Thread 是实现了Runnable 接口的类,使得run支持多线程
2)因类的单一继承原则,推荐多使用Runnable接口
如何给 run() 方法传参
1)构造函数传参
2)成员变量传参
3)回调函数传参
如何实现处理线程的返回值
1)主线程等待法
2)使用Thread 类的 join() 阻塞当前线程以等待子线程处理完毕
3)通过Callable 接口实现: 通过FutureTesk Or 线程池获取
线程的状态
1)new (新建): 创建后尚未启动的线程的状态
2)runnable (运行) : 包含Running 和 Ready(等待cpu分配时间)
3)waiting (无限期等待)(object.wait() / Thread.join() / LockSupport.park()) : 不会被分配cpu执行时间,需要显式被唤醒
4)timed_waiting (限期等待) (object.wait(xxx) / Thread.join(xxx) / LLockSupport.parkNanos()/ ockSupport.parkUntil()): 在一定时间后会由系统自动唤醒
5)blocked (阻塞) : 等待获取排它锁
6)terminated (结束) : 已终止线程的状态, 线程已经结束执行
sleep 和 wait 的区别
1)sleep 是Thread类的方法, wait 是 Object 类中定义的方法
2)sleep() 方法可以在任何地方使用
3)wait() 方法只能在 synchronized() 方法或 synchronized块中使用 (在里面已经获取锁,wait可以释放锁)
最重要的本质区别
1)Thread.sleep 只会让出cpu,不会导致锁行为的改变
2)Object.wait 不仅让出CPU, 还会释放已经占有的同步资源锁
notify 和 notifyall 的区别
1)锁池 EntryList
假设线程A已拥有了某个对象(不是类)的锁,而其他线程B、C想要调用这个对象的某个synchronized方法(或模块),由于B、C线程在进入对象的synchronized方法(或模块)
之前必须获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是给对象的锁池。
2)等待池 WaitSet
假设线程A调用了某个对象的wait() 方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池,进入等待池的线程就不会去竞争该对象的锁。
3)notifyall 会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会
4)notify 只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会
yield
1)概念 : 当调用Thread.yiled() 函数时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示。
如何中断线程
1)已经被抛弃的方法:
a)通过调用stop() 方法停止线程
b) 通过调用suspend() 和 resume() 方法
2)目前使用的方法
a) 如果线程处于被阻塞状态,name线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
b) 如果线程处于正常活动状态,name会将线程的中断标志设置为true。被设置中断标志的线程将继续运行,不受影响。
3) 需要被调用的线程配合中断
a) 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
b) 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,被设置中断标志的线程将会继续正常运行,不受影响。
synchronized
1)线程安全问题的主要诱因
a) 存在共享数据(也称临界资源)
b) 存在多条线程共同操作这些共享数据
解决问题的根本方法:
同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再对共享数据进行操作。
互斥锁的特性
1)互斥性:即在同一时间只允许一个线程持有某个对象的锁,通过这种特性来实现多线程的协调机制,这样在同一时间只有一个线程
对需要同步的代码块(复合操作)进行访问。互斥性也称为操作的原子性。
2)可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程三生三世可见的(即获得锁时应获得最新的共享变量的值),
否则另一个线程可能是在本地缓存的某个副本上继续操作,从而引起不一致。
synchronized锁的不是代码,锁的都是对象
根据获取的锁的分类:获取对象锁和获取类锁
获取对象锁的两种用法:
1)同步代码块(synchronized(this),synchronized(类实例对象)), 锁时小括号()中的实例对象。
2)同步非静态方法(synchronized method),锁是当前对象的实例对象。
获取类锁的两种用法:
1)同步代码块(synchronized(类.class), 锁时小括号()中的类对象(Class对象)。
2)同步静态方法(synchronized method),锁是当前对象的类对象(Class对象)。
synchronized底层实现原理
实现synchronized的基础
1)Java对象头
2)Monitor
对象在内存中的布局
1)对象头
2)实例数据
3)对齐填充
对象头的结构
虚拟机位数 头对象结构 说明
32/64 bit Mark Word 默认存储对象的hashCode,分代年龄,锁类型,锁标志位等信息
32/64 bit Class Metadta Address 类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的数据
Monitor : 每个java对象天生自带了一把看不见的锁
什么是重入
从互斥性的设计上来说,当一个线程试图操作一个其他线程持有的对象的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入。
为什么会对synchronized不爽
1)早期版本中,synchronized属于重量级锁,依赖于Mutex Lock实现
2)线程之间的切换需要从用户态转换到核心态,开销较大
java6之后,synchronized性能得到了很大的提升(hotSpot优化)
1)自旋锁
a) 许多情况下,共享数据的锁定持续时间较短,切换线程不值得
b) 通过让线程执行忙循环等待锁的释放,不让出CPU
c) 缺点:若锁被其他线程长时间占用,会带来许多性能上的开销
2)自适应自旋锁
a) 自旋的次数不固定
b) 由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
3)锁消除
更彻底的优化: JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁。
4)锁粗化
a)通过扩大锁的范围,避免反复加锁和解锁。
synchronized的四种状态
1)无锁、偏向锁、轻量级锁、重量级锁
锁膨胀方向: 无锁 ——》 偏向锁 ——》 轻量级锁 ——》 重量级锁
a) 偏向锁 : 减少同一线程获取锁的代价
1)大多数情况下,锁不存在多线程竞争,总是由同一个线程多次获得
核心思想:
如果一个线程获得了锁,那么锁进入偏向模式,此时Mark Word的结构也变为偏向锁结构,当该线程再次请求锁时,
无需再做任何同步操作,即获取锁的过程只需要检查Mark Word的锁标记位为偏向锁
以及当前线程Id等于Mark Word的ThreadID即可,这样就省去了大量有关锁申请的操作。
不适用于锁竞争比较激烈的多线程场合。
b)轻量级锁
1)轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,
偏向锁就会升级为轻量级锁。
适应场景:线程交替执行同步块
若存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
锁的内存语义
当线程释放锁时,Java内存模型会把该线程对应的本地内存中的共享变量刷新到主内存中;
而当线程获取锁时,Java内存模型会把该线程对应的本地内存置为无效,从而使得监控器保护的临界区代码必须从主内存中读取共享变量。
锁 优点 缺点 使用场景
偏向锁 加锁和解锁不需要CAS操作, 如果线程之间存在锁竞争, 只有一个线程访问同步块
没有额外的性能消耗,和执行 会带来额外的锁撤销的消耗 或者同步方法的场景
非同步方法相比仅存在纳秒级的差距
轻量级锁 竞争的线程不会阻塞,提高了响应速度 若线程长时间抢不到锁, 线程交替执行同步块或者同步方法的场景
自旋会消耗CPU性能
重量级锁 线程竞争不适用自旋,不会消耗CPU 线程阻塞,响应时间缓慢, 追求吞吐量,同步块或同步方法执行时间较长的场景
在多线程下,频繁的获取释放锁,
会带来巨大的性能消耗
synchronized 和 ReentrantLock 的区别
1)ReentrantLock(再入锁)
a) 位于java.util.concurrent.locks包
b) 和CountDownLatch、FutureTask、Semaphore一样基于AQS(AbstractQueuedSynchronizer)实现
c) 能够实现比synchronized更细粒度的控制,如控制fairness
d) 调用lock()之后,必须调用unlock()释放锁
e) 性能未必比synchronized高,并且也是可重入的
2)ReentrantLock 公平性的设置
a) ReentrantLock fairLock = new ReentrantLock(true);
b)参数为true时,倾向于将锁赋予等待时间最久的线程
c) 公平锁 : 获取锁的顺序按先后调用lock方法的顺序(慎用)
d) 非公平锁 : 抢占的顺序不一定,看运气
e) synchronized是非公平锁
3) ReentrantLock将锁对象化
a) 判断是否有线程,或者某个特定线程,在排队等待获取锁
b) 待超时的获取锁的尝试
c) 感知有没有成功获取锁
4)将wait、notify、notifyall对象化
a) java.util.concurrent.locks.Condtion
synchronized 是关键字 ReentrantLock 是类
ReentrantLock 可以对获取锁的等待时间进行设置,避免死锁
ReentrantLock 可以获取各种锁的信息
ReentrantLock 可以灵活地实现多路通知
机制:sync 操作 Mark word , lock 调用 Unsale 类的 park() 方法
java内存模型 JMM
java内存模型(即java Memory Model,简称 JMM) 本身是一种抽象的概念,并不真实存在,它描述的是一组规则或者规范,
通过这组规范定义了程序中各个变量(包括实力字段,静态字段和构成数组对象的元素)的访问方式。
JMM中的主内存
1)存储java实例对象
2)包括成员变量、类信息、常量、静态变量等
3)属于数据共享的区域,多线程并发操作时会引发多线程安全问题
JMM 的工作内存
1)存储当前方法的所有本地方法变量,本地变量对其他线程不可见
2) 字节码行号指示器、native方法信息
3)属于线程私有数据区域,不存在线程安全问题
JMM 与 java内存区域划分是不同的概念层次
1)JMM描述的是一组规则,围绕原子性、有序性、可见性展开
2)相似点 : 存在共享区域和私有区域
主内存与工作内存的是数据存储类型以及操作方式归纳
1) 方法里的基本数据类型本地变量将直接存储在工作内存的栈帧结构中
2) 引用类型的本地变量 : 引用存储在工作内存中,实例存储在主内存中
3) 成员变量、static变量、类信息均会被存储在主内存中
4) 主内存共享的方式是线程拷贝一份数据到工作内存,操作完成后刷新回主内存
指令重排序需要满足的条件
1) 在单线程环境下不能改变程序运行的结果
2) 存在数据依赖关系不允许重排序
3)无法通过happens-before原则推导出来的,才能进行指令的重排序
A操作的结果需要对B操作可见,则AyuB存在happens—before关系
volatile : JVM 提供的轻量级同步机制
1)保证被volatile 修饰的共享变量对所有的线程总是可见的
2)禁止指令的重排序优化
volatile的可见性
volatile变量为何立即可见
当写一个volatile变量时,JMM会把该线程对应的工作内存中的共享变量值刷新到主内存中;
当读取一个volatile变量时,JMM 会把该线程对应的工作内存置为无效。
volatile如何禁止重排优化
内存屏障(Memory Barrier)
1)保证特定操作的执行顺序
2)保证某些变量的内存可见性
通过插入内存屏障指令禁止在内存屏障前后的指令执行重排序优化
强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本
volatile 和 synchronized 的区别
1)volatile 本质是在告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主内存中读取;
synchronized则是所动当前变量,只有当前线程可以访问该变量,其他线程被阻塞住直到该线程完成变量操作为止。
2)volatile 仅能使用在变量级别; synchronized 则可以使用变量、方法和类级别
3)volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量修改的可见性和原子性。
4)volatile不会造成线程的阻塞;synchronized可能造成线程的阻塞
5)volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
CAS(Compare and Swap)
一种高效实现线程安全性的方法
1)支持原子更新操作。适用于计数器,序列发生器等场景
2)属于乐观锁机制,号称lock-free
3)cas操作失败时有开发者决定是继续尝试,还是执行别的操作
cas思想
1)包含三个操作数 -- 内存位置(V)、预期原值(A)和新值(B)
cas多数情况下对开发者来说是透明的
1)J.U.C的atomic包提供了常用的原子性数据类型以及引用、数组等相关院子类型和更新操作工具,是很多线程安全程序的首选
2)Unsale类岁提供CAS服务,但因能够操纵任意内存地址读写而有隐患
3)java9以后,可以使用Variable Handle Api来替代Unsale
CAS缺点
1)若循环时间长,则开销很大
2)只能保证一个共享变量的原子操作
3)ABA问题(内存初次读取的位置是A,在准备赋值的值也为A,也会有问题(有可能会被改为B再改回来A))
解决ABA问题: AtomicStampedReference
Java线程池
利用Executors创建不同的线程池满足不同场景的需求
1.newFixedThreadPool(int nThreads)
指定工作线程数量的线程池
2.newCachedThreadPool()
处理大量短时间工作任务的线程池
a)试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程
b)如果线程闲置的时间超过阈值,则会被终止并移除缓存
c)系统长时间闲置的时候,不会消耗什么资源
3.newSingleThreadExecutor()
创建唯一的工作者线程来执行任务,如果线程异常结束,会有另一个线程取代它。
4.newSingleThreadSchduledExecutor() 与 newSingleThreadExecutor(int corePoolSize)
定时或者周期性的工作调度,两者的区别在于单一工作线程还是多个线程
5.newWorkStralingPool()
内部会构建ForkJoinPool,利用working-stealing算法,并行地处理任务,不保证处理顺序
Fork/Join框架
1)把大任务分割成若干个小任务并行执行,最终汇总每个小任务结果后得到大任务结果的框架
J.U.C 的三个Executor接口
1)Executor : 运行新任务的简单接口,将任务提交 和任务执行细节解耦
2)ExecutorService : 具备管理执行器和任务生命周期的方法,提交任务机制更完善
3)ScheduledExecutorService : 支持Future和定期执行任务
ThreadPoolExecutor的构造函数
1) corePoolSize : 核心线程数
2) maximumPoolSize : 线程不够用时能够创建的最大线程数
3) workQueue : 任务等待队列
4) keepAliveTime : 抢占的顺序不一定,看运气
5) threadFactory : 创建新线程,Executors.defaultThreadFactory()
6) handler : 线程池的饱和策略
a) AbortPolicy : 直接抛出异常,这是默认策略
b)CallerRunPolicy : 用调用者所在的线程来执行任务
c) DiscardOldestPolicy : 丢弃队列中靠前的任务,并执行当前任务
d) DiscardPolicy : 直接丢弃任务
实现RejectedExecutionHandler接口的自定义handler
新任务提交execute执行后的判断
1)如果运行的线程少于corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程空闲的;
2)如果线程池中的线程量大于等于corePoolSize且小于maximumPoolSize,则只有当workQueue满时才
创建新的线程去处理任务;
3)如果设置的corePoolSize和maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新的任务提交,若workQueue未满,
则将请求放入workQueue中,等待有空闲的线程从workQueue中取任务并处理;
4)如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务
Java线程池的状态
1)running : 能接受新提交的任务,并且也能处理阻塞队列中的任务
2)shutdown : 不再接受新提交的任务,但可以处理存量任务
3)stop : 不再接受新提交的任务,也不处理存量任务
4)tidying:所有新的任务都已终止
工作线程的生命周期
开始 -》execute —》增加worker -》 创建worker线程 —》runWorker -》 获取任务 -》 task != NULL (执行任务) -》 结束线程
线程池的大小如何选定
1)CPU密集型 : 线程数 = 按照核数或者(核数+1)设定
2)I/O密集型 : 线程数 = CPU核数 * (1 + 平均等待时间 / 平均工作时间 )
String、StringBuffer、StringBuilder的区别
## Java异常
异常处理机制主要回答了三个问题
1) What : 异常类型回答了什么被抛出
2) Where : 异常堆栈跟踪回答了在哪抛出
3) Why : 异常信息回答了为什么被抛出
java异常体系
Error和Exception的区别
---Throwable---
| |
Error ---Exception---
| |
RuntimeException 非RuntimeException
1)Error : 程序无法处理的系统问题,编译器不做检查(一般都是说JVM相关的问题,程序无法处理的错误 )
2)Exception :程序可以处理的异常, 捕获后可能恢复
3)总结 : 前者是程序无法处理的错误,后者是可以处理的异常
4)RuntiemException : 不可预知的,程序应当自行避免
5)非RuntiemException : 可预知的,从编译器校验的异常
从责任角度看:
1) Error属于JVM需要承担的责任
2) RuntimeException 是程序应该负担的责任
3) Check Exception 可检查异常时Java编译器应该负担的责任
常见的Error 以及 Exception
RuntimeException
1) NullPointerException
2) ClassCastException
3) IllegalArgumentException
4) IndexOutOfBoundsException
5) NumberFormatException
非RuntimeException
1) ClassNotfoundException 找不带指定classs的异常
2) IOException
Error
1) BoClassDefFoundError 找不到class定义的异常
2)StackOverflowError 深入递归导致栈被耗尽而抛出的异常
3)OutOfMemoryError 内存溢出
JUC并发工具类
1)闭锁 CountDownLatch : 让主线程等待一组事件发生后继续执行
2)栅栏 CyclicBarrier : 阻塞当前线程,等待其他线程
3)信号量 Semaphore : 控制某个资源可被同时访问的线程个数
4)交换器 Exchanger : 两个线程到达同步点后,相互交换数据
BlockingQueue :提供可阻塞的入队和出队操作
主要用于生产者-消费者模式,在多线程场景时生产者线程在队列尾部添加元素,
而消费者线程则在队列头部消费元素,
通过这种方式能够达到将任务的生产和消费进行隔离的目的。
BIO、NIO、AIO
Block-IO : InputStream 和 OutputStream, Reader 和 Writer
NonBlock-IO : 构建多路复用、同步非阻塞的IO操作
NIO的核心:
1)Channels (可以通过Channels到Buffers):
FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel
2)Buffers (可以通过Buffers到Channels):
3)Selectors
Asynchronous IO(NIO 2.0) : 基于事件和回调机制
基于回调:实现CompletionHandler接口,调用时触发回调函数
返回Future : 通过isDone() 查看是否准备好,通过get() 等待返回是数据
##Spring
Spring IOC
IOC(Inversion of Control) : 控制反转
DI(Dependency Inversion) : 依赖注入
Spring IOC支持的功能
1)依赖注入
2)依赖检查
3)自动装配
4)支持集合
5)指定初始化方法和销毁方法
6)支持回调方法
Spring IOC容器的核心接口
1)BeanFactory : Spring框架最核心的接口
a) 提供IOC的配置机制
b) 包含Bean的各种定义,便于实例化Bean
c) 建立Bean各种定义,便于实例化Bean
d) Bean生命周期的控制
2)ApplicationContext (继承了多个接口)
a) BeanFactory : 能够管理、装配Bean
b) ResourcePatternResolver : 能够加载资源文件
c) MessageSource : 能够实现国际化等功能
d) ApplicationEventPublisher : 能够注册监听器,实现监听机制
BeanFactory 与 ApplicationContext 的比较
1)BeanFactory 是Spring框架的基础设施,面向Spring
2)ApplicationContext 面向使用Spring框架的开发者
BeanDefinition : 主要用来描述Bean的定义
BeanDefinitionRegistry : 提供向IOC容器注册BeanDefinition对象的方法
getBean方法的代码逻辑
1) 转换BeanName
2) 从缓存中加载实例
3) 实例化Bean
4) 检测ParentBeanFactory
5) 初始化依赖的Bean
6) 创建Bean
Spring Bean的作用域
1)singleton : Spring 的默认作用域,容器里拥有唯一的Bean实例
2)prototype : 针对每个getBean请求,容器都会创建一个Bean实例。
3)request : 会为每个http请求创建一个Bean实例
4)session : 会为每个session 创建一个Bean实例
5)globalSession : 会为每个全局Http Session创建一个Bean实例,该作用域仅对Portlet有效