Java-八股文

dengliang356a / 2023-07-08 / 原文

  1. hashcode()、equals()
  2. 1.如果hashcode()不相同,两个对象一定不是同一个对象
    2.如果hashcode()相同,两个对象不一定上同一个对象,需要进一步判别equals
    3.如果equals相同,应当认为两个对象就是相同对象
    由于hashcode方法仅返回一个值,equals里面有若干逻辑,因此,部分集合类的判断中,会优先判断hashcode,如果相同再继续equals判断
    结论:如果改写了equals方法,则必须改写hashcode方法,以便于逻辑一致
    
  3. string、stringbuffer、stringbuilder
  4. string:常量,每次修改都会创建新的字符串常量
    stringbuffer:线程安全的字符串变量
    stringbuilder:不安全的字符串变量
    
  5. extends、super
  6. <? extends T>:?是T的子类
    <? super T>:?是T的基类
    
  7. == 、 equals
  8. ==:基本类型-看值;引用类型-看引用地址
    euqals:看各种类的重写逻辑[逻辑里面也有可能复用==]
    
  9. 重载、重写
  10. 重载:针对单一类中,相同方法名,不同方法签名
    重写:针对父子类,相同方法名,相同方法签名
    重写:返回值类型、抛出异常都必须小于等于父类;访问修饰符大于等于父类
    
  11. list、set
  12. list:有序,可重复,允许多个null,支持下标随机访问
    set:无序,不可重复,单一null,必须遍历访问
    
  13. arraylist、linkedlist
  14. arraylist:基于数组实现,有利于查找、修改,不利于插入、删除[适用场景不同]
    linkedlist:基于链表实现,有利于插入、删除,不利于查找、修改[适用场景不同]
    
  15. concurrenthashmap扩容机理
  16. 1.7之前:基于segment分段hashmap存储实现的,segment分段存储部分不扩容,仅内部的hashmap进行扩容[哪个线程对应的内部hashmap需要扩容,哪个线程就负责做这个事情]
    1.8之后:不是基于segment分段存储实现的,所有的K-V对都包含在一个map中,只是扩容的时候,那就每个子线程都参与扩容
    
  17. JDK版本变迁,hashmap的主要变更
  18. 1.7:底层是数组+链表,哈希算法复杂
    1.8:底层是数组+链表+红黑树,由于引入红黑树,哈希算法得到简化,从而优化hashmap的插入、查询效率
    
  19. hashmap的put方法
  20. 大体流程:
    1.依据key和哈希算法计算下标
    2.如果下标位置为空,则封装成对象(1.7:entry对象,1.8:node对象),放置该位置
    3.如果下标位置不空,
    3.1 JDK1.7:判断是否需要扩容,然后,头插法插入到对应位置的链表中
    3.2 JDK.8:判断是红黑树节点还是链表节点,然后插入到hashmap里面,最后判断扩容
    3.2.1 红黑树节点:将KV对封装成红黑树节点,加入到红黑树中
    3.2.2 链表节点:尾插法插入到对应位置的链表中,如果多于等于8个节点,将链表转为红黑树
    
  21. 深拷贝与浅拷贝
  22. 浅拷贝:基本数据类型拷贝了第二份,引用类型的变量全部都是拷贝了一份引用地址,指向了原来的地方。
    深拷贝:所有数据类型都拷贝了第二份,不仅仅拷贝引用地址。
    
  23. hashmap扩容机理
  24. 1.7:生成新数组,遍历原数组上的每个链表,将内部数据逐个转移至新数组
    1.8:生成新数组,遍历原数组上的每个链表与红黑树
    ①如果原数组上是链表,遍历每个元素,重新计算下标并转移
    ②如果原数组上是红黑树,遍历每个元素,重新计算下标并转移,有冲突再购建链表与红黑树
    
  25. copyonwriteArrayList
  26. 线程安全的arrayList,底层也是用数组实现的,主要集中在读与写操作上
    读:由于读写分别在老新数组上,因此,互相不干扰,也因此,读的性能不会受写的性能影响[适用于读多写少]
    写:写操作会生成新数组,在完成之前,其他线程无法进行写操作[上了锁,线程安全];在完成之前,读的是原数组,写的是新数组,两者是不会互相干扰的。
    
  27. 字节码
  28. 定义:字节码文件是Java源码编译过后的一种格式,各个平台上相同的源码编译出的字节码是相同的,但是,字节码转化的机器码不相同,这个转化上JDK(JRE)做的,因此,Java上跨平台语言
    相当于编译过程所指的,中间代码[前有词法分析、语法分析、语义分析][后有代码优化、目标代码生成]
    优点:实现了语言的跨平台;编译过程可以做代码优化,提高执行效率
    
  29. Java异常体系
  30. graph TD; Throwable --> Exception Throwable --> Error Exception --> RuntimeException Exception --> 非RuntimeException

    error是非常严重的错误,程序已无法正常运行,没必要捕获
    运行时异常,一般是逻辑问题,应尽量避免
    非运行时异常,不处理无法正常运行,必须处理

  31. 什么时候抛异常?什么时候捕获异常?
  32. 抛:自身无法处理就往上抛,由上级处理
    捕获:自身能处理就捕获,捕获走既定处理流程

  33. 类加载器
  34. graph TD; bootstrapClassLoader负责加载JAVA_HOME下lib里面的jar包与类文件 --> ExtClassLoader负责加载JAVA_HOME下lib下ext里面的jar包与类文件 --> AppClassLoader负责加载classpath路径下的类文件
  35. 类加载器的双亲委派模型
  36. 由16可知,类加载器共三个,AppClassLoader有两个父类
    当AppClassLoader加载时,会优先调用父类的加载器,这种JVM机制叫做双亲委派模型
    AppClassLoader.loadClass(){
      ExtClassLoader.loadClass(){
        bootstrapClassLoader.loadClass();
      }
    }
    
  37. 线程共享区
  38. ![](https://img2023.cnblogs.com/blog/3090785/202306/3090785-20230628171602882-13131299.png)
    堆区与方法区是所有线程共享的
    栈区:主要存放基本数据类型与对象引用(仅限局部变量,成员变量不管是不是基本数据类型都存放于堆)
    堆区:主要存放对象实例与数组[我的理解就是,由代码操作生成的对象或者说引用类型存放于堆]
    方法区:类信息(class文件)、静态变量与静态方法
    本地方法栈:非静态方法
    
  39. 如何排查JVM问题-※
  40. 空间:jmap 查看 JVM各个区域的占用情况,查看是否栈区过大?是否堆区过大?
    时间:jstack 查看线程的运行情况,是否存在死锁或者阻塞?
    OOM:
    查看: jstat 查看垃圾回收情况,fullgc 与 younggc 的运行情况
    记录: JVM启动参数 --> -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base,发生OOM的时候记录下当时的情况-dump文件,便于回溯定位问题
    分析:jvisualvm 可以针对dump文件进行分析异常对象,异常线程
    
  41. 从加载JVM到GC清理,一共经历什么过程-√
  42. 1.方法区加载字节码,加载静态方法、静态成员变量
    2.堆区创建实例化对象,保存至 年轻代 Eden 区
    3.将引用传回栈区
    4.经过minor GC,对象每次存活,年龄加一,并迁移至survival区-存活区,年龄大于等于15的时候,对象进入老年代
    5.栈区的引用被移除以后,对象最终被GC回收
    
  43. 如何确定对象是否需要回收
  44. 根可达算法:所有对象都是基于一个根对象上的,从根对象开始遍历,能找到引用的就是还需要的对象,否则就是不需要的对象。
    
  45. JVM垃圾回收机制
  46. 总共三种算法:
    标记:将所有对象判断是否根可达,将不可达对象标记并进行删除
    缺点:会有碎片空间出现
    
    复制法:申请一块与当前存储空间相同的空间,根可达算法标记所有存活对象,将存活对象迁移至新申请空间,迁移完毕以后,原空间统一清空
    缺点:需要的空间较大
    
    标记优化法:根可达算法标记所有存活对象,并将存活对象重新排列至连续空间,标记以及迁移排列完毕以后,将存活对象连续空间以外的空间统一清除
    
  47. 什么是STW
  48. STW,stop the world,即,在GC运行过程中,要将所有Java进程冻结,以便于GC进程进行垃圾回收。
    
  49. 常用的JVM启动参数有哪些?-※
  50. 线程安全的理解?
  51. 线程安全说的是,当多个线程并发访问互斥资源时,读写互斥资源的代码逻辑能正常处理,获得正确结果,不会互相干扰的情况。
    
  52. 守护线程的理解?
  53. 守护线程是与普通线程相区分的概念,用户一般使用的就是普通线程,普通线程有自身独立的生命周期,而守护线程的生命周期取决于普通线程,只有当所有相关的普通线程都结束时,守护线程才会结束。
    守护线程是为所有普通线程提供服务的线程
    
  54. threadlocal的底层原理
  55. threadlocal:在线程里面多保存了一份map结构,由于变量存在线程内部,从而保证不同线程互相不干扰。
    现在大多数用的都是线程池,由于线程都是复用的,而threadlocal往线程里面添加了一个map,因此,需要代码里面手动回收移除threadlocal保存的数据,否则,线程一直复用,数据量会一直累加至OOM。
    
  56. 并发、并行与串行之间的区别?
  57. 并行:多条处理流同时执行,例如多端口同时工作,多条线路同时传送数据
    串行:多条处理流排队执行。
    并发:例如微机系统上有多个进程存活,从宏观上,多个进程上同时工作,是个并行的流程,但是,从微观底层上来看,多个进程是由CPU通过时间片轮转,逐个调度执行的,是个串行的流程,这个就是并发。
    
  58. Java死锁应如何避免?-√
  59. Java死锁由四个因素导致:
    • 资源是互斥资源 每次仅可由单个线程持有
    • 在获得所有资源之前,线程自身不释放已持有资源
    • 所需资源在其他线程上,且不可被剥夺
    • 循环依赖,A持有B所需资源,B持有A所需资源
    应当打破第四点因素,即可避免死锁发生。 即将所有线程持有资源的顺序固定,避免产生循环依赖 每个锁的持有时间应当设置过期时间,确保锁最终一定会被释放
  60. 线程池底层工作原理-√
  61. num:线程数量
    当有任务需求时,
    当num<poolSize时,优先创建线程
    当num>poolSize时,且queue没满时,将任务添加到队列中排队
    当maxpoolsize>num>poolSize时,且queue已满时,增加线程处理任务
    当maxpoolsize==num,且queue已满时,不再新增线程,会按照handler所指定策略处理新任务。
    同时会检查,是否有线程的空闲时间大于keepalivetime,如果有,终止对应线程
    
  62. 线程池中阻塞队列的作用?线程池为何先添加队列而不是先创建线程-√
  63. 阻塞队列可以通过阻塞保留那些想要入队的任务;阻塞队列自带阻塞和唤醒功能,不需要一直占有CPU资源。
    因为创建新线程需要获取全局锁,影响其他线程的效率,且线程数量不足有可能是临时性不足,因此,优先排队等待。
    
  64. ReentrantLock中的公平锁与非公平锁的底层实现-※
  65. 加锁[竞争锁]:
    公平锁:新线程直接进入到队列
    非公平锁:先直接竞争锁,竞争不到再进入队列
    
    唤醒:无论公平锁还是非公平锁都会直接唤醒队列第一个获取锁
    
  66. ReentrantLock的tryLock()与lock()区别
  67. tryLock()可能加锁成功[返回真],也可能加锁不成功[返回假],非阻塞加锁
    lock()阻塞性加锁,一直执行,直到加锁成功
    
  68. CountDownLatch与Semphore的区别和底层原理-※
  69. sychronized的偏向锁、轻量锁以及重量锁
  70. sychronized语法,某个线程持有锁的时候,锁就已经是偏向锁
    当某个线程持有锁,且有其他线程也在等待锁释放,锁就升级为轻量锁
    某个线程持有锁,其他线程会自旋尝试获取锁,自旋次数达到一定程度,线程仍然没有获取到锁时,轻量锁就升级为重量锁
    
  71. ReentrantLock与synchronized的区别?-√
  72. 含义:ReentrantLock是一个类;synchronized是一个关键字
    是否公平:ReentrantLock可以加公平锁,也可以加非公平锁;synchronized只能是非公平锁。
    synchronized自动加锁释放锁,ReentrantLock需要手动加锁释放锁
    synchronized是JVM层面的锁,ReentrantLock是API层面的锁
    
  73. 对AQS的理解,AQS如何实现可重入锁-※
  74. AQS是一个用于JAVA线程同步的框架。
    AQS内部维护了一个状态变量state和一个线程双向队列。
    在可重入锁的场景下,state表示加锁次数,当state减少至0时,代表持有锁的线程释放锁了,就唤醒下一个线程竞争锁。
    
  75. 如何实现接口幂等性
  76. 所谓的幂等性,指的是,相同的请求,发一次和发多次,效果应当是稳定一致的。
    就请求类型而言,
    删除、查询
    1.通过请求里面带的ID,或者请求参数拼接计算出一个ID,将ID存入Redis等存储中间件,用ID来标识请求是否处理过
    修改、新增
    2.除了阻断请求到数据库处理以外,还可以通过在数据库处理的时候,加上乐观锁,保证如果有其他请求修改的话,就放弃修改
    
  77. 布隆过滤器原理及优缺点
  78. 原理
    存
    1.每个key通过若干哈希算法,得到对应下标
    2.对应下标的位置全部置为1
    取
    3.给定key计算对应下标,如果都为1,可能在里面,有0,肯定不在里面
    
    优点:
    1.占用空间小
    2.速度快,耗时少,O(K),K为哈希算法数量
    
    缺点:
    1.仅能判断在或不在,拿不到原值
    2.存在假阳性误判情况
    
  79. arraylist
  80. arraylist

参考:

  1. https://blog.csdn.net/liwenxiang629/article/details/109508530
  2. https://blog.csdn.net/weixin_43235210/article/details/90444710?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-90444710-blog-109508530.235%5Ev38%5Epc_relevant_sort&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-90444710-blog-109508530.235%5Ev38%5Epc_relevant_sort&utm_relevant_index=2