java面试题收集

java面试题收集

Java基础

HashMap结构

jdk1.8
Node(实现entry接口)数组+链表或红黑树
hash算法,(h = key.hashCode()) ^ (h >>> 16),hashCode右移16位,正好是32bit的一半。与自己本身做异或操作(相同为0,不同为1),为了混合哈希值的高位和低位,增加低位的随机性

HashMap负载因子默认多少,大了会怎样,小了呢

默认0.75

  1. 负载因子越大,发生碰撞的几率越高,数组中的链表越容易长或红黑树高度容易高,造成查询或插入时的比较次数增多,性能会下降。
  2. 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能

put流程,什么时候转红黑树

  1. 计算hash
  2. 如果tab为null或tab.length==0,初始化数组
  3. 根据 (n - 1) & hash 下标找到数组中key位置的node,若为空,则直接设置
  4. 否则:1)若节点hash与hash相等并且,节点key== key或节点key.equals(key)为true,则直接替换;2)若节点为TreeNode及红黑树结构,则添加到红黑树;3)否则遍历链表,若节点key==key或节点key.equals(key)为true,则直接替换,否则添加到链表尾部,如果链表长度为8,则扩容,若数组长度小于64则直接扩容,大于64则转位红黑树
    链表长度为8并且数组长度大于等于64时会转位红黑树
    扩展:为什么使用红黑树,不使用AVL(平衡二叉树)
    AVL平衡条件必须满足(所有结点的左右子树高度差不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保存平衡,而因为旋转非常耗时,由此我们可以知道AVL树适合用于插入与删除次数比较少,但查找多的情况。
    红黑树确保没有一条路径会比其他路径长出两倍,因此,红黑树是一中弱平衡二叉树(由于是弱平衡,可以看到,在相同的节点情况下,AVL树的高度低于红黑树),相对于要求严格的AVL树来说,它的旋转次数少,插入最多两次旋转,删除最多三次旋转,所以对于搜索,插入,删除操作较多的情况下,我们就用红黑树。
    红黑树的特性:
    (1)每个节点或者是黑色,或者是红色。
    (2)根节点是黑色。
    (3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
    (4)如果一个节点是红色的,则它的子节点必须是黑色的。
    (5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。[这里指到叶子节点的路径]

HashMap与HashTable区别

  1. 继承的父类不同: Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。
  2. **线程安全性不同: ** Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的,所以Hashtable是线性安全的。
  3. **是否提供contains方法: ** HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。
  4. **key和value是否允许null值: ** Hashtable中,key和value都不允许出现null值;HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
  5. **两个遍历方式的内部实现上不同: ** Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
  6. **hash值不同: ** 哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值((h = key.hashCode()) ^ (h >>> 16))。Hashtable在求hash值对应的位置索引时,用取模运算,而HashMap在求位置索引时,则用与运算
  7. **内部实现使用的数组初始化和扩容方式不同: ** HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。

HashMap之1.7和1.8的区别

  1. 数据结构: 1.7是数组+链表,1.8则是数组+链表或红黑树结构
  2. 链表新增: 1.7中新增节点采用头插法,1.8中新增节点采用尾插法
  3. hash值的计算逻辑不同: 1.7基于key自身的hashCode方法的返回值又进行了一些位运算,1.8直接拿key的hash值做高低位异或操作

ConcurrentHashMap为什么线程安全

  1. 初始化: 自旋+CAS操作+volatile, 控制数组初始化和扩容操作。
    进入一个自旋过程,一旦有线程扩容成功,才break,如果sizeCtl < 0,说明已经有线程正在扩容,所以直接让出线程(Thread.yield())。如果sizeCtl>=0,说明当前没有线程扩容,尝试CAS操作,设置sizeCtl为-1,设置失败则退出扩容,设置sizeCtl为-1成功的线程,进行扩容操作,并且将sc更新为数组负载阈值0.75*n。
  2. **put:**计算hash值,之后进入一个自旋循环过程。如果数组未初始化,则初始化; 判断hash映射的位置是否为null,如果为null,直接通过CAS自旋操作,插入元素成功,则直接返回;如果映射的位置值为MOVED(-1),则直接去协助扩容;排除以上条件后,尝试对链头Node节点f加锁,加锁成功后,链表通过尾插遍历,进行插入或替换。红黑树通过查询遍历,进行插入或替换。之后如果当前链表节点数量大于8,则调用treeifyBin函数,进行扩容或转换为红黑树,最后通过调用addCount,执行CAS操作,更新数组大小,并且判断是否需要进行扩容。
  3. 统计ConCurrentHashMap中的元素个数: baseCount+ counterCells各个元素值,就是元素数量,因为在CAS更新baseCount值时,由于高并发而导致失败,最终将值保存到CounterCell中,放到counterCells里
    参考:> https://blog.csdn.net/zycxnanwang/article/details/105424734

Io流

1字符 = 2字节; 1字节(byte) = 8位(bit); 一个汉字占两个字节长度。

  1. 分类:
    在这里插入图片描述
  2. 整体架构图:
    在这里插入图片描述
    引用:> https://baijiahao.baidu.com/s?id=1659851047751244423&wfr=spider&for=pc

编译期多态和运行时多态区别

  1. **编译时的多态:**是指参数列表的不同, 来区分不同的函数, 在编译后, 就自动变成两个不同的函数名,像方法重载
  2. 运行时多态: 用到的是后期绑定的技术, 在程序运行前不知道,会调用那个方法, 而到运行时, 通过运算程序,动态的算出被调用的地址. 动态调用在继承的时候,方法名 参数列表完全相同时才出现运行时多态!

java实例化过程,内存分配

  1. 当虚拟机遇到new对应的字节码指令时,首先检查这个指令的参数是否能在常量池中找到一个类的符号引用,并检查该引用代表的类是否已被虚拟机 加载解析初始化。如果没有则执行相应的类加载过程。
  2. 虚拟机为新生对象分配内存(对象所需的内存大小在类加载的过程中已经确定)
    内存分配有两种方式:
    🌳指针碰撞:假设Java堆中的内存是规整有序的。已用的内存聚集在一块,空闲的内存聚集在另一块。使用一个指针指向两块区域中间,那么需要分配的内存就仅仅把这个指针向空闲区域移动当前对象大小的距离。
    🌳空闲列表:如果Java堆内存是已用和空闲交错在一块,并且维护一个列表记录内存的使用情况。当需要分配一定大小的存储时,通过查询列表来获取存储空间。
    选择哪种分配方式由Java堆内存是否规整决定,而Java堆内存是否规整取决于垃圾收集器是否带有压缩的功能。
  3. 分配的内存空间初始化
    内存空间分配完成后,虚拟机必须对分配好的内存空间(不包括对象头)都进初始化为零值。这一步操作保证了对象的实例字段在Java 代码中可以不用赋值直接使用。
  4. 设置对象头
    将对象的所属类(即类的元数据信息)、对象的HashCode和对象的GC信息、锁信息等数据存放在对象的对象头中。
  5. 执行()方法进行初始化
    ()方法包含了初始化成员变量、执行实例化代码块和调用类的构造方法:

线程安全问题
由于对象的创建在虚拟机中是一个频繁的行为,可能会引起非线程安全问题。解决方法有两种:
🌴对分配内存空间的动作进行同步处理——采用CAS(比较再交换)配上失败重试的方法保证更新操作的原子性
🌴把内存分配的动作按照线程划分在不同的空间进行——每个线程预先分配一块内存(本地线程分配缓冲区TLAB),哪个线程要分配内存就在哪个TLAB中分配。当TLAB用完了,分配新的缓冲区时才需要同步锁定。

引用:> https://www.cnblogs.com/code-duck/p/13550383.html

静态代理与动态代理区别,final类可以被静态代理吗

静态代理: 程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理类: 在程序运行时,运用反射机制动态创建而成。InvocationHandler
cglib代理: 运行时生成子类对象

接口和抽象类区别

1.定义语法不通: interface、abstract
2. 一个类只能继承一个直接的父类(可能是抽象类),但一个类可以实现多个接口
3. 抽象类普通方法可以有方法体,接口方法实现要加defult关键字

对象深拷贝和浅拷贝区别

浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用。

Synchronized如何实现锁的

在堆中的每个对象将会有一个对象头,在对象头中Mark word的字段加标示
首先代码是未锁定的,当进入到synchronized块后,默认就使用了偏向锁(JDK6+),如果没有其他的线程来争抢锁资源,那么就会一直使用偏向锁就可以了,一旦有多个线程来争抢锁,那么立马就会升级会轻量级锁,因为轻量级锁采用了CAS的机制,而CAS是不断的通过自旋的方式进行抢锁的,这个样子就会造成内存资源的紧张,因此自旋到一定数据次数后,轻量级锁就会自动的升级到重量级锁,在重量级锁中通过jdk源码的share\vm\runtime下的objectMoniter.hpp文件可用看到有watiset和entrylist2个字段,这2个字段就是分别用来存放wait的线程和已经通过notify唤醒了的线程,通过这2个字段中的值的不断变化去消费线程从而达到锁机制。

Synchronized和lock区别

1.首先synchronized是java内置关键字,在jvm层面,Lock是个java接口;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可中断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

线程状态,有几种线程池,线程池参数

线程是一个动态执行的过程,它有一个从产生到死亡的过程,共五种状态:
新建(new Thread)
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)
就绪(runnable)
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();
** 运行(running)**
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
** 死亡(dead)**
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
** 堵塞(blocked)**
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用motify()方法回到就绪状态)

线程池拒绝策略

在这里插入图片描述

死锁原因、预防和解决

ThreadLocal使用场景

集合结构

在这里插入图片描述

JVM

jvm调优参数

-Xms20M  表示设置堆容量的最小值为20M,必须以M为单位
-Xmx20M    表示设置堆容量的最大值为20M,必须以M为单位。将-Xmx和-Xms设置为一样可以避免堆自动扩展。
-verbose:gc  表示输出虚拟机中GC的详细情况
-Xss128k  表示可以设置虚拟机栈的大小为128k
-Xoss128k  表示设置本地方法栈的大小为128k。不过HotSpot并不区分虚拟机栈和本地方法栈,因此对于HotSpot来说这个参数是无效的
-XX:PermSize=10M  表示JVM初始分配的永久代的容量,必须以M为单位
-XX:MaxPermSize=10M  表示JVM允许分配的永久代的最大容量,必须以M为单位,大部分情况下这个参数默认为64M
-Xnoclassgc  表示关闭JVM对类的垃圾回收
-XX:+TraceClassLoading  表示查看类的加载信息
-XX:+TraceClassUnLoading  表示查看类的卸载信息
-XX:NewRatio=4  表示设置年轻代:老年代的大小比值为1:4,这意味着年轻代占整个堆的1/5
-XX:SurvivorRatio=8  表示设置2个Survivor区:1个Eden区的大小比值为2:8,这意味着Survivor区占整个年轻代的1/5,这个参数默认为8
-Xmn20M  表示设置年轻代的大小为20M
-XX:+HeapDumpOnOutOfMemoryError  表示可以让虚拟机在出现内存溢出异常时Dump出当前的堆内存转储快照
-XX:+PrintGCDetails  表示在控制台上打印出GC具体细节
-XX:+PrintGC  表示在控制台上打印出GC信息
-XX:MaxTenuringThreshold=1  表示对象年龄大于1,自动进入老年代

"-XX"开头的参数是非稳定参数,随时可能被修改或者移除。
"-X"开头的参数是非标准参数,也就是只能被部分VM识别,而不能被全部VM识别的参数。

jvm调优工具

在这里插入图片描述

Spring

SpringMVC流程

在这里插入图片描述

SpringBean生命周期

在这里插入图片描述

FactoryBean和BeanFactory区别及内部方法

Spring能解决循环依赖不能解决什么

Spring中的三级缓存,两次曝光

AOP

@SpringBootApplication

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

Dubbo

Dubbo与springcloud区别

Dubbo服务注册与发现流程

Dubbo服务治理,怎么样使用负载均衡

Dubbo默认序列化方式

Dubbo组件

Dubbo调用流程

Dubbo线程池配置

<dubbo:protocol name=“dubbo” dispatcher=“all” threadpool=“fixed” threads=“100” />

Dispatcher
all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行。
message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
execution 只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池

ThreadPool
1. fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
2. cached 缓存线程池,空闲一分钟自动删除,需要时重建。
3. limited 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。

数据库

Sql优化步骤,explain关注字段

Mysql间隙锁哪个版本引入的,解决什么问题

可重复读的情况下防止幻读

Mysql索引类型,索引结构,组合索引,建索引要考虑什么

聚簇索引和非聚簇索引区别

以InnoDB为例:
1、聚簇索引叶子节点存储的是整行数据,每张表只能有一个聚簇索引,聚簇索引的索引顺序与数据的物理存放顺序是一致的
2、非聚簇索引叶子结点存储的是索引信息及主键信息,每张表可有多个非聚簇索引

Mysql引擎区别

在这里插入图片描述

Mysql悲观锁、乐观锁、排它锁意思及例子

事物传播,事物隔离

Redis

redis击穿、穿透、雪崩概念及解决方案

击穿: 某热点key在失效的一瞬间,持续的高并发访问就击破缓存直接访问数据库,导致数据库宕机。
解决方案:

  1. 设置热点数据"永不过期"
  2. 加上互斥锁:上面的现象是多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它 其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后将数据放到redis缓存起来。后面的线程进来发现已经有缓存了,就直接走缓存

穿透: redis缓存和数据库中没有相关数据,redis中没有这样的数据,无法进行拦截,直接被穿透到数据库,导致数据库压力过大宕机。
解决方案:

  1. 对不存在的数据缓存到redis中,设置key,value值为null(不管是数据为null还是系统bug问题),并设置一个短期过期时间段,避免过期时间过长影响正常用户使用。
  2. 拉黑该IP地址 (可能为恶意攻击)
  3. 对参数进行校验,不合法参数进行拦截
  4. 布隆过滤器 将所有可能存在的数据哈希到一个足够大的bitmap(位图)中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

雪崩: 在高并发下,大量缓存key在同一时间失效,大量请求直接落在数据库上,导致数据库宕机。
解决方案:

  1. 随机设置key失效时间,避免大量key集体失效。
  2. 若是集群部署,可将热点数据均匀分布在不同的Redis库中也能够避免key全部失效问题
  3. 不设置过期时间 跑定时任务,在缓存失效前刷进新的缓存

Redis分布式锁实现

SETNX
set

Redis为什么快

首先,采用了多路复用io阻塞机制
然后,数据结构简单,操作节省时间
最后,运行在内存中,自然速度快

Redis事务与mysql数据库事务的区别

mysql:
mysql实现事务,是基于undo/redo日志,undo记录修改前状态,rollback基于undo日志实现,redo记录修改后的状态,commit基于redo日志实现,既然是基于redo日志实现记录修改后的状态,那么大家应该也知道,redo日志是innodb专有的,所以innodb会支持事务。在mysql中无论是否开启事务,sql都会被立即执行并返回执行结果,只是事务开启后执行后的状态只是记录在redo日志,执行commit之后,数据才会被写入磁盘

redis:
redis实现事务,是基于commands队列,如果没有开启事务,command将会被立即执行并返回执行结果,并且直接写入磁盘,如果事务开启,command不会被立即执行,而是排入队列,并返回排队状态(具体依赖于客户端(例如:spring-data-redis)自身实现)。
调用exec才会执行commands队列
Redis事务不支持Rollback,没有事物隔离

主从同步

Redis主从同步策略:主从刚刚连接的时候,进行全量同步;全量同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

全量同步:Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:
1)从服务器连接主服务器,发送SYNC命令;
(2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
(3)主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
(4)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
(5)主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
(6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
增量同步:Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

MQ

RabbitMq流程、死信队列

exchange 类型

  1. Exchange类型direct
    他是根据交换器名称与routingkey来找队列的。
    只要找到路由器与routingkey绑定的队列,那么他有多少队列,他就分发给多少队列。
  2. Exchange类型fanout
    这个类型忽略Routingkey,他为广播模式。
    只要queue与exchange有绑定,那么他不管你的Routingkey是什么他都会将消息分发给所有与该exchang绑定的队列中。
  3. Exchange类型topic
    他是根据RoutingKey的设置,来做匹配的,其中这里还有两个通配符为:
    ,代表任意的一个词。例如topic.zlh.,他能够匹配到,topic.zlh.one ,topic.zlh.two ,topic.zlh.abc, …
    #,代表任意多个词。例如topic.#,他能够匹配到,topic.zlh.one ,topic.zlh.two ,topic.zlh.abc, …
  4. headers Exchange:headers类型的交换机分发消息不依赖routingKey,是使用发送消息时basicProperties对象中的headers来匹配的。headers是一个键值对类型,发送者发送消息时将这些键值对放到basicProperties对象中的headers字段中,队列绑定交换机时绑定一些键值对,当两者匹配时,队列就可以收到消息。匹配模式有两种,在队列绑定到交换机时用x-match来指定,all代表定义的多个键值对都要满足,而any则代码只要满足一个就可以了

Mq作用

如何保证消息不丢失

1.消息持久化

2.ACK确认机制

3.设置集群镜像模式

4.消息补偿机制

如何保证消息顺序性

JVM

G1、cms区别

Jvm划分,对象从新生代到老年代过程

垃圾清除算法

Mybatis

Mybatis启动流程

mybatis一级缓存和二级缓存

mybatis使用的设计模式

zk

zk分布式锁

ZAB协议

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值