1 Java 相关
1.)说说 HashMap
HashMap 的 数据结构是数组+链表的结构, 传入默认长度的时候,会自动根据默认的长度和扩容因子算出实际默认的长度,由于使用二进制算法,这个长度必然是二的倍数,在发生hash 碰撞时采用的链表的方式来解决的,在jdk 1.8以后 引入了红黑树 ,在链表的长度>=8时 由链表转化了红黑树,当红黑树的数据<=6的时候再由红黑树转为了链表, 为什么引入红黑树的,因为如果链表的长度过长会导致在遍历的链表的时候速度过慢,hashmap 的扩容机制,在数组中的数据长度达到整个数组长度的扩容因子的比例时开始扩容,默认的扩容因子是0.75 , 至于为什么选择0.75,其实这个也是一个比较特殊的知识点,那就是可以使用二进制进行运算, 我们就拿 默认长度16 来做示范,那么达到扩容可以表示为 2<<3 - 2<<(3-2) , 扩容后还需要进行重新hash 来决定当前的数据是否需要重新排列,在jdk 1.7的时候链表采用的头插法,这就导致多个线程操作的时候可能会形成环,而jdk1.8时则采用了尾插法,规避了这个问题
2.) 关于锁了解过哪些
乐观锁 和悲观锁的概念,
乐观锁 ReentrantReadWriteLock
就是每次修改数据默他是不会修改数据的,但是在执行期间还是会判断其他线程是否对数据做了修改,底层实现使用version 等机制来实现的,比较适用于大量的读数据的时候,执行速度比较快,
悲观锁 synchronized ReentrantLock
悲观锁正好和乐观锁相反,每次执行时都会将数据上锁,不允许其他线程访问, 适用于修改数据
重入锁
重入锁的概念是在一段被锁定的代码块中,是否可以再次获取到这个锁,他是实现方式是如果多次上锁,则锁的引用计数+1,只有在锁的引用计数为0时才会释放锁对象
CAS 的实现方式
这个自行百度
死锁产生的原因,先用一段例子来说明一下
public class TsmTest{
private Object lock1=new Object();
private Object lock2=new Object();
public void method1(){
synchronized (lock1){
...
synchronized (lock2){
...
}
}
}
public void method2(){
synchronized (lock2){
...
synchronized (lock1){
...
}
}
}
}
在这段代码中可以看到 TsmTest 中存在两个方法,这个两个方法会有先后顺序的去获取不同的锁,假设当两个线程同时获取到该对象,并执行两个方法, 线程1 执行方法1,线程2 执行方法2,那么线程1将会持有lock1 的对象锁,线程2 将会持有lock2的对象锁,在线程1和线程2 都执行完了第一层synchronized 代码块中的代码时,此时线程1 想要继续执行则需要lock2的锁对象,而这个对象被线程2锁定,线程2也是同样的的道理,导致两个线程在这里无线等待,造成死锁, 想要解决这个问题也非常的简单,那就是锁对象的获取顺序必须要统一,而且在获取锁的过程中建立获取锁的时间上限,超过此上限则释放原有锁对象,
3.) new Object() 在内存中占用多少字节
一个Object 对象在内存中所占用的字节是16个字节,那么这些字节都分配给了谁呢
1.markdown 8字节 他主要存储了 一些标记位 还有hashcode ,标记位主要包括version(数据是否改变的依据,如果改变后的数据相同,但是version不同,则还是认为数据已经被修改过了), 还有重量级锁(重入锁是可以将锁的引用计数增加,这个引用计数就在markdown 中) , 还有自旋锁的一些状态等等
2.class pointer 类指针 4字节
- data 数据
4.padding 对齐方式 4字节
以上情况是发生在class pointer 压缩的情况,如果在不发生class poiniter 压缩的情况下,padding 的字节是0, class pointer 则是8个字节
4.) java 中有几种引用类型,以及他们的用法和用处
1.强引用类型 强引用类型的对象会被内存一直持有,直到引用消失,如果在内存不足的情况下即使是抛出OutOfMemeryException,也不会将引入释放,在存储轻量级的持久数据上比较有优势,但是突然有较大数据的插入,则有可能抛出OOM异常,例如图片使用
2.软引用类型 SofeReferce<T> , 软引用相对于强引用关联要弱一些,软引用所持有的对象只有在内存不足或者已经没有引用计数的情况下才会被清除,并将该对象的引用会被放入到相应的引用队列当中,相比于强引用他会更安全一些, 在抛出OOM异常前会执行二次垃圾回收,如果本次回收后还是没有足够的内存,才会抛出OOM异常
3.弱引用类型 WeakReferce<T> 弱引用相对于软引用关联还要再弱一些,如果在回收时发现只有弱引用引用了改对象,那么该对象则会被回收,该对象的引用会被放入到相应的引用队列当中
4.虚引用类型 PhantomReference<T>
其实虚引用和弱引用对于对象的引用关系其实没有本质上的区别, 只是在构造虚引用时,必须要指定引用队列,来检测对象是否被回收,而弱引入的引用队列则是非必要字段,在这里可以看出来虚引用的主要用法其实是用来判断他所引用的对象是否被回收,而不是在引用本身,
引用由强弱排序 强引用 > 软引用 > 弱引用 > 虚引用
5.) ClassLoader 的作用,以及类加载过程
类从被加载开始到卸载的整个过程分别有7个阶段 加载、验证、准备、解析、初始化、使用和卸载, 在加载过程中使用了双亲委派机制,为什么使用双亲委派机制,使用双亲委派机制有什么好处,
为什么使用双亲委派机制,比如在java 虚拟机中已经定义好了String.class ,如果你自己同样的定义了一个String.class,那么在加载过程到底选择哪个类来加载呢,如果是由类本身来做,就会存在多个同样类名,不同文件的对象,这样就会比较混乱,如果统一交给parent 来处理,那么到最后只有一个,而到底需要加载哪一个类,则看这两个类的排列顺序,而String.class 在jvm 中 是由Bootstrap classLoader 来加载的,而他的排序又比较靠前,所以重新写一个String.class 也是不会起作用的,
使用双亲委派机制有什么好处我能想到的有两个,那就是如果同样的类第二次加载可以省略这个过程,还有一个问题就是如果加载的类名相同 不同文件 的类,如果不使用双亲委派机制,而是由类本身去加载,那么equal 还有 == 则永远不能相同,原因是他们的制造者都不相同
DexClassLoader 与 PathClassLoader 有什么区别
其实从代码上来看他们两个同时都继承自BaseDexClassLoader ,
// DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
// PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
而实现方法中只有一个地方,也就是optimizedDirectory 这个String 不同,而关于optimizedDirectory他的定义其实就是指定一个内部存储路径,DexClassLoader 可以将这个内部路径下的dex文件加载到内存中,而pathclassloader 则只能加载app 安装包内的路径,不可以指定路径,这也就是热修复中的关键原理
网友评论