(高频问题)1-20 计算机 Java后端 实习 and 秋招 面试高频问题汇总
专栏地址 https://www.nowcoder.com/creation/manager/columnDetail/0ybw76
专栏介绍 https://www.nowcoder.com/discuss/745223576873156608?sourceSSR=users
1.Java 中 3 种常见的 IO 模型
BIO
BIO 即 Blocking I/O
;字面意思就可以看出它属于同步阻塞 IO。
如下图,应用程序发出一个 read 调用,内核空间需要经历准备数据的几个阶段,准备好之后返回数据给应用程序。期间如果另一个应用程序也需要 read 调用,那么它必须等待;这就是阻塞。
BIO 最大的特点就是一次只能处理一个调用,这在高并发的场景下肯定是不行的。
NIO
NIO 就是 Non-blocking I/O。字面翻译为非阻塞
非阻塞模型:关键词是 轮询 ,例如小明需要找人帮忙,于是找到张三,第一次张三在忙,第二次张三还在忙,此后小明的做法是每一个小时来一次,直到等到张三有空为止。该做法很不明智,具体体现在浪费了小明的时间,来来回回都是需要消耗处理器资源的。
I/O 多路复用模型
这种 IO 模型同样存在问题:应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。
这个时候,I/O 多路复用模型 就上场了。
IO多路复用是指使用一个系统调用(如select、poll、epoll等)来同时监控多个文件描述符(如socket、文件、管道等),当其中某些文件描述符发生了可读、可写或异常事件时,系统调用返回,通知应用程序进行相应的IO操作。IO多路复用可以减少系统调用的次数,避免不必要的阻塞,提高并发性能。但是IO多路复用也有一些缺点,比如需要维护一个文件描述符集合,可能有一定的开销;对于大量的文件描述符,可能会超过系统的限制;对于某些事件,可能需要轮询才能发现。
非阻塞IO是指在进行IO操作时,如果数据没有准备好,不会阻塞当前进程或线程,而是立即返回一个错误码(如EWOULDBLOCK或EAGAIN),让应用程序可以继续执行其他任务。非阻塞IO可以避免等待数据的时间,提高CPU的利用率。但是非阻塞IO也有一些缺点,比如需要不断地轮询文件描述符的状态,可能会浪费CPU资源;对于每个文件描述符,只能进行一次IO操作,不能保证数据的完整性;对于某些情况,可能会发生假唤醒或惊群现象。
IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间->用户空间)还是阻塞的。
目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,是目前几乎在所有的操作系统上都有支持
AIO (Asynchronous I/O)
AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
2.HTTPS加密算法、原理、知道有哪些对称加密和非对称加密算法吗
HTTPS就是HTTP加上SSL/TLS加密处理
HTTPS 建立连接的过程中,一共发起两次请求,进行非对称和对称两次加密
1.客户端发送一个https的请求到服务端
2.服务端申请配置好数字证书,包含公钥和私钥
3.服务端将证书传送给客户端,证书中包含了很多信息,比如证书的颁发机构,过期时间,网址,公钥等
4.客户端解析证书,由客户端的TLS完成,首先会验证公钥是否有效,比如颁发机构,过期时间等。如果有异常,就会弹出警告信息,并结束通信。如果正常,则生成一个随机值(用于对称加密),然后用服务端的公钥对随机值进行非对称加密
5.客户端将加密后的随机值传送到服务端
6.服务端使用证书的私钥非对称解密得到客户端的随机值,用获取的随机值将传输的明文内容进行对称加密
7.服务端把对称加密后的数据传输到客户端
8.客户端通过随机值对称解密获取明文内容
非对称加密的加解密效率是非常低的,而 http 的应用场景中通常端与端之间存在大量的交互,非对称加密的效率是无法接受的。另外,在 HTTPS 的场景中只有服务端保存了私钥,一对公私钥只能实现单向的加解密,所以 HTTPS 中内容传输加密采取的是对称加密,而不是非对称加密
加密算法我们整体可以分为:可逆加密和不可逆加密,可逆加密又可以分为:对称加密和非对称加密。
不可逆加密算法 MD5
对称加密算法是应用比较早的算法,在数据加密和解密的时用的都是同一个密钥,AES
,对称加密算法的安全性相对较低
非对称加密算法有两个密钥,只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。常见的非对称加密有RSA
3.SpringCloud组件了解哪些
springcloud五大组件:1、Eureka实现服务治理;2、Ribbon主要提供客户侧的软件负载均衡算法;3、Hystrix断路器,保护系统,控制故障范围;4、Zuul(spring cloud gateway),api网关,路由,负载均衡等多种作用;5、Config配置管理
1、Eureka
作用:实现服务治理(服务注册与发现)
Eureka服务端用作服务注册中心。支持集群部署。
Eureka客户端是一个java客户端,用来处理服务注册与发现。
在应用启动时,Eureka客户端向服务端注册自己的服务信息,同时将服务端的服务信息缓存到本地。客户端会和服务端周期性的进行心跳交互,以更新服务租约和服务信息。
2、Ribbon
作用:Ribbon,主要提供客户侧的软件负载均衡算法。
简介:Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。
注意看上图,关键点就是将外界的rest调用,根据负载均衡策略转换为微服务调用。Ribbon有比较多的负载均衡策略,以后专门讲解。
3、Hystrix
作用:断路器,保护系统,控制故障范围。
简介:为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
4、Zuul
作用:api网关,路由,负载均衡等多种作用
spring-cloud-Gateway
是spring-cloud的一个子项目。而zuul则是netflix公司的项目,只是spring将zuul集成在spring-cloud中使用而已。因为zuul2.0连续跳票和zuul1的性能表现不是很理想,所以催生了spring团队开发了Gateway项目。
简介:类似nginx,反向代理的功能,不过netflix自己增加了一些配合其他组件的特性。
在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。
5、Config
作用:配置管理
简介:SpringCloud Config提供服务器端和客户端。服务器存储后端的默认实现使用git,因此它轻松支持标签版本的配置环境,以及可以访问用于管理内容的各种工具。
这个还是静态的,得配合Spring Cloud Bus实现动态的配置更新。
4.ConcurrentHashMap如何实现的线程安全
JDK 1.7 中 ConcurrentHashMap 是如何保证线程安全
ConcurrentHashMap 的线程安全是建立在 Segment 加锁的基础上的,这样就能保证多个线程同时访问 ConcurrentHashMap 时,同一时间只有一个线程能操作相应的节点,这样就保证了 ConcurrentHashMap 的线程安全了。
在 JDK 1.7 中,ConcurrentHashMap 虽然是线程安全的,但因为它的底层实现是数组 + 链表的形式,所以在数据比较多的情况下访问是很慢的,因为要遍历整个链表,而 JDK 1.8 则使用了数组 + 链表/红黑树的方式优化了 ConcurrentHashMap 的实现
链表升级为红黑树的规则:当链表长度大于 8,并且数组的长度大于 64 时,链表就会升级为红黑树的结构。
在 JDK 1.8 中 ConcurrentHashMap 使用的是 CAS + volatile 或 synchronized 的方式来保证线程安全的
sychronized锁机制,当线程进入synchronized代码块前,获得锁,其他线程就会被阻塞挂起。当该线程正常退出或者抛出异常或者调用wait()系列方法释放锁时,其他线程才有机会获得锁,执行该代码块。简而言之,sychronized锁机制通过阻塞来确保java操作的原子性
CAS就是Compare And Swap,是JDK提供给的非阻塞性原子性操作。它通过硬件保证了比较--更新的原子性操作
自旋锁,比较并交换、保证原子操作;通过volatile实现数据的可见性 CAS是非阻塞的、轻量级的乐观锁,利用 CPU 指令保证操作的原子性,以达到锁的效果,并且性能还很高;适合简单对象操作,比如布尔值、整型值
volatile,通过内存屏障保证可见性、不保证原子性、禁止指令重排,当变量被volatile修饰时,它会保证修改的值会立即写回主内存
ConcurrentHashMap 是在头节点加锁来保证线程安全的,锁的粒度相比 Segment 来说更小了,发生冲突和加锁的频率降低了,并发操作的性能就提高了。而且 JDK 1.8 使用的是红黑树优化了之前的固定链表,那么当数据量比较大的时候,查询性能也得到了很大的提升,从之前的 O(n) 优化到了 O(logn) 的时间复杂度
- ConcurrentHashMap 是 Java 中支持高并发、高吞吐量的线程安全 HashMap 实现。
- 在 Java 7 中,ConcurrentHashMap 采用了分段锁的设计,将整个哈希表分为若干个 Segment,每个 Segment 有自己的锁,这样可以减少锁的粒度,提高并发访问的效率。
- 在 Java 8 中,ConcurrentHashMap 放弃了分段锁的设计,而是采用了 CAS(Compare and Swap)和 synchronized 来实现更好的并发性能。它使用 CAS 操作来保证节点插入和删除的原子性,使用 synchronized 来保证节点更新的互斥性。
- 在 Java 8 中,ConcurrentHashMap 还引入了红黑树的结构来优化链表过长的情况,当链表长度超过一定阈值时,会将链表转换为红黑树,这样可以降低查找的时间复杂度。
5.Java可达性分析算法
引用计数算法
在对象中添加一个引用计数器,每当新加一个引用时,计数器就加1,当引用失效时,计数器就减1。任何时刻只要计数器为0就代表对象没有引用可以被回收。
这种算法实现简单,判断高效,但是有一些缺点
可达性分析算法
目前主流的商用JVM都是通过可达性分析来判断对象是否可以被回收
通过一系列被称为「GC Roots」的根对象作为起始节点集,从这些节点开始,通过引用关系向下搜寻,搜寻走过的路径称为「引用链」,如果某个对象到GC Roots没有任何引用链相连,就说明该对象不可达,即可以被回收。
对象可达指的就是:双方存在直接或间接的引用关系。根可达或GC Roots可达就是指:对象到GC Roots存在直接或间接的引用关系。
垃圾回收时,JVM首先要找到所有的GC Roots,这个过程称作 「枚举根节点」 ,这个过程是需要暂停用户线程的,即触发STW。 然后再从GC Roots这些根节点向下搜寻,可达的对象就保留,不可达的对象就回收。
Stop-the-World,简称STW,指的是GC事件发生过程中,会产生应用程序的停顿。 停顿产生时整个应用程序线程都会被暂停,没有任何响应。 有点像卡死的感觉
GC Roots就是对象,而且是JVM确定当前绝对不能被回收的对象(如方法区中类静态属性引用的对象 )。只有找到这种对象,后面的搜寻过程才有意义,不能被回收的对象所依赖的其他对象肯定也不能回收嘛。
1、方法区静态属性引用的对象 全局对象的一种,Class对象本身很难被回收,回收的条件非常苛刻,只要Class对象不被回收,静态成员就不能被回收。
2、方法区常量池引用的对象 也属于全局对象,例如字符串常量池,常量本身初始化后不会再改变,因此作为GC Roots也是合理的。
3、方法栈中栈帧本地变量表引用的对象 属于执行上下文中的对象,线程在执行方法时,会将方法打包成一个栈帧入栈执行,方法里用到的局部变量会存放到栈帧的本地变量表中。只要方法还在运行,还没出栈,就意味这本地变量表的对象还会被访问,GC就不应该回收,所以这一类对象也可作为GC Roots。
4、JNI本地方法栈中引用的对象 和上一条本质相同,无非是一个是Java方法栈中的变量引用,一个是native方法(C、C++)方法栈中的变量引用。
5、被同步锁持有的对象 被synchronized锁住的对象也是绝对不能回收的,当前有线程持有对象锁呢,GC如果回收了对象,锁不就失效了嘛。
6.Python怎么解决引用计数循环引用问题
python 采用的是引用计数机制为主,标记 - 清除和分代收集两种机制为辅的策略。
只靠强引用计数方式,会存在循环引用的问题,导致对象永远无法被释放,弱引用就是专门用来解决循环引用问题的:
若 A 强引用了 B,那 B 引用 A 时就需使用弱引用,当判断是否为无用对象时仅考虑强引用计数是否为 0,不关心弱引用计数的数量
这样就解决了循环引用导致对象无法释放的问题
但这会引发野指针问题:当 B 要通过弱指针访问 A 时,A 可能已经被销毁了,那指向 A 的这个弱指针就变成野指针了。在这种情况下,就表示 A 确实已经不存在了,需要进行重新创建等其他操作
7.生产过程中发现问题,内存飙高/OOM,怎么去定位和解决问题?
某Java服务(假设PID=19813)出现了OOM,最常见的原因为:
- 有可能是内存分配确实过小,而正常业务使用了大量内存
- 某一个对象被频繁申请,却没有释放,内存不断泄漏,导致内存耗尽
- 某一个资源被频繁申请,系统资源耗尽,例如:不断创建线程,不断发起网络连接
一、确认是不是内存本身就分配过小 jmap -heap 19813
上图,可以查看新生代,老生代堆内存的分配大小以及使用情况,看是否本身分配过小。
二、找到
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
曾获多国内大厂的 ssp 秋招 offer,且是Java5年的沉淀老兵(不是)。专注后端高频面试与八股知识点,内容系统详实,覆盖约 30 万字面试真题解析、近 400 个热点问题(包含大量场景题),60 万字后端核心知识(含计网、操作系统、数据库、性能调优等)。同时提供简历优化、HR 问题应对、自我介绍等通用能力。考虑到历史格式混乱、质量较低、也在本地积累了大量资料,故准备从头重构专栏全部内容