面试笔记01

类加载
**1.类加载step:**加载,链接(校验,准备,解析),初始化,使用,卸载。
(1)加载:加载class文件(获取class的二进制流文件,加载到JVM中,内存生成一个class对象表示该类)
(2)校验:校验是否符合JVM合法性(class文件规范,语义验证:是否继承final,是否实现抽象方法)
(3)准备:为class分配内存空间。设置static成员变量初始值为默认值,int为0,对象为null,但如果是final static 该阶段会之间进行赋值,因为同时被final和static修饰的成员变量会有ConstantValue属性,且限于基本类型和String(如果该变量没有被final修饰,或者并非基本类型及字符串,则选择在类构造器中进行初始化。)
(4)解析:符号引用替换为直接引用,解析主要针对类,接口,方法成员变量等符号引用(符号引用用一组符号来描述目标引用,用于定位目标引用,在编译时JVM虚拟机不知道具体的目标地址,即用符号引用来代替)
(5)初始化:执行类构造器,执行对static变量赋值,以及static代码块(1.是创建类对象实例时构造方法;2.一个类中没有static变量,也没有stiatc代码块,则不会生成该类的方法;3.虚拟机会保证方法执行线程安全,所以能够当做单列)

2.触发类加载的6个时机:
(1)创建类的实例,即new一个对象;
(2)访问类的静态变量;
(3)调用类的静态方法;
(4)反射,Class.for.name;
(5)初始化一个子类(会首先初始子类的父类);
(6)定义了main方法;

3.反射的三种方式:
(1)Class.for.name(全类名);
(2)类.class;
(3)对象.getClass()方法;
同步锁
锁分类:
锁可分为乐观锁和悲观锁,乐观锁即cas,悲观锁synchronzied;对象锁(锁的是对象)和类锁(锁的是类)
锁级别:
锁根据资源消耗从低到高分别为偏向锁,轻量级锁以及重量级锁,随着竞争偏向锁升级轻量级锁,再升到重量级锁,升级是单向的,不可逆
1) 偏向锁:第一个线程获取锁,并将Mark World修改为ThreadID;适应于只有一个线程访问同步代码块的场景,加锁和解锁消耗资源低,若存在竞争,便会升级为轻量级锁,升级过程需要撤销偏向锁,会导致stop the world操作;
2) 轻量级锁:另外一个线程发现mark world中的ThreadID与自己线程ID不等,则会立即撤销偏向锁,升级为轻量级锁,然后尝试自旋cas获取轻量级锁,竞争线程不会阻塞(线程状态不会改变),提高同步代码执行速度,如果自旋cas失败,并超过最大等待时间,则会升级会重量级锁(自旋会消耗cpu),线程进入阻塞状态;
3) 重量级锁:线程阻塞,响应时间长,适用于吞吐量高,同步代码执行时间长的场景

可重入锁:
即一个线程成功获得对象锁后,能够调用该对象的另一个synchronized方法(jvm有个计数器,获取锁则计为1,此时其他线程获取该锁需要等待,持有该锁的线程就可以再次拿到这个锁,同时计数器会递增,当该线程退出一个synchronized方法/代码块时,计数器会递减,直到计数器为0则会释放该锁)
注意;如果是static修饰的synchronized方法代表是类锁,锁的是类.class对象,与对象锁不同,全局只有一个。

公平锁和非公平锁:
1) 公平锁:线程按顺序执行,所有线程都能获取资源,不会饿死在队列里,吞吐量低,采用的同步队列是FIFO模式;
2) 非公平锁:多个线程不需要排队可以直接竞争获取锁,性能好,吞吐率高,但会导致队列部分线程饿死

非公平锁为什么性能优于公平锁?
因为公平锁由中线程释放锁后,如果仍需要获取锁,则需排在队列尾部,则线程的上下文切换频繁,导致cpu损耗严重,但非公平锁会导致线程饿死现象。

ReentrantLock包括:公平锁和非公平锁,内部的加锁机制是cas操作更新state值,需要手动加锁解锁,并能够通过注册Condition,唤醒指定线程,即可控制线程的执行顺序(notify()/notifyAll(),唤醒策略是jvm随机选择的);

死锁预防:
1)设置线程优先级;2)缩小同步加锁的代码范围,在需要的代码加锁;3)避免使用嵌套锁;4)添加超时时间,避免无限等待。
(可以用jstack拿到程序死锁的线程转存文件进行分析)

ThreadLocal(典型空间换时间):
1)ThreadLocal为每个线程中创建了一个变量副本(数据库链接或者session),每个线程操作访问自己内部的副本变量,每个线程内部的变量是相互独立的,即线程隔离,threadlocal内部维护了一个map,key为当前threadlocal对象,value为具体的变量值(值存放在ThreadLocal.ThreadLocalMap.Entry实例中);
2)核心就是内部维护了一个内部静态类ThreadLocalMap,里面定义个Entry进行存储,threadlocal全局只有一个,负责访问和维护Thread的ThreaLocalMap,而ThreadLocalMap和Thread一一对应,是Thread的一个属性;
3)使用完ThreadLocal,必须需要执行remove操作,否则会出现内存泄漏(ThreadLocal被垃圾回收后,ThreadLocalMap的生命周期和Thread一样,当ThreadLocalMap的key即ThreadLocal是弱引用,当jvm执行gc时会被回收不存在了,但是value仍然存在,造成这块内存无法回收,造成内存泄漏)
 Jvm的引用类型:
1) 强引用(一般对象引用):只要处于引用状态,永远不会被回收,即使jvm内存不足,抛出OOM;
2) 软引用(对象缓存):在内存充足的情况,不会进行回收,当jvm内存不足时会进行回收;
3) 弱引用(对象缓存);不管内存空间是否充足,jvm执行gc就会进行回收
4) 虚引用:随时会被gc
其中,软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

ConcurrentHashMap
1) 1.7中实现机制为Segment(Segment继承了ReentrantLock)+hashEntry
在这里插入图片描述

 size操作:三次不加锁进行计算,前后计算相等则正确,若不正确,对每个segment进行加锁计算。
 需要进行两次hash,第一次hash获取segment位置,第二次hash获取segment中的entry位置,再进行遍历
2) 1.8中实现机制为Node数组+CAS+Synchronized(在加锁时仅锁住了Node的head节点)
在这里插入图片描述

 put操作:
1) 如果没有初始化则调用initTable方法进行初始化;
2) 如果没有hash冲突直接CAS插入;
3) 如果还在进行扩容就先执行扩容操作;
4) 如果存在hash冲突,则加锁来保证线程安全(链表尾插法或红黑树插入)
5) 如果链表长度超过8,则要先转换成红黑树结构,break一次再进入循环;
6) 如果添加成功就调用addCount方法(该方法会计算size,如果调用size方法获取长度时,会直接返回结果)统计size,判断是否需要扩容。

HashMap:
hashMap 的数组长度都是2的n次幂 的原因:是因为当这个数再减去1,转换成二进制的话,就肯定是最高位为0,其他位全是1 的数。当 HashMap 的数组长度是 2 的整次幂时,计算元素的index时使用的计算公式(n - 1) & hash与 hash % n计算的结果是等价的,而后者是更容易想到的办法,前者是更有效率的办法。

线程池
KeepAliveTime:超过核心线程数的线程(即最大线程数的线程)空闲时间达到keepalivetime,就会退出,其中allowCoreThreadTimeout参数:是否允许核心线程空闲退出,默认值为false。
可以理解为:当线程数 > 核心指定数量时,keepAliveTime 为终止前多余的空闲线程等待新任务的最长时间。
线程池的四种拒绝策略:

  1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出异常(适合重要关键任务);
  2. ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛弃异常(适合无关紧要的任务);
  3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务,丢弃旧的任务,执行新的任务;
  4. ThreadPoolExecutor.CallerRunsPolicy:任务拒绝,调用线程池的(即提任务的线程)直接执行此任务。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值