以下是个人实际面试中没有回答出来或者回答的不好的问题,有些题目稍微总结了一些答案,有些仅仅是一个题目,更多可以给大家一个参考。整个面试资料和问题的详细解答将逐渐在GitHub项目中解析。smart-java
文章目录
基础问题Java字符串拼接的方法i++线程安全吗Java内存泄露和内存溢出实际项目中你用过自定义的注解吗三次握手后,服务端和客户端是什么样的状态?建立连接,这个连接是什么?集合HashMap的底层原理,链表转红黑树,红黑树转链表?使用红黑树有什么好处HashSet和ArrayList的实现原理,它们的contains方法哪个效率高CurrentHashMap为什么是线程安全的,和HashTable有什么区别多线程实际项目多线程的应用场景创建线程池的方式,对线程池的了解多线程任务时间的统计,多线程之间的通信CASAQSReentrantLock实现公平锁Syncronized实现原理,锁的升级的过程非公平锁和公平锁Synchronized和Lock的比较JVMJVM如何判断两个类相同?怎么判断一个对象要回收?(可达不可达)可以作为gc root的对象类加载一定会走双亲委派吗?怎么打破双亲委派。新生代和老年代的比例一定是1:2吗?可以调吗?Java对象实例和数组元素都是在堆内存上分配的吗?逃逸分析垃圾收集器Serial 收集器ParNew 收集器Parallel Scavenge 收集器Serial Old 收集器Parallel Old 收集器CMS 收集器G1 收集器MySQLMySQL的几种日志慢查询索引最左匹配原则索引失效场景,like一定会失效吗MySQL 索引数据结构默认是 B+树 而不是 HashMySQL覆盖索引MySQL集群部署MySQL是怎么解决幻读的(可重复读)数据库索引hash、B树和B+树的区别B+树的结构B+树的层数计算SpringSpring Bean的生命周期,Spring Bean的创建过程Spring 事务的传播行为事务失效的场景Spring AOP和IoC,AOP代理的实现怎么解决Spring循环依赖SpringBoot使用SpringBoot的好处springboot+mybatis+durid中,数据库连接池是谁管理的,怎么管理的SpringCloudSpringCloud的组件服务注册中心服务调用服务熔断和降级服务网关配置中心服务总线链路追踪使用SpringCloud Feign的好处springcloud feign开放的feign接口能够承受的压力你有测过吗,实现原理是什么SpringCloud实际中你们模块是怎么划分的MyBatismybatis或jpa中是如何转化成sql语句的Redisredis怎么实现布隆过滤器的?命令。bitmap。还有什么其他的布隆过滤器的实现吗?Redis3种特殊的数据类型Redis的部署方式redis的,string的自增redis缓存不一致Redis数据类型的实际使用Redis实际项目中怎么来用的,使用Redis缓存用户临时数据熟悉Redis,熟悉备份策略,了解高并发情况下缓存雪崩的解决方案Redis跳表,zset的实现原理MQ使用MQ的好处,为什么不用异步线程举例说明你们项目中实际使用MQ的场景,它解决的是什么问题MQ顺序消费Dubbo什么是Dubbo直连分布式实际项目是怎么做限流的分布式事务的应用场景分布式锁分布式ID分布式Sessiondockerdocker命令docker镜像和仓库的区别docker实例之间的通信设计模式实际项目应用的设计模式策略模式双检锁的单例模式数据结构与算法一个TXT文档每一行存一个数字,一共有10G,内存只有1G,怎么实现对数字排序和去重堆排序算法快速排序算法二分查找算法递归求和一致性算法你了解哪些基础问题
Java字符串拼接的方法
“+”连接符
使用String的concat方法
使用StringBuffer.append()
使用StringBuilder.append()
StringUtils.join(list, “”);
比较
** 加号拼接:JVM会使用StringBuilder来实现。每次都需要创建一个StringBuilder对象,还需要每次都创建一个String对象(StringBuilder.toString()方法)。会导致性能很慢。
** concat其实就是申请一个char类型的buf数组,将需要拼接的字符串都放在这个数组里,最后再转换成String对象
** StringBuilder/StringBuffer:这两个类实现append的方法都是调用父类AbstractStringBuilder的append方法,只不过StringBuffer是的append方法加了sychronized关键字,因此是线程安全的。append代码如下,他主要也是利用char数组保存字符,通过ensureCapacityInternal方法来保证数组容量可用还有扩容。数组容量右移1位(也就是翻倍)再加2
** StringBulider > StringBuffer >> String.concat > “+”。
i++线程安全吗
i++会申请一个临时空间做i++的结果。/javastack/p/12779266.htmlJava内存泄露和内存溢出
实际项目中你用过自定义的注解吗
三次握手后,服务端和客户端是什么样的状态?建立连接,这个连接是什么?
‘’’
集合
HashMap的底层原理,链表转红黑树,红黑树转链表?
使用红黑树有什么好处
HashSet和ArrayList的实现原理,它们的contains方法哪个效率高
/larrydpk/p/11729208.html
ArrayList大概是20K纳秒,而HashSet则10纳秒左右
ArrayList的contains() ArrayList的底层使用数组作为数据存储,当给定一个Object去判断是否存在,需要去遍历数组,与每个元素对比。
ArrayList的contains()方法的时间复杂度为O(n),也就是说,时间取决于长度,而且是正比的关系
HashSet底层是通过HashMap来实现的,而HashMap的底层结构为数组+链表,JDK 8后改为数组+链表+红黑树。
首先通过获取Hash值来找,如果Hash值相等且对象也相等,则找到。一般来说,在hashCode()方法实现没问题的情况下,发生Hash冲突的情况是比较少。所以可以认为,大部分情况下,contains()的时间复杂度为O(1),元素个数不影响其速度。如果发生Hash冲突,在链表长度小于8时,时间复杂度为O(n);在链表大于8时,转化为红黑树,时间复杂度为O(logn)。一般地,我们认为,HashSet/HashMap的查找的时间复杂度为O(1)
CurrentHashMap为什么是线程安全的,和HashTable有什么区别
‘’’
多线程
实际项目多线程的应用场景
多线程处理后台任务
** 一般来说,我们需要在后台处理的任务,通常会使用定时器来开启后台线程处理,比如有些数据表的状态我需要定时去修改、我们搜索引擎里面的数据需要定时去采集、定时生成统计信息、定时清理上传的垃圾文件等。
多线程异步处理任务
** 当我们需要处理一个耗时操作并且不要立刻知道处理结果时,我们可以开启后台线程异步处理该耗时操作,这样可以提高用户体验。比如我之前做的一个项目需要上传简历附件,后台需要对简历附件进行解析,保存到数据表中,因为涉及多种格式的处理,所以我们开启多线程异步处理此操作,这样用户就不用等到我们的简历解析完就能看到服务端的响应了。再比如用户下载简历时,我们需要将数据表中的数据生成简历附件并且通过邮件发送到用户邮箱,该操作也可以开启多线程异步处理。
多线程分布式计算
** 当我们处理一个比较大的耗时任务时,我们可以将该任务切割成多个小的任务,然后开启多个线程同时处理这些小的任务,切割的数量一般根据我们服务器CPU的核数,合理利用多核CPU的优势。比如下载操作可以使用多线程下载提高下载速度;清理文件时,开启多个线程,按目录并行处理等等。
举例
** 同步商品资料到线上平台
创建线程池的方式,对线程池的了解
ThreadPoolExecutor的参数意义:核心线程数,最大线程数,队列长度,…多线程任务时间的统计,多线程之间的通信
CAS
数据a变成数据b,a要和数据库现在的值比较,等于a就改成b。否则,失败。ABA:在另一个线程中使用了a并且修改了,最后又变成了a,看起来好像没变。在我这个线程中这个a是不能被其他线程使用的。解决:版本号机制。AQS
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:* 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。底层是模板方法模式参考** 模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
** Semaphore:/p/ec637f835e08
** CyclicBarrier : /p/333fd8faa56e
** AbstractQueuedSynchronizer实现分析:/p/6e8e5a12286c
** ReentrantLock:/p/65ae75ee3f01
ReentrantLock实现公平锁
A、B两个线程同时执行lock()方法获取锁,假设A先执行获取到锁,此时state值加1,如果线程A在继续执行的过程中又执行了lock()方法(根据持有锁的线程是否是当前线程,判断是否可重入,可重入state值加1),线程A会直接获取锁,同时state值加1,state的值可以简单理解为线程A执行lock()方法的次数;当线程B执行lock()方法获取锁时,会将线程B封装成Node节点,并将其插入到同步等待队列的尾部,然后阻塞当前线程,等待被唤醒再次尝试获取锁;线程A每次执行unlock()方法都会将state值减1,直到state的值等于零则表示完全释放掉了线程A持有的锁,此时将从同步等待队列的头节点开始唤醒阻塞的线程,阻塞线程恢复执行,再次尝试获取锁。ReentrantLock公平锁的实现使用了AQS的同步等待队列和state。Syncronized实现原理,锁的升级的过程
Synchronized的实现依赖于底层操作系统,monitorenter和monitorexist指令锁的升级过程包括:无锁,偏向锁,轻量级锁,重量级锁非公平锁和公平锁
在独占式获取资源的方式中** 公平锁:按照线程等待的队列的顺序依次获取锁非公平锁:多个线程不按照排队的顺序来获取锁,谁先抢到谁先用。
Synchronized和Lock的比较
‘’’
JVM
JVM如何判断两个类相同?
类的全限定名是否相等类加载器是否相等怎么判断一个对象要回收?(可达不可达)
引用计数法** 每个对象上都有一个引用计数,对象每被引用一次,引用计数器就+1,对象引用被释放,引用计数器-1,直到对象的引用计数为0,对象就标识可以回收
** 引用计数法问题:出现循环引用,无法被回收root搜索法
** 定义了几个root,也就是这几个对象是jvm虚拟机不会被回收的对象,所以这些对象引用的对象都是在使用中的对象,这些对象未使用的对象就是即将要被回收的对象。简单就是说:如果对象能够达到root,就不会被回收,如果对象不能够达到root,就会被回收。
可以作为gc root的对象
被启动类(bootstrap加载器)加载的类和创建的对象jvm运行时方法区类静态变量(static)引用的对象jvm运行时方法去常量池引用的对象jvm当前运行线程中的虚拟机栈变量表引用的对象本方法栈中(jni)引用的对象类加载一定会走双亲委派吗?怎么打破双亲委派。
自定义类加载器,重写loadclass方法。新生代和老年代的比例一定是1:2吗?可以调吗?
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 )Java对象实例和数组元素都是在堆内存上分配的吗?
不一定,满足条件可以在虚拟机的栈上分配内存。
虚拟机栈一般是用来存储基本数据类型、引用和返回地址的,怎么可以存储实例数据了呢?
这是因为Java JIT(just-in-time)编译器进行的两项优化,分别称作逃逸分析(escape analysis)和标量替换(scalar replacement)。
JIT编译器(just in time 即时编译器):当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为(Hot Spot Code 热点代码,为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,完成这项任务的正是JIT编译器。
逃逸分析
如果一个子程序分配一个对象并返回一个该对象的指针,该对象可能在程序中被访问到的地方无法确定——这样指针就成功“逃逸”了。
如果指针存储在全局变量或者其它数据结构中,因为全局变量是可以在当前子程序之外访问的,此时指针也发生了逃逸。
JVM中的逃逸分析可以通过分析对象引用的使用范围(即动态作用域),来决定对象是否要在堆上分配内存
垃圾收集器
Serial 收集器
Serial(串行)收集器:它在进行垃圾收集工作的时候必须暂停其他所有的工作线程Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。新生代采用复制算法,老年代采用标记-整理算法。ParNew 收集器
ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。新生代采用复制算法,老年代采用标记-整理算法。它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。Parallel Scavenge 收集器
Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它看上去几乎和ParNew都一样。Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。新生代采用复制算法,老年代采用标记-整理算法。JDK1.8默认使用的是Parallel Scavenge + Parallel OldSerial Old 收集器
Serial 收集器的老年代版本,它同样是一个单线程收集器。Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。CMS 收集器是一种 “标记-清除”算法实现的主要优点:并发收集、低停顿G1 收集器
G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.static StringBuilder getStringBuilder1(String a, String b) {StringBuilder builder = new StringBuilder(a);builder.append(b);return builder; // builder通过方法返回值逃逸到外部}static String getStringBuilder2(String a, String b) {StringBuilder builder = new StringBuilder(a);builder.append(b);return builder.toString(); // builder范围维持在方法内部,未逃逸}
关闭逃逸分析还会造成频繁的GC,开启逃逸分析就没有这种情况。这说明逃逸分析确实降低了堆内存的压力。
所谓标量,就是指JVM中无法再细分的数据,比如int、long、reference等。相对地,能够再细分的数据叫做聚合量。
所以,在对象不逃逸出作用域并且能够分解为纯标量表示时,对象就可以在栈上分配。
参考:/xiaoxiaole0313/article/details/104489795/
‘’’
MySQL
MySQL的几种日志
慢查询
Sql查询语句执行很慢,一般需要举例说明索引最左匹配原则
最左优先,以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配。索引失效场景,like一定会失效吗
使用了不等式<>、!=
查询条件类型不一致,string类型我用了int值
查询条件使用了函数计算
like查询要走前缀匹配查询
联合索引,不使用首列,不会走索引
联合索引,只要有一个使用了不等式,就都不会走索引
第N个参数类型不一致,索引能使用前N-1列
like不一定会失效,只有在%在第一个位置会失效
MySQL 索引数据结构默认是 B+树 而不是 Hash
索引是一种数据结构,可以是B+树,也可以是Hash
查询速度
** B+树与树的高度有关,log(n)
** hash无论数量是多少,只有一层,o(1)
结构的区别
** B+树:数据排序,小的放左边,大的放右边。并且数据都在叶子节点,同时叶子节点之间还增加了指针形成了一个链表。只需要找到首尾就能把所有数据查出来。
** hash:是key-value的存储形式,不具有排序性
使用场景:
** B+:范围查找
** hash不能用复合索引,适用于单条记录查询或等值查询
MySQL覆盖索引
MySQL集群部署
MySQL是怎么解决幻读的(可重复读)
MySQL中使用Next-Key Lock来解决的数据库索引hash、B树和B+树的区别
B+树的结构
B+树的层数计算
MySQL最小存储单元是页(16k)非叶子节点层:主键占8个字节,指针占6个字节。16k/(8+6)约等于1170叶子节点要存储数据,1k。那么一页可以存储16k。2层:1170 * 16=18720三层:1170 * 1170 * 16 约等于219w。‘’’
Spring
Spring Bean的生命周期,Spring Bean的创建过程
实例化Bean对象设置属性值执行相关Aware接口的方法执行前置操作,调用BeanPostProcessor中的方法如果类实现了initiallizaleBean执行其中的方法如果配置了init-menthod方法执行配置的方法执行后置操作,调用BeanPostProcessor中的方法使用中如果实现了DisaposableBean接口,调用destroy方法如果配置了destroy-method方法,执行该配置方法Spring 事务的传播行为
RequiredRequires_NewNested(嵌套)MandotorySupportsNot_supportednever事务失效的场景
使用在了非public方法上事务的传播行为指定错了,以非事务的方式运行事务的异常处理指定错误异常被catch吞掉了方法出现自调用数据库引擎不支持事务Spring AOP和IoC,AOP代理的实现
怎么解决Spring循环依赖
‘’’
SpringBoot
使用SpringBoot的好处
简化配置springboot+mybatis+durid中,数据库连接池是谁管理的,怎么管理的
‘’’
SpringCloud
SpringCloud的组件
Eureka:服务发现与注册中心Ribbon:负载均衡,服务调用Feign:Web Series的客户端(RPC远程过程调用)Hystrix:断路器,服务熔断、服务降级Zull:网关Zuul:网关2.0Config:配置中心(各种配置文件的读取)Zipkin:链路跟踪服务注册中心
EurekaZooKeeperConsulNacos服务调用
RibbonLoadBalancerFeignOpenFeign服务熔断和降级
Hystrixresilice4jSentinel服务网关
ZuulGetWay配置中心
ConfigApolloNacos服务总线
BusNacos链路追踪
ZipkinSleuth使用SpringCloud Feign的好处
Feign是一种声明式、模板化的HTTP客户端。feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类似controller调用service。Spring Cloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。springcloud feign开放的feign接口能够承受的压力你有测过吗,实现原理是什么
SpringCloud实际中你们模块是怎么划分的
‘’’
MyBatis
mybatis或jpa中是如何转化成sql语句的
‘’’
Redis
redis怎么实现布隆过滤器的?命令。bitmap。还有什么其他的布隆过滤器的实现吗?
Redis3种特殊的数据类型
BitMap
** 布隆过滤器
** SETBIT key offset value:setbit key (0,2,5,9,12) 1
** 例如:储存用户在线状态。这里只需要一个 key,然后把用户 ID 作为 offset,如果在线就设置为 1,不在线就设置为 0。
** setBit(‘online’, $uid, 1);
Geo
** 可以将用户给定的地理位置信息储存起来, 并对这些信息进行操作
HyperLogLog
** 可以非常省内存的去统计各种计数,比如注册 IP 数、每日访问 IP 数、页面实时UV、在线用户数等。但是它也有局限性,就是只能统计数量,而没办法去知道具体的内容是什么。
** HyperLogLog解决海量数据统计问题
Google开源的 Guava中自带的布隆过滤器
Redis的部署方式
redis的,string的自增
redis缓存不一致
Redis数据类型的实际使用
Redis实际项目中怎么来用的,使用Redis缓存用户临时数据
熟悉Redis,熟悉备份策略,了解高并发情况下缓存雪崩的解决方案
Redis跳表,zset的实现原理
‘’’
MQ
使用MQ的好处,为什么不用异步线程
1.消息队列和多线程两者并不冲突,多线程可以作为队列的生产者和消费者。使用外部的消息队列时,第一是可以提高应用的稳定性,当程序fail后,写入外部消息队列的数据依旧是保存的,如果使用两步commit的队列的话,可以更加提高这个项目。2.用线程的话,会占用主服务器资源,消息队列的话,可以放到其他机器上运行,让主服务器尽量多的服务其他请求。3.解耦更充分,架构更合理多线程是在编程语言层面解决问题
消息队列是在架构层面解决问题
我认为架构层面解决问题是“觉悟比较高的方式“,理想情况下应该限制语言层面滥用多线程,能不用就不用。4.用线程池ExecutorService异步处理:我理解ExecutorService其实也是内部使用了队列(如LinkedBlockingQueue),所以从设计上,其实和使用中间件的消息队列是差不多一致的。只是这里应用服务器既充当生产者又充当消费者,也是消息队列中间件的实现者。这种应该适合非分布式的架构,比如简单的只有一台服务器。使用消息队列:消息队列(指activeMQ,rabbitMQ,kafaKa,Redis等)因为一般都是中间件,部署在其他机器,需要一定的网络消耗。本着解耦的目的,使用后者更合理,因为应用服务器一般内存也不会太多,队列长度不易太长。让应用服务器只处理逻辑比较合理。适合分布式架构。
举例说明你们项目中实际使用MQ的场景,它解决的是什么问题
比如:系统间单据的传递,好处是系统间的解耦。比如:并发场景下,使用MQ存数据比存数据库要快,可以提高并发量,好处是流量削峰。MQ顺序消费
‘’’
Dubbo
什么是Dubbo直连
‘’’
分布式
实际项目是怎么做限流的
分布式事务的应用场景
分布式锁
分布式ID
分布式Session
‘’’
docker
docker命令
docker镜像和仓库的区别
docker实例之间的通信
‘’’
设计模式
实际项目应用的设计模式
策略模式
双检锁的单例模式
‘’’