常见锁机制
- 8.19
悲观锁,乐观锁,独占锁,共享锁,公平锁,非公平锁,分布式锁,自旋锁
面试
- 8.19
- 中间还因为不习惯准备了一段时间去其他公司面试
作者:小傻瓜你说呢
链接:https://www.nowcoder.com/discuss/427988
来源:牛客网
自我介绍:
你好,面试官,我是浙江大学城市学院的大三学生,专业是计算机科学与技术,拿过两次1等学业奖学金,一次1等科技竞赛奖学金,绩点是4.67,主要掌握语言是java,对jvm模型有过了解,现在在杭州谐云科技有限公司的产品研发部进行实习,很荣幸能够获取这次面试的机会,为了不浪费您的时间,如果还有什么要询问我的,可以直接现在问我。
JVM:
- 【高频】Java内存区域 :
{ 程序计数器 , 虚拟机栈 ,本地方法栈 , 堆 ,方法区 ,元空间 } // 每个区域要能介绍一下
程序计数器: 记录要执行的下一条字节码指令的地址,当前线程执行字节码的行号指示器,线程私有
虚拟机栈:
方法的内存模型
若干个栈帧,局部变量表,操作数栈,动态链接,返回地址等,一个java方法从开始执行到执行结束,就是栈帧入栈出栈的过程,线程私有
本地方法栈:
与虚拟机栈类似,不过这里对应的方法是本地方法
堆内存:
主要存储数组和对象等,是jvm管理的最大一块地域,为所有线程共享。
在有分代存在的收集器里,主要分为新生代,老年代,新生代可分为eden,s0(from),s1(to)。
方法区:
是常量,静态变量,类编译后代码等存储的地方,和堆内存一样,是所有内存共享的地方。
jdk1.7之前是由永久代实现的,jdk1.7的时候已经把静态变量,字符串常量池移到堆内存中。
jdk1.8中,已经改为元空间实现了,存储在本地内存中,而不是堆内存里
- Minor GC 触发条件 : eden区剩余内存是否足够 两种情况分开分析
Minor GC 触发条件:
eden剩余内存不足以分配,触发minor gc,如果此时s0,s1内存也不足以分配的话,直接晋升至老年代中
如果足够的话,则此行minor gc之后,分配到eden区
FULL GC 触发条件
1.Minor GC 平均晋升空间大小 > 老年代连续剩余空间,则触发FULL GC
老年代空间不足
2.由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
3.调用System.gc时,系统建议执行Full GC。
- 【高频】 判断对象死亡的方法 : { 引用计数法 ,可达分析算法 }
深入一些,:GC root对象有哪些?为什么选择他们做GC root对象
引用计数法:
加入一个引用数,有引用到的地方,就给该值+1,引用失去则-1,如果为0则表示没有引用
可达分析算法:
从gc root开始遍历,如果存在引用,则为可达,否则不可达
GC root对象有哪些:
局部变量表里的引用,类静态属性的引用,字符串常量池的引用,还有一些相关引用对象。
持有同步锁的对象。
因为引用的对象在执行期间不会被回收。
- 垃圾收集算法 : { 标记清除算法 、标记整理算法 、 复制算法、 分代收集算法 }
深入一些: 各个算法的优点和适用场景
标记清除算法:
标记整理算法:针对老年代
复制算法:
分代收集算法:
-
垃圾收集器 : { serial 、 parallel 、 CMS 、 G1 }
CMS 、G1 重点 , 介绍工作流程和优缺点
cms:
初始标记:gc root能够关联的对象
并发标记;gc root关联到的对象开始遍历整个对象图
重新标记:修改并发标记时因为用户操作而导致标记错误的部分标记
并发清除:与用户进程一起,清理掉判断为已经死亡的对象
-
内存泄漏
例子: { 单例 , 容器 等等}
单例模式中,对象始终存在引用,类属性引用,所以单例不会被回收
容器:比如vector引用object,object化为null,但object仍然引用object,需要remove才可,或者直接vector设置为null
原因 : 长生命周期持有短生命周期引用 -
引用类型 ; { 强引用、 软引用、 弱引用 、 虚引用 }
引用类型 | 被垃圾回收时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 当内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 正常垃圾回收时 | 对象缓存 | 垃圾回收后终止 |
虚引用 | 正常垃圾回收时 | 跟踪对象的垃圾回收 | 垃圾回收后终止 |
强引用:比如直接创建一个对象,Object obj = new Object();
只要引用java堆内存的对象就可,宁愿跑出OOm也不会区收集它
软引用:内存充足时候不会进行回收,但是当内存不足的时候则会进行回收
弱引用:发现即回收
虚引用:形同虚设,并不对该对象的生存周期存在影响,任何时候都有可能被回收
- 【高频】 类加载过程 : { 加载 , 连接 , 初始化 }
有哪些类加载器 , 能否自定义 Java.Object.String 的类加载器 ?
加载,连接(验证,准备,解析(符号,引用解析等)),初始化
bootstrap加载器,扩展类加载器,系统加载器(应用类加载器),用户自定义加载器
不能,java. 开头的都不准被定义,在findclass方法下会报错提示,即使定义了,也无法被加载,因为双亲委派的机制存在
- 【高频】 双亲委派机制 介绍 & 作用
作用:
1.防止系统核心类被篡改
2.防止加载同一个类
Java :
- ArrayList 和 LinkedList 的区别
ArrayList:
动态扩展,基于动态数组实现
对随机元素访问的支持比较好
对于插入,末尾插入的开销都是一样的,主要是在中间插入的时候,linkedlist的效率会高一点,而arrayList则需要移动元素,所以开销会大一点。
LinkedList
底层由链表实现,双向链表
如果在插入,删除的场景下使用,则推荐这个
linkedlist不支持高效的随机访问
- HashMap & ConcurrentHashMap 的比较 : 线程安全问题等等
深入一些 : HashMap 为什么线程不安全? 能否举例 =
{
并发resize()触发闭环结构(循环) ,覆盖put操作
Entry next = e.next
}
ConcurrentHashMap 线程安全
jdk1.7 分段锁,segment实现了可重入锁,所以当放入值的时候,如果trylock()获取不到,则自旋.
ssize是与运算的,所以segment的个数最大为65535,16位
cap值为1,while(cap<c) cap <<=1; 所以hashEntry最小为2
计算size: 先重复计算三次,如果不一样,再加锁计算
jdk1.8 node+红黑树+同步锁+CAS
降低了粒度,所以使用同步锁效率会更高
使用红黑树来代替,遍历效率会更高
-
【高频】 HashMap 的 相关问题 // HashMap系列需要通过关键源码理解,比较重要
jdk 7 数组+链表 entry
jdk 8 数组+链表+红黑树(node(??? 8个
原因:
红黑树的平均查找长度是log(n),长度为8,查找长度为log(8)=3,链表的平均查找长度为n/2当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。 -
为什么 HashMap的size 为 2的幂次方 ?
默认长度为16
(为了分布均匀,韵味hashmap的hash函数indexfor 是通过length-1 & hashcode进行的
所以当size为2的幂次方时,计算出来的的hash值能够比较正常,为了降低hash碰撞的几率。
HashMap resize()过程能否介绍 ?
新建一个长度为之前数组2倍的新的数组,然后将当前的Entry数组中的元素全部传输过去,扩容后的新数组长度为之前的2倍,所以扩容相对来说是个耗资源的操作。
并且重新计算hash位置
扩容的触发条件:
阈值 = 数组默认的长度 x 负载因子(阈值=16x0.75=12)
HashMap效率受什么影响 (负载因子、hash数组size)?
负载因子、hash数组size
HashMap中扰动函数的作用 ?
为了混和hashcode高位和低位,所以加入扰动函数,使得hashcode值右移16位与原本的hashcode进行异或运算,这样算出来的hash值就相对更加不会碰撞了。
-
Hashtable 和 HashMap的区别 : { 底层数据结构 (JDK1.8后不同)、父类不同 、扩容方法不同 、 线程上锁范围不同(重点) }
父类
hashtable 就只是数组+链表,hashmapjdk1.8之后 是数据+链表+红黑树
Hashtable继承了Dictionary,HasMap继承了AbstractMap,都实现了map接口
扩容方法:
Hashtable 扩大为2倍+1,HashMap则为2倍
Hashtable默认容器大小为11,hashmap为16,一个需要为2的幂次方,一个不需要
Hashtable修改数据时会锁住整个hashtable
hashtable 计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length -
equals 和 == 区别
equals 默认比较的是两个对象的地址
==
基本类型比较的是 值是否相同
引用类型比较的是 地址是否相同
equal 默认没有重写的话比较的是地址
为啥重写equals要重写hashCode()
为了保证equal相等的时候hashcode相等
而hash值相等,而两个对象不一定equal
6 . 【高频】 String StringBuffer StringBuilder 区别 和各自使用场景
深入一些 : String 是如何实现它不可变的? 为什么要设置String为不可变对象 ? (字节一面这个问题给我问懵了)
StringBuffer 线程安全
StringBuilder 线程不安全
都可变
string不可变
设置为private final对象,且外部没有方法可以修改内部的value[]值
为什么要设置String为不可变对象:
1.字符串常量池的需要,节省堆空间
2.安全性,很多网络连接url,文件路径等使用的都是String对象
3.hash缓存的需要,保证了hashcode的唯一性,可以放心缓存,这也是为什么建议使用string作为key的原因
- 接口和抽象类区别
接口:
可以被多实现,并且只允许存在抽象方法和不可变静态属性存在,不允许存在不可变属性值
抽象类:
只能被单继承,并且抽象类里面可以拥有属性和抽象方法,和实现的方法
当子类继承的时候,如果有没有实现的方法,那么子类也是抽象类
- 重写和重载的区别
重写
重写父类的方法,并且参数不可变
重载,继承方法,并且修改参数
-
深拷贝和浅拷贝区别
深拷贝 就直接拷贝了全部,是一个独立的对象,对拷贝的对象操作不会影响到被拷贝对象
浅拷贝相反,为引用。 -
Java三大特性
继承,多态,封装
封装:
属性私有化,就是提供getter和setter方法,隐藏具体实现细节
增加安全性和简化编程。
继承:
继承是指将相同的属性和方法提取出来,新建一个父类
只能继承非private的方法和属性
子类可以重写方法之类的
代码复用
多态:
多态分为设计时多态和运行时多态
设计时多态:重载,java允许方法名相同参数不同
运行时多态:重写,根据运行的方法类型来决定执行哪个方法
增加灵活度
-
Object的方法 : { finalize 、 clone、 getClass 、 equals 、 hashCode }
finalize:gc垃圾回收使用
clone:浅拷贝,拷贝一个对象操作
getClass:获得字节码对象,反射时可以使用
equals:比较地址是否相等
hashCode:获得hashcode,可以看作地址值 -
【高频】 设计模式: { 单例模式 、 工厂模式 、 装饰者模式 、 代理模式 、 策略模式 等等} (此处我的掌握也不是很好)
工厂模式: 抽象产品(car),具体产品(奔驰,宝马),具体工厂(driver,方法返回为car)
策略模式:
抽象策略角色 - 接口
具体策略角色 - 实现接口的具体策略
context - 使用策略的类
测试就是使用策略的时候进行构造函数传入,然后进行策略测试
多态运用
适配器模式:
将一个类的接口转换成客户希望的另外 一个接口
比如把对于鸡的接口转换成鸭来使用
装饰者模式:
拓展一个类的功能,并且可以动态撤销
比如定义一个饮料接口
定义两个实现饮料接口的饮料
装饰器接口:定义一个继承饮料接口的调料接口
定义一个加柠檬的装饰器继承调料接口,并且实现(描述+柠檬
由比如定义一个加芒果的装饰器继承调料接口,并且实现(描述+芒果
代理模式:
静态代理和动态代理
静态代理的拓展需要代理类实现相同的方法,数目一多容易搞混
动态代理:???Cglib
深入一些 : 单例模式为什么采用双检测机制 ? 单例为什么用Volatile修饰? 装饰模式和代理模式区别?
1.为了线程安全,如果此时有多线程调用的话,同时初始化,会生成两个实例
2.因为volatile修饰之后的变量就会在直接在共享内存操作,这样可以确保多个线程可以正确的处理它
3.代理模式是通过一个代理类来执行他不关心的方法
装饰模式则是拓展一个类的功能,增强自己
两者都是继承同一个接口
并发 :
-
线程的状态 : { new ,runnalbe , wait , time-wait , block , terminated }
-
进程 、 线程 、 协程 的含义和区别 // 个人理解 是一组渐进提出的概念
进程 、 线程 、 协程
1.是系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是一个实体。每一个进程都有它自己的地址空间。
2.线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
3.协程,是一种比线程更加轻量级的存在,类似于自我控制的线程 -
进程间通信方式 : { 管道 、FIFO 、 信号量 、 共享内存 、 消息队列 、 Socket }
管道:
基于文件描述符,fd[2]一个用来读,一个用来写,半双工通讯模式
只在具有亲缘关系的两个线程间传递
FIFO:
命名管道,可以使两个不相关的线程进行通讯
其余基本类似
信号量:
pv,支持同步操作
共享内存:
指多个线程共享一个给定的存储区
消息队列:
消息队列,是消息的链接表,存放在内核中。
用户进程可以向消息队列添加消息,也可以向消息队列读取消息。
与管道相比,其优势在于可以自定义类型进行发送和接收
创建或打开消息队列:int msgget(key_t key, int flag);
添加消息:int msgsnd(int msqid, const void *ptr, size_t size, int flag);
读取消息:int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
控制消息队列:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
Socket:
面相网络的一种进程间通信方式,
他可以跨越任何限制
-
如何避免死锁 ? 死锁的四个必要条件
互斥
持有且等待: 一次性获取所有的资源
不可剥夺: 如果一次性获取不了全部的资源就睡眠,并且主动释放自己持有的资源
循环等待:将所有资源按照稀有程度进行排序,稀有的序列排列为较大的,一个线程只有申请到了序列小的资源才可以申请序列大的资源 -
Sleep和wait的区别
- sleep是线程的方法,wait是object的方法
- sleep不会释放lock,wait则会释放并且进入等待队列
- sleep不需要依赖锁,wait需要依赖(在同步锁里面调用
- sleep = time-wait状态 一段时间后自动唤醒
进入waiting状态 没有满足条件就一直无法被唤醒
-
【高频】Sychronized 和 lock 的区别 ?
1.Sychronized是内置的,而lock是java的类
2.Sychronized无法得知锁的状态,而lock可以得知
3.Sychronized会自动释放锁,而lock则需要在finally里面执行unlock释放锁
4.使用Sychronized的两个线程如果线程1获得锁,线程2阻塞等待,则会一直等待。
使用lock的话,如果线程获取不到锁的话,可以不用一直等待就结束了。 -
Sychronized的底层优化 : { 无锁、偏向锁、轻量级锁 、重量级锁 }
无锁、偏向锁、轻量级锁 、重量级锁
锁只能升级不能降级
无锁 就是正常状态
如果偏向锁则在mark wod(设置为01)和栈帧锁记录里存储锁偏向的线程ID
(只有一个线程竞争的时候,如果有两个则升级)
第二个线程竞争的时候,如果已经有线程持有锁,则把标识位修改,并且自旋。
如果这时候竞争自旋的时候又有线程 来竞争资源,则升级为重量级锁,并阻塞
-
volatile的作用 : { 指令重拍 、 保证变量的可见性(设计JMM)}
禁止指令重拍,volatile修饰的变量前后会有内存ping zhang -
ThreadLocal 底层原理
内部存在一个ThreadLocalMap,以当前线程为key,存储value
最出名的例子就是数据库连接的例子
使用ThreadLocal管理多线程的数据库连接即安全又高效 -
【高频】 线程池
线程池构造器涉及哪些参数 : { corePoolSize , maxinumPoolSize , timeout , timeUnit , RejectHandler , 阻塞队列 , 线程工厂 }
RejectHandler:线程池对拒绝任务的处理策略
timeUnit:线程池维护线程所允许的空闲时间的单位
线程工厂:
给线程命名,查看创建线程数
给线程设置是否是后台运行
设置线程优先级
介绍线程池工作过程?
当阻塞队列刚传递进去的时候,线程池是不会立即运行的
直到执行excute时候,则线程池会进行判断
(有界的时候
如果正在运行的线程数量小于corePoolSize,则立即创建线程执行此任务
如果大于corePoolSize,则塞入队列
如果队列满了,并且执行的数目小于maximumPoolSize,则创建非核心线程去执行当前任务
如果队列满了,并且执行数目等于maximumPoolSize,则根据饱和策略抛出rejectexceptionHanler错误
如果一个线程完成任务,则会从队列取出一个任务进行
当一个线程没事可做的时候,且当前线程数目大于corePoolSize的时候,则停止线程运行,所以最后线程池的线程数目会缩减成corePoolSize。
线程池拒绝策略那些?
AbortPolicy,丢弃任务并且抛出RejectedExecutionHandler,默认
CallerRunsPolicy: 该任务被拒绝,由调用execute方法的线程去执行这个任务
DiscardOldestPolicy: 抛弃队列最前面的任务,然后继续执行
DiscardPolicy: 直接抛弃该任务,且不报错
使用Executors创建线程池的弊端?
创建的newFixedThreadPool和newSingleThreadPool里面的队列最大可为Integer.MAX_VALUE,很容易引起OOM
创建的newCachedThreadPool里面corePoolSize为0,所以可以认为队列永远都是满的,因为一直会塞入队列,很容易造成OOM
- AQS 框架原理和 源码理解
???
计网 :
CA签发流程:
-
TCP/IP 模型 & OSI模型
OSI:
物理层/数据链路层 /网络层/ 传输层/ 会话层/表示层/应用层
tcp/ip
四层模型:物理接口层/互联网层/传输层/应用层
五层模型: 物理层/数据链路层/互联网层/传输层/应用层
-
应用层、传输层、网络层常见协议 : { DNS 、 HTTP 、FTP、 STMP 、SSL、 TCP、 UDP、 ARP、 IP }
- 【高频】 TCP 三次握手 、 TCP四次挥手
深入一些 : 为什么三次握手,能否两次?
不行,如果两次的话,即b发送请求,a就通过,就有可能出现已失效的链接请求报文突然又传送到了服务端这种情况,从而导致浪费或者错误。
四次挥手中什么是 time_wait 状态 ? close-wait 状态?
就在收到服务器发来的fin之后,没发送ack之前,这段时间是time_wait状态
会一直持续2MSL(即两倍的分段最大生存期)
服务器收到fin之后就是close_wait状态
time_wait状态什么场景下过多 , 会造成什么问题?
高并发短连接的时候,这时候服务器主动断开链接,所以会有大量链接处于time_wait状态,导致客户端访问过慢。甚至连接不上。
就短连接中数据传送+业务处理的时间远远小于2MSL的时间。
-
TCP可靠性 : { 超时重传 、 流量控制(接收端只允许发送端发送接收端能够接受的数据 、 拥塞控制(拥堵则少发 、 校验和(保留首部和数据的校验和,为了保证完整性 }
-
【高频】输入URL显示主页的过程 // 个人仅遇过一次,但是看面经感觉问的挺多
/*** -
由域名→IP地址 寻找IP地址的过程依次经过了浏览器缓存、系统缓存、hosts文件、路由器缓存、 递归搜索根域名服务器。
-
根域名服务器也是一个个询问,DNS解析
(例如先查看本地缓存,然后根域名服务器,在是com顶级域名服务器,再是google.com域名服务器,然后再一层层返回给本机。 -
建立TCP/IP连接(三次握手具体过程)
-
由浏览器发送一个HTTP请求
-
经过路由器的转发,通过服务器的防火墙,该HTTP请求到达了服务器
-
服务器处理该HTTP请求,返回一个HTML文件
-
浏览器解析该HTML文件,并且显示在浏览器端
-
这里需要注意:
-
HTTP协议是一种基于TCP/IP的应用层协议,进行HTTP数据请求必须先建立TCP/IP连接
-
可以这样理解:HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
-
两个计算机之间的交流无非是两个端口之间的数据通信,具体的数据会以什么样的形式展现是以不同的应用层协议来定义的。
***/
-
HTTPS 和HTTP 区别 // 字节专属问题
深入一些 : HTTPS 加密过程介绍 ?
1.端口不一致
2.http直接运行在tcp之上,明文传输/https则是有一层ssl/tls运行在应用层与tcp/ip层之间,所有传输过程都是加密的
3.https需要ca证书
client hello/server hello/pre master secret随机数 -
Cookie和Session的区别
Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;
主要用来追踪会话。
Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。
session id一般包括在cookie中,然后服务器可以通过session id来获取对应的信息是属于谁的。
- Http1.0 和HTTP1.1 和 Http2.x 的区别
http1.1相比http1.0
优化了缓存处理,错误通知类型增加,长连接(一个tcp可以进行多个http请求和响应,带宽优化等方面),host头处理(一台物理机上存在多个虚拟主机,共享同一ip
http2.x相比较
1.将原本基于文本的处理转换成2进制处理,为了http2.x的协议流程处理
2.多路复用,使得双向传递流,不用像之前那样keep-alive处于阻塞状态
3.header因为数据信息多采用了压缩处理
4.服务端推送功能,js传送给客户端,客户端css再次使用直接从缓存中获取。
-
get和post的区别
1.get是获取数据,post是向服务器传送数据
2.get提交的数据可以在url里面看到,post则是包裹在request payload里面
3.get最后提交1024字节数据,post没有限制
4.post提交比get更安全 -
常见的状态码
1xx 临时响应
200,
301,302
401,403,404
501,502
操作系统(相对较少):
-
用户态和内核态的区别
内核态:运行操作系统程序,操作硬件,0
用户态:运行用户程序,3
两者最大的区别是特权级别不同,用户态拥有最低的特权级别
内核态则拥有最高的特权级。运行在用户态的程序不能直接访问到os内核的数据结构等。
两者的切换,主要包括系统调用(主动),异常与中断(被动)。
-
fork()作用
创建子进程
并且返回的线程里
pid=0的是子进程
pid!=0的是父进程
两个线程共享代码空间,数据空间则是独立的
-
Select poll epoll的区别
本质上都是同步io,并且都是io多路复用的机制
程序使用select的时候,整个程序会进入阻塞状态,然后select会去轮询其他的归select负责的fd,如果发现有准备好了的,则系统调用,将数据从内核空间复制到用户空间。
poll本质上与select差不多,只是稍作了改进
结构相对变成了pollfd(取代了fd_set),是链式结构,所以没有最大连接数目的限制
并且poll是水平触发的,就是如果这次fd通知了程序已经准备就绪但是没有触发,下次poll的时候还是会通知poll,这个fd已经准备就绪。
epoll是两者的升级
1.epoll没有fd数量限制,只需要注册fd
2.epoll不需要每次都去内核空间进行复制
3.epoll不需要主动询问,因为epoll注册函数的存在,如果fd准备就绪后则会进入队列等待。
-
虚拟内存作用? 内存分页的作用?
使得程序员开发程序不需要考虑内存的占有问题,由操作系统来完成虚拟地址到真实物理地址的转换,甚至部分使用到了磁盘空间,这样就使得各个用户进程相互隔离,并且也同时保护了内核不受破坏。
将大小不一的段映射成统一的页大小,同时也使得分段时候因为物理碎片存在而无法加载较大进程的问题,同时以页交换也相比段来说要高效不少,因为一页只有4kb
-
缺页异常的介绍
虚拟地址到物理地址转换不可用的时候会产生此类错误
虚拟地址转换,物理地址不可用
虚拟地址转换,物理地址存在但权限不足
没有创建该虚拟地址-物理地址的映射
- OOM问题和 StackOverFlow的区别
StackOverFlow:
如果线程申请的栈深度大于虚拟机所允许的深度,将抛出此错误
比如递归问题的时候
OOM:
如果虚拟机栈内存允许动态扩展,并且扩展栈容量无法申请到足够的内存时,将抛出此错误
MySQL:(对redis 需要学习 , 个人理解不深不多介绍) // MySQL比较重要
-
【高频】MyISAM 和 InnoDB(5.5之后的版本)的区别 : {是否支持行锁 、 是否支持事务、 是否支持 MVCC 、 底层索引结构不同 }
-
事务特性ACID
深入一些 : 为什么要有一致性? AID不是已经保证了一致性了吗?
c为目的,aid为手段,比如原子性,没有原子性,那么便不能保证一致性
-
并发事务带来的问题 : {脏读 、 修改丢失 、 不可重复读 、 幻影读 }
-
【高频】事务的隔离级别
未提交读 脏读 不可重复读 幻读
已提交读 不可重复读 幻读
可重复读 幻读
可序列化 -
【高频】 MVCC机制 (多版本并发控制
乐观锁的实现方式、
比如select的时候只会显示id版本在本次事务之前或者等于当前事务版本的数据
加入两列 一个是创建时间,一个是删除时间
事务是什么:
事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合
- 【高频】索引
为什么索引使用B+树结构,而不是B树
因为B树不管叶子节点还是非叶子节点,都会保存数据,这样导致在非叶子节点中能保存的指针数量变少,从而导致树的高度变高,导致io查询次数变多,查询性能变低。
为什么索引使用B+树结构,而不是红黑树 :
{ 磁盘预读取 、红黑树高度 }
因为b+数是一棵多路复用的树可以降低树的高度,提高查找效率
减少io次数,磁盘预读取
-
聚簇索引和非聚簇索引区别? 主键索引和二级索引了解吗?
聚簇索引的叶子节点就是数据节点,而非聚簇索引的叶子节点仍然是索引节点,只不过有指向对应数据块的指针。聚簇索引具有唯一性,与物理顺序一致,非聚簇索引可以有多个,与逻辑顺序无关。
innodb中,建立的聚簇索引默认就是主键索引,非聚簇索引是二级索引,在二级索引里面存储的是主键值,所以根据条件二级索引查询到主键后,再主键索引就能获得完整的数据行。 -
为什么不对每个列创建索引呢?
1.创建索引和维护索引都需要花费时间,特别是随着数据量增大,时间会越来越长
2.创建索引需要占据物理空间,所以建立的索引越多,占据的空间也就越大
3.需要时间去维护索引,特别当数据面临删除,修改的时候,如果索引很多,就会导致速度的下降。 -
【高频】SQL语句优化 ,SQL题目(字节要求撸代码)
???
-
explain中 rows type key extra字段的含义?
rows:扫描出的行数(估算的)
type:对表访问方式,表示MySQL在表中找到所需行的方式
key:表示实际使用的索引
extra:执行情况的描述和说明,例如using where,using temporary等 -
count(1) count(*) count(列值)的区别
count(1)表示取的第一个代码行的值,不会忽略null值
count(*)包括了所有列,相当于统计行数,统计结果时,不会忽略null值
count(列值)则会计算那一列的值,会忽略null值
其他:
需要了解linux的指令和 git指令 ,
对一些大数据场景题需要了解。 例如 1亿数据取top10 , 1亿数据取出现频率top10,1亿URL取出重复URL
1亿数据取top10:
1亿数据取出现频率top10:
1亿URL取出重复URL:
64匹马,8个跑道,最少比赛几次选出前八?等智力题(字节常考)
此外,项目相关的知识体系需要准备, 例如我的项目涉及Netty和Zookeeper,则我会去准备相应的问题。此处因人而异。
启动类加载器(引导类加载器,BootStrap ClassLoader)
- 8.11
1.这个类加载使用C/C++语言实现的,嵌套在JVM内部
2.它用来加载java的核心库(JAVA_HOME/jre/lib/rt.jar/resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
3.并不继承自java.lang.ClassLoader,没有父加载器
4.加载拓展类和应用程序类加载器,并指定为他们的父加载器,即ClassLoader
5.出于安全考虑,BootStrap启动类加载器只加载包名为java、javax、sun等开头的类
拓展类加载器(Extension ClassLoader)
1.java语言编写 ,由sun.misc.Launcher$ExtClassLoader实现。
2.派生于ClassLoader类
3.从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会由拓展类加载器自动加载
应用程序类加载器(系统类加载器,AppClassLoader)
1.java语言编写, 由sun.misc.Launcher$AppClassLoader实现。
2.派生于ClassLoader类
3.它负责加载环境变量classpath或系统属性 java.class.path指定路径下的类库
4.该类加载器是程序中默认的类加载器,一般来说,java应用的类都是由它来完成加载
5.通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器
代码演示
/**
* 虚拟机自带加载器
*/
public class ClassLoaderTest1 {
public static void main(String[] args) {
System.out.println("********启动类加载器*********");
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
//获取BootStrapClassLoader能够加载的api路径
for (URL e:urls){
System.out.println(e.toExternalForm());
}
//从上面的路径中随意选择一个类 看看他的类加载器是什么
//Provider位于 /jdk1.8.0_171.jdk/Contents/Home/jre/lib/jsse.jar 下,引导类加载器加载它
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println(classLoader);//null
System.out.println("********拓展类加载器********");
String extDirs = System.getProperty("java.ext.dirs");
for (String path : extDirs.split(";")){
System.out.println(path);
}
//从上面的路径中随意选择一个类 看看他的类加载器是什么:拓展类加载器
ClassLoader classLoader1 = CurveDB.class.getClassLoader();
System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@4dc63996
}
}
[](javascript:void(0); "复制代码")
知识扩展:启动类加载器BootStrapClassLoader能够加载的api路径有
最近看java.util.concurrent包的内容,发现java.time.、java.util.、java.nio.、java.lang.、java.text.、java.sql.、java.math.*等等都在rt.jar包下,
为什么要使用用户自定义类加载器
1.隔离加载类
2.修改类加载的方式
3.拓展加载源
4.防止源码泄漏
ClassLoader的常用方法及获取方法
ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)
ClassLoader继承关系
代码示例如下
学习日记
- 8.12
读写锁
实现了Lock接口
实现类是RetenntReadWriterLock - 只有写锁支持条件变量
- 读锁不支持
- 读锁可以有多个线程进入,写锁不行
- 读锁不可以升级成写锁
- 写锁可以降级成读锁
方法区,虚拟机栈,本地方法栈,堆,程序计数器
程序计数器:
当前线程执行的字节码的行号指示器,字节码解释器工作时,就是通过改变此计数器的值来获取下一行字节码指令应该执行的地方,每条线程都有各自独立的程序计数器,独立存储,我们称这类内存区域为“线程私有“的内存。
如果线程执行的是native方法的话,则该计数器应为空.
虚拟机栈:
与程序计数器一致,都为线程私有。 生命周期与线程相同。
每个方法被执行的时候,java虚拟机都会同步创建一个栈帧,
用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
1- 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
如果可以动态拓展,则抛出OOM异常,不过HotSpot不允许动态拓展,所以不会因此出现OOM异常。
本地方法栈
与虚拟机栈其实十分的相似,只是其为本地方法服务,
堆
也被称为“gc堆”
为所有线程所共享的区域
唯一的作用就是为了存放对象实例
分为多个代也只是为了提升gc效率
方法区
为所有线程所共享的区域
用于存储已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等数据
放弃永久代,改为逐步采用native memory来实现方法区的计划
jdk8的时候就已经改为本地内存中实现元空间进行代替
运行时常量池
方法区的一部分
用于存放编译期间形成的各种字面量与符号引用,这部分在类加载成功之后将会被存放入方法区中的常量池中
直接内存
在jdk1.4中新加入了NIO类,引入了一种基于通道与缓存区的I/O方式,他可以使用native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在java堆与native堆中来回复制数据
本机直接内存溢出
线程安全
如果只有一个线程可以操作此数据,则必定是线程安全的
8.5 学习日记
原子性 AtomicInteger、AtomicLong
// 可见性,有序性
// 三个关键字
// synchronized, final, volatile
// happen-before 6个规则
// 顺序性 - 一个线程中顺序执行
// volatile 写操作 对 后面读操作可见
// 传递性
// 解锁对后续的加锁可见
// 主线程启动子线程b,主线程之前的操作对子线程可见