跳槽涨薪必备精选⾯试题

本文详细介绍了Java后端开发面试中常见的面试题,包括String对象的intern方法、Integer对象的缓存机制、StringBuffer与StringBuilder的区别、ArrayList与LinkedList的不同、CopyOnWriteArrayList的工作原理、HashMap的扩容机制、ConcurrentHashMap的并发扩容、ThreadLocal的底层原理、volatile关键字的作用、ReentrantLock的公平锁与非公平锁、线程池的工作原理、JVM的内存区域、类加载器的双亲委派模型、Tomcat的类加载优化及请求处理流程。此外,还讨论了跨域请求的解决方案和Spring Bean的生命周期。这些问题涵盖了Java性能优化、并发编程、面试准备等多个重要领域。

 

 

                                 
作者:图灵课堂-------周瑜

1、看以下代码回答问题(⼀) 答案:  

2、看以下代码回答问题(⼆)

 

 

答案:

1. s1 == s2为false

2. s2 == s3为true

String对象的intern⽅法,⾸先会检查字符串常量池中是否存在"abc",如果存在则返回该字符串引⽤,

如果不存在,则把"abc"添加到字符串常量池中,并返回该字符串常量的引⽤。

三、看以下代码回答问题(三)

答案:

1. i1 == i2为true

2. i3 == i4为false

在Interger类中,存在⼀个静态内部类IntegerCache, 该类中存在⼀个Integer cache[], 并且存在⼀

个static块,会在加载类的时候执⾏,会将-128⾄127这些数字提前⽣成Integer对象,并缓存在cache数

组中,当我们在定义Integer数字时,会调⽤Integer的valueOf⽅法,valueOf⽅法会判断所定义的数字

是否在-128⾄127之间,如果存在则直接从cache数组中获取Integer对象,如果超过,则⽣成⼀个新的

Integer对象。

4、String、StringBuffer、StringBuilder的区别

1. String是不可变的,如果尝试去修改,会新⽣成⼀个字符串对象,StringBuffer和StringBuilder是

可变的

2. StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效

率会更⾼

5、ArrayList和LinkedList有哪些区别

1. ⾸先,他们的底层数据结构不同,ArrayList底层是基于数组实现的,LinkedList底层是基于链表实

现的

2. 由于底层数据结构不同,他们所适⽤的场景也不同,ArrayList更适合随机查找,LinkedList更适合

删除和添加,查询、添加、删除的时间复杂度不同

3. 另外ArrayList和LinkedList都实现了List接⼝,但是LinkedList还额外实现了Deque接⼝,所以

LinkedList还可以当做队列来使⽤

6、CopyOnWriteArrayList的底层原理是怎样的

1. ⾸先CopyOnWriteArrayList内部也是⽤过数组来实现的,在向CopyOnWriteArrayList添加元素

时,会复制⼀个新的数组,写操作在新数组上进⾏,读操作在原数组上进⾏

2. 并且,写操作会加锁,防⽌出现并发写⼊丢失数据的问题

3. 写操作结束之后会把原数组指向新数组

4. CopyOnWriteArrayList允许在写操作时来读取数据,⼤⼤提⾼了读的性能,因此适合读多写少的应

⽤场景,但是CopyOnWriteArrayList会⽐较占内存,同时可能读到的数据不是实时最新的数据,所

以不适合实时性要求很⾼的场景

7、HashMap的扩容机制原理

1.7版本

1. 先⽣成新数组

2. 遍历⽼数组中的每个位置上的链表上的每个元素

3. 取每个元素的key,并基于新数组⻓度,计算出每个元素在新数组中的下标

4. 将元素添加到新数组中去

5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

1.8版本

1. 先⽣成新数组

2. 遍历⽼数组中的每个位置上的链表或红⿊树

3. 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去

4. 如果是红⿊树,则先遍历红⿊树,先计算出红⿊树中每个元素对应在新数组中的下标位置

a. 统计每个下标位置的元素个数

b. 如果该位置下的元素个数超过了8,则⽣成⼀个新的红⿊树,并将根节点的添加到新数组的对

应位置

c. 如果该位置下的元素个数没有超过8,那么则⽣成⼀个链表,并将链表的头节点添加到新数组

的对应位置

5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

8、ConcurrentHashMap的扩容机制

1.7版本

1. 1.7版本的ConcurrentHashMap是基于Segment分段实现的

2. 每个Segment相对于⼀个⼩型的HashMap

3. 每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似

64. 先⽣成新的数组,然后转移元素到新数组中

5. 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值

1.8版本

1. 1.8版本的ConcurrentHashMap不再基于Segment实现

2. 当某个线程进⾏put时,如果发现ConcurrentHashMap正在进⾏扩容那么该线程⼀起进⾏扩容

3. 如果某个线程put时,发现没有正在进⾏扩容,则将key-value添加到ConcurrentHashMap中,然

后判断是否超过阈值,超过了则进⾏扩容

4. ConcurrentHashMap是⽀持多个线程同时扩容的

5. 扩容之前也先⽣成⼀个新的数组

6. 在转移元素时,先将原数组分组,将每组分给不同的线程来进⾏元素的转移,每个线程负责⼀组或

多组的元素转移⼯作

9、ThreadLocal的底层原理

1. ThreadLocal是Java中所提供的线程本地存储机制,可以利⽤该机制将数据缓存在某个线程内部,

该线程可以在任意时刻、任意⽅法中获取缓存的数据

2. ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对

象)中都存在⼀个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的

3. 如果在线程池中使⽤ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使⽤完之后,应该要

把设置的key,value,也就是Entry对象进⾏回收,但线程池中的线程不会回收,⽽线程对象是通过

强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收,

Entry对象也就不会被回收,从⽽出现内存泄漏,解决办法是,在使⽤了ThreadLocal对象之后,⼿

动调⽤ThreadLocal的remove⽅法,⼿动清楚Entry对象

4. ThreadLocal经典的应⽤场景就是连接管理(⼀个线程持有⼀个连接,该连接对象可以在不同的⽅

法之间进⾏传递,线程之间不共享同⼀个连接)

10、如何理解volatile关键字

在并发领域中,存在三⼤特性:原⼦性、有序性、可⻅性。volatile关键字⽤来修饰对象的属性,在并发

环境下可以保证这个属性的可⻅性,对于加了volatile关键字的属性,在对这个属性进⾏修改时,会直接

将CPU⾼级缓存中的数据写回到主内存,对这个变量的读取也会直接从主内存中读取,从⽽保证了可⻅

性,底层是通过操作系统的内存屏障来实现的,由于使⽤了内存屏障,所以会禁⽌指令重排,所以同时

也就保证了有序性,在很多并发场景下,如果⽤好volatile关键字可以很好的提⾼执⾏效率。

11、ReentrantLock中的公平锁和⾮公平锁的底层实现

⾸先不管是公平锁和⾮公平锁,它们的底层实现都会使⽤AQS来进⾏排队,它们的区别在于:线程在使

⽤lock()⽅法加锁时,如果是公平锁,会先检查AQS队列中是否存在线程在排队,如果有线程在排队,

则当前线程也进⾏排队,如果是⾮公平锁,则不会去检查是否有线程在排队,⽽是直接竞争锁。

不管是公平锁还是⾮公平锁,⼀旦没竞争到锁,都会进⾏排队,当锁释放时,都是唤醒排在最前⾯的线

程,所以⾮公平锁只是体现在了线程加锁阶段,⽽没有体现在线程被唤醒阶段。

另外,ReentrantLock是可重⼊锁,不管是公平锁还是⾮公平锁都是可重⼊的。

 

12、ReentrantLock中tryLock()和lock()⽅法的区别

1. tryLock()表示尝试加锁,可能加到,也可能加不到,该⽅法不会阻塞线程,如果加到锁则返回

true,没有加到则返回false

2. lock()表示阻塞加锁,线程会阻塞直到加到锁,⽅法也没有返回值

13、CountDownLatch和Semaphore的区别和底层原理

CountDownLatch表示计数器,可以给CountDownLatch设置⼀个数字,⼀个线程调⽤

CountDownLatch的await()将会阻塞,其他线程可以调⽤CountDownLatch的countDown()⽅法来对

CountDownLatch中的数字减⼀,当数字被减成0后,所有await的线程都将被唤醒。

对应的底层原理就是,调⽤await()⽅法的线程会利⽤AQS排队,⼀旦数字被减为0,则会将AQS中

排队的线程依次唤醒。

Semaphore表示信号量,可以设置许可的个数,表示同时允许最多多少个线程使⽤该信号量,通

过acquire()来获取许可,如果没有许可可⽤则线程阻塞,并通过AQS来排队,可以通过release()

9⽅法来释放许可,当某个线程释放了某个许可后,会从AQS中正在排队的第⼀个线程开始依次唤

醒,直到没有空闲许可。

14、Sychronized的偏向锁、轻量级锁、重量级锁

1. 偏向锁:在锁对象的对象头中记录⼀下当前获取到该锁的线程ID,该线程下次如果⼜来获取该锁就

可以直接获取到了

2. 轻量级锁:由偏向锁升级⽽来,当⼀个线程获取到锁后,此时这把锁是偏向锁,此时如果有第⼆个

线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻

量级锁底层是通过⾃旋来实现的,并不会阻塞线程

3. 如果⾃旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞

4. ⾃旋锁:⾃旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就⽆所谓唤醒线程,阻塞和唤醒

这两个步骤都是需要操作系统去进⾏的,⽐较消耗时间,⾃旋锁是线程通过CAS获取预期的⼀个标

记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程⼀直在运

⾏中,相对⽽⾔没有使⽤太多的操作系统资源,⽐较轻量。

15、Sychronized和ReentrantLock的区别

1. sychronized是⼀个关键字,ReentrantLock是⼀个类

2. sychronized会⾃动的加锁与释放锁,ReentrantLock需要程序员⼿动加锁与释放锁

3. sychronized的底层是JVM层⾯的锁,ReentrantLock是API层⾯的锁

4. sychronized是⾮公平锁,ReentrantLock可以选择公平锁或⾮公平锁

5. sychronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识

来标识锁的状态

6. sychronized底层有⼀个锁升级的过程

16、线程池的底层⼯作原理

线程池内部是通过队列+线程实现的,当我们利⽤线程池执⾏任务时:

1. 如果此时线程池中的线程数量⼩于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建

新的线程来处理被添加的任务。

2. 如果此时线程池中的线程数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放⼊

缓冲队列。

3. 如果此时线程池中的线程数量⼤于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数

量⼩于maximumPoolSize,建新的线程来处理被添加的任务。

104. 如果此时线程池中的线程数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等

于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

5. 当线程池中的线程数量⼤于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被

终⽌。这样,线程池可以动态的调整池中的线程数

17、JVM中哪些是线程共享区

堆区和⽅法区是所有线程共享的,栈、本地⽅法栈、程序计数器是每个线程独有的

18、JVM中哪些可以作为gc root

什么是gc root,JVM在进⾏垃圾回收时,需要找到“垃圾”对象,也就是没有被引⽤的对象,但是直接

找“垃圾”对象是⽐较耗时的,所以反过来,先找“⾮垃圾”对象,也就是正常对象,那么就需要从某

些“根”开始去找,根据这些“根”的引⽤路径找到正常对象,⽽这些“根”有⼀个特征,就是它只会引⽤其

他对象,⽽不会被其他对象引⽤,例如:栈中的本地变量、⽅法区中的静态变量、本地⽅法栈中的变

量、正在运⾏的线程等可以作为gc root。

19、你们项⽬如何排查JVM问题

对于还在正常运⾏的系统:

1. 可以使⽤jmap来查看JVM中各个区域的使⽤情况

2. 可以通过jstack来查看线程的运⾏情况,⽐如哪些线程阻塞、是否出现了死锁

3. 可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc⽐较频繁,那么就得进⾏

调优了

4. 通过各个命令的结果,或者jvisualvm等⼯具来进⾏分析

5. ⾸先,初步猜测频繁发送fullgc的原因,如果频繁发⽣fullgc但是⼜⼀直没有出现内存溢出,那么表

示fullgc实际上是回收了很多对象了,所以这些对象最好能在younggc过程中就直接回收掉,避免

这些对象进⼊到⽼年代,对于这种情况,就要考虑这些存活时间不⻓的对象是不是⽐较⼤,导致年

轻代放不下,直接进⼊到了⽼年代,尝试加⼤年轻代的⼤⼩,如果改完之后,fullgc减少,则证明

修改有效

6. 同时,还可以找到占⽤CPU最多的线程,定位到具体的⽅法,优化这个⽅法的执⾏,看是否能避免

某些对象的创建,从⽽节省内存

对于已经发⽣了OOM的系统:

1. ⼀般⽣产系统中都会设置当系统发⽣了OOM时,⽣成当时的dump⽂件(

-

XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base

2. 我们可以利⽤jsisualvm等⼯具来分析dump⽂件

3. 根据dump⽂件找到异常的实例对象,和异常的线程(占⽤CPU⾼),定位到具体的代码

4. 然后再进⾏详细的分析和调试

总之,调优不是⼀蹴⽽就的,需要分析、推理、实践、总结、再分析,最终定位到具体的问题

20、说说类加载器双亲委派模型

JVM中存在三个默认的类加载器:

1. BootstrapClassLoader

2. ExtClassLoader

3. AppClassLoader

AppClassLoader的⽗加载器是ExtClassLoader,ExtClassLoader的⽗加载器是

BootstrapClassLoader。

JVM在加载⼀个类时,会调⽤AppClassLoader的loadClass⽅法来加载这个类,不过在这个⽅法中,会

先使⽤ExtClassLoader的loadClass⽅法来加载类,同样ExtClassLoader的loadClass⽅法中会先使⽤

BootstrapClassLoader来加载类,如果BootstrapClassLoader加载到了就直接成功,如果

BootstrapClassLoader没有加载到,那么ExtClassLoader就会⾃⼰尝试加载该类,如果没有加载到,

那么则会由AppClassLoader来加载这个类。

所以,双亲委派指得是,JVM在加载类时,会委派给Ext和Bootstrap进⾏加载,如果没加载到才由⾃⼰

进⾏加载。

21、Tomcat中为什么要使⽤⾃定义类加载器

⼀个Tomcat中可以部署多个应⽤,⽽每个应⽤中都存在很多类,并且各个应⽤中的类是独⽴的,全类

名是可以相同的,⽐如⼀个订单系统中可能存在com.zhouyu.User类,⼀个库存系统中可能也存在

com.zhouyu.User类,⼀个Tomcat,不管内部部署了多少应⽤,Tomcat启动之后就是⼀个Java进程,

也就是⼀个JVM,所以如果Tomcat中只存在⼀个类加载器,⽐如默认的AppClassLoader,那么就只能

加载⼀个com.zhouyu.User类,这是有问题的,⽽在Tomcat中,会为部署的每个应⽤都⽣成⼀个类加载

器实例,名字叫做WebAppClassLoader,这样Tomcat中每个应⽤就可以使⽤⾃⼰的类加载器去加载⾃

⼰的类,从⽽达到应⽤之间的类隔离,不出现冲突。另外Tomcat还利⽤⾃定义加载器实现了热加载功

能。

22、Tomcat如何进⾏优化?

对于Tomcat调优,可以从两个⽅⾯来进⾏调整:内存和线程。

⾸先启动Tomcat,实际上就是启动了⼀个JVM,所以可以按JVM调优的⽅式来进⾏调整,从⽽达到

Tomcat优化的⽬的。

另外Tomcat中设计了⼀些缓存区,⽐如appReadBufSize、bufferPoolSize等缓存区来提⾼吞吐量。

还可以调整Tomcat的线程,⽐如调整minSpareThreads参数来改变Tomcat空闲时的线程数,调整

maxThreads参数来设置Tomcat处理连接的最⼤线程数。

并且还可以调整IO模型,⽐如使⽤NIO、APR这种相⽐于BIO更加⾼效的IO模型。

23、浏览器发出⼀个请求到收到响应经历了哪些步骤?

1. 浏览器解析⽤户输⼊的URL,⽣成⼀个HTTP格式的请求

2. 先根据URL域名从本地hosts⽂件查找是否有映射IP,如果没有就将域名发送给电脑所配置的DNS进

⾏域名解析,得到IP地址

3. 浏览器通过操作系统将请求通过四层⽹络协议发送出去

4. 途中可能会经过各种路由器、交换机,最终到达服务器

5. 服务器收到请求后,根据请求所指定的端⼝,将请求传递给绑定了该端⼝的应⽤程序,⽐如8080被

13tomcat占⽤了

6. tomcat接收到请求数据后,按照http协议的格式进⾏解析,解析得到所要访问的servlet

7. 然后servlet来处理这个请求,如果是SpringMVC中的DispatcherServlet,那么则会找到对应的

Controller中的⽅法,并执⾏该⽅法得到结果

8. Tomcat得到响应结果后封装成HTTP响应的格式,并再次通过⽹络发送给浏览器所在的服务器

9. 浏览器所在的服务器拿到结果后再传递给浏览器,浏览器则负责解析并渲染

24、跨域请求是什么?有什么问题?怎么解决?

跨域是指浏览器在发起⽹络请求时,会检查该请求所对应的协议、域名、端⼝和当前⽹⻚是否⼀致,如

果不⼀致则浏览器会进⾏限制,⽐如在www.baidu.com的某个⽹⻚中,如果使⽤ajax去访问

www.jd.com是不⾏的,但是如果是img、iframe、script等标签的src属性去访问则是可以的,之所以浏

览器要做这层限制,是为了⽤户信息安全。但是如果开发者想要绕过这层限制也是可以的:

1. response添加header,⽐如resp.setHeader("Access-Control-Allow-Origin", "*");表示可以访问

所有⽹站,不受是否同源的限制

2. jsonp的⽅式,该技术底层就是基于script标签来实现的,因为script标签是可以跨域的

3. 后台⾃⼰控制,先访问同域名下的接⼝,然后在接⼝中再去使⽤HTTPClient等⼯具去调⽤⽬标接⼝

4. ⽹关,和第三种⽅式类似,都是交给后台服务来进⾏跨域访问

25、Spring中的Bean创建的⽣命周期有哪些步骤

Spring中⼀个Bean的创建⼤概分为以下⼏个步骤:

1. 推断构造⽅法

2. 实例化

3. 填充属性,也就是依赖注⼊

4. 处理Aware回调

5. 初始化前,处理@PostConstruct注解

6. 初始化,处理InitializingBean接⼝

7. 初始化后,进⾏AOP

当然其实真正的步骤更加细致,可以看下⾯的流程图

 

26、Spring中Bean是线程安全的吗

Spring本身并没有针对Bean做线程安全的处理,所以:

1. 如果Bean是⽆状态的,那么Bean则是线程安全的

2. 如果Bean是有状态的,那么Bean则不是线程安全的

另外,Bean是不是线程安全,跟Bean的作⽤域没有关系,Bean的作⽤域只是表示Bean的⽣命周期范

围,对于任何⽣命周期的Bean都是⼀个对象,这个对象是不是线程安全的,还是得看这个Bean对象本

身。

27、ApplicationContext和BeanFactory有什么区别

BeanFactory是Spring中⾮常核⼼的组件,表示Bean⼯⼚,可以⽣成Bean,维护Bean,⽽

ApplicationContext继承了BeanFactory,所以ApplicationContext拥有BeanFactory所有的特点,也

是⼀个Bean⼯⼚,但是ApplicationContext除开继承了BeanFactory之外,还继承了诸如

EnvironmentCapable、MessageSource、ApplicationEventPublisher等接⼝,从⽽

ApplicationContext还有获取系统环境变量、国际化、事件发布等功能,这是BeanFactory所不具备的

28、Spring中的事务是如何实现的

151. Spring事务底层是基于数据库事务和AOP机制的

2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean

3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解

4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接

5. 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮

常重要的⼀步

6. 然后执⾏当前⽅法,⽅法中会执⾏sql

7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务

8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务

9. Spring事务的隔离级别对应的就是数据库的隔离级别

10. Spring事务的传播机制是Spring事务⾃⼰实现的,也是Spring事务中最复杂的

11. Spring事务的传播机制是基于数据库连接来做的,⼀个数据库连接⼀个事务,如果传播机制配置为

需要新开⼀个事务,那么实际上就是先建⽴⼀个数据库连接,在此新数据库连接上执⾏sql

 

 

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值