avatar

目录
Java面试复习

1、为什么阿里巴巴编码规范中规定不让使用Executors定义线程池?

Executors主要包含了种创建线程池的方法:

  • newFixedThreadPool():创建一个固定线程数量的线程池
  • newSingleThreadExecutor():创建一个只包含一个线程的线程池
  • newCachedThreadPool():创建一个自适应(不受控制)的线程池
  • newScheduledThreadPool():创建一个大小无限的线程池

为什么规定不让使用Executors定义线程池?

  1. newFixedThreadPool()newSingleThreadExecutor()所创建的线程池,在任务量非常大的时候,请求队列会无休止的消耗内存,最终导致OOM
  2. newCacheThreadPool()newScheduledThreadPool()所创建的线程池,在任务量非常大的时候,会创建数量非常多的线程,最终导致OOM

解决办法:使用ThreadPoolExecutor创建线程池。

原因如下:

首先,ThreadPoolExecutor类的构造方法为:

Code
1
2
3
4
5
6
7
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

各个参数介绍:

  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数
  • keepAliveTime:当线程数大于核心线程时候,多余的空余线程在终止之前的等待时间
  • unit:参数keepAliveTime的时间单位
  • workQueue:一个阻塞队列,用来存储等待执行的任务
  • threadFactory(可省略):线程工厂,用于创建线程
  • handler(可省略):拒绝策略,当阻塞队列为满且线程数大于最大线程时候,程序的拒绝策略

使用ThreadPoolExecutor创建线程池的方式,可以让编写程序的同学更加明确线程池的运行规则,规避OOM的风险。

2、堆和栈的区别是什么?

Java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建

Java堆的目的是存放对象实例几乎所有的对象实例都在这里分配内存。

Java堆是垃圾收集器管理的主要区域,所以也被称为“GC堆”。

Java堆可以处于物理上不连续的内存空间中,只要逻辑上连续的即可。

如果在堆中没有内存完成实例分配,并且堆也无法在扩展时候,将会抛出OOM。

Java虚拟机栈是线程私有的,它的生命周期与线程相同。

Java虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时,都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

Java虚拟机栈中的局部变量表存放了编译器可知的各种基本数据类型对象引用(reference)returnAddress类型(指向一条字节码指令的地址)

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。

如果虚拟机栈可以动态扩展但扩展时无法申请到足够的内存,就会抛出OOM异常。

区别

  1. 栈内存保存的是局部变量,堆内存保存的是实体
  2. 栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短
  3. 栈内存中存放的变量生命周期一旦结束就会被释放,堆内存中存放的实体需要虚拟机进行GC来释放。

3、数组是在堆上分配内存还是栈上分配内存?

数组的引用在栈中,对象在堆中。图示如下:

引用变量与引用对象断开

4、关系型数据库和NoSQL的区别是什么?为什么要使用NoSQL?

关系型数据库NoSQL的区别

关系型数据库 NoSQL
存储方式 表格式存储,容易关联协作存储,提取数据很方便 将数据大块的组合在一起,通常存储在数据集中
存储结构 结构化数据,数据表预先定义了结构,结构描述了数据的形式和内容 基于动态结构,使用非结构化数据,一般使用key-value的形式存储数据
存储规范 把数据分割为最小的关系以避免重复 存储在平面数据集中,数据经常可能会重复,单个数据库很少被分开,而是存储为一个整体
存储扩张 纵向扩展,因为操作瓶颈可能涉及到多个表,需要通过提升计算机性能来克服 横向扩展,可以通过给资源池添加更多的普通数据库服务器来分担负载
查询方式 使用结构化查询语句(SQL)来操作数据库;使用索引加快查询操作 使用非结构化查询语言(UnQI)
事务 遵循ACID原则,对事务支持很好;支持对事务原子性细粒度控制,并且抑郁会滚事务 遵循BASE原则,对事务的支持不是很好,只能在CAP中任选两项
性能 为了维护数据的一致性付出了巨大的代价,读写性能比较差 采用K-V的方式存储数据,并且存储在内存中,因此非常容易存储;无序解析SQL,提高了读写性能
授权方式 一般都是付费且价格昂贵,成本较大 一般都是开源的

名词解释:

  • ACID原则
    • A(Atomicity):原子性,指事务是一个不可以再分割的工作单元,事务中的操作要么都发生,要么都不发生
    • C(Consistency):一致性,指事务开始之前和结束之后,数据库的完整性约束没有被破坏
    • I(Isolation):隔离性,指多个事务并发访问时,事务之间是隔离的
    • D(Durability):持久性,指完成的事务对数据的更改是持久的,并不会被回滚
  • BASE原则
    • BA(Basically Available):基本可用,指分布式系统在出现不可预知的故障的时候,允许损失部分可用性。
    • S(Soft state):弱状态,指允许系统中的数据存在中间众泰,并认为该中间状态的存在不会影响系统的整体可用性(允许系统在不同的节点之间的数据副本进行数据的同步过程存在延迟)。
    • E(Eventually consistent):最终一致性,指系统中所有的数据副本,在经过一段时间的同步后,最终能达到一个一直的状态。
  • CAP定理:
    • C(Consistent):一致性,指将一个数据副本在一个节点上进行了更新操作并更新成功后,其他的节点上的数据也得到相应的更新。
    • A(Available):可用性,指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果.
    • P:分区容错性,指分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性的可用性的服务,除非整个网络环境都发生故障。

为什么要使用NoSQL

  1. 关系型数据库扩展困难
  2. 关系型数据库读写慢
  3. 关系型数据库成本高
  4. 关系型数据库仅有有限的支撑容量
  5. NoSQL扩展简单
  6. NoSQL读写速度快
  7. NoSQL成本低廉

4、都了解过哪些NoSQL框架?区别是什么?

当前较为流行的NoSQL数据库有:Redis、Memcache、MongoDb

Redis

优点

  1. 支持多种数据结构
  2. 支持持久化操作,可以进行AOF及RDB数据持久化到磁盘,从而进行数据备份或数据恢复等操作
  3. 支持通过Replication进行数据复制,通过master-slave机制,可以实时进行数据的同步复制,支持多级复制和增量复制,master-salve机制是Redis进行HA(高可用性)的重要手段
  4. 单线程跪求,所有命令串行执行,并发情况下不需要考虑数据一致性问题
  5. 支持pub/sub消息订阅机制,可以用来进行消息订阅与通知,也可以用来做简单的消息队列
  6. 支持简单的事务需求

缺点

  1. Redis只能使用单线程,性能受限于CPU
  2. Redis在string类型上会消耗较多内存

名词解释

  • RDB持久化:把当前进程数据生成快照保存到硬盘的过程
  • AOF持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的。
  • master-salve机制:主从设备模式,核心思想是基于分而治之的思想,将一个原始任务分解为若干个语义等同的子任务,并由专门的工作者线程来并行执行这些任务,原始任务的结果是通过整合各个子任务的处理结果形成的。

Memcache

优点

  1. 可以利用多核优势,单实例吞吐量极高
  2. 支持直接配置为session handle

缺点

  1. 只支持简单的key-value数据结构
  2. 无法进行持久化,数据不能备份
  3. 无法进行数据同步
  4. 内存分配采用Slab Allocation机制管理内存,value大小分布差异较大时候回造成内存利用率降低,并引发低利用率时一餐出现踢出等问题,需要用户注重value设计

MongoDB

优点

  1. 更高的写负载,更高的插入速度
  2. 很容易的分割表
  3. 高可用性,设置master-selve不仅方便而且很快,还可以快速、安全、自动化的实现节点故障转移
  4. 查询块
  5. 添加一个新列不会对旧表格有任何影响,整个过程非常快速

缺点

  1. 不支持事务
  2. 占用空间过大
  3. 没有成熟的维护工具

5、mybatis和Hibernate的区别是什么?各自有什么优缺点?分别适合什么场景

区别

MyBatis Hibernate
编写实现 手动编辑SQL语句 自动生成SQL语句
性能比较 性能消耗较小 性能消耗较大
执行细节 用户需要自己对对象进行详细的管理 对象的状态管理全部交由hibernate
缓存机制 二级缓存,对应不同的xml文件可以有不同的缓存机制 二级缓存,二级缓存灵活性较差
扩展性 扩展性较差 扩展性良好

mybatis

优点

  1. 入门简单,提供了数据表查询的自动对象绑定功能
  2. 可以进行更为细致的SQL优化,可以减少查询字段

缺点

  1. 数据库移植性较差
  2. 功能较弱
  3. 需要同时维护SQL和结果映射

hibernate

优点

  1. DAO层开发比较简单
  2. 对增删改查的对象维护更方便
  3. 数据库移植性较好
  4. 更好的二级缓存机制

缺点

  1. 上手难度大,门槛较高
  2. 性能开销大

适用场景

Mybatis:需求多变的互联网项目

Hibernate:需求明确、也无固定的项目

6、SpringBoot中starter的原理是什么?

首先,在starter的jar包下的META-INF文件夹中包含了两个文件spring.factoriesspring-configuration-metadata.json,第一个文件中描述了SpringBoot在启动的时候需要自动装配哪个类,第二个文件中描述了自动装配时候需要的配置信息。

在starter的源码中,需要一个自动装配的类,其类上包含注解:

  1. @Configuration:表明该类是一个配置类
  2. @EnableConfigurationProperties(xxxProperties.class):启用对@ConfigurationProperties注解的bean的支持,这个xxxProperties.class就是需要装配的Bean的配置类

该类中至少包含一个被@Bean注解的公开方法,该方法返回一个装配好的Bean

然后就是需要一个xxxPriperties类,该类上需要包含注解@ConfigurationProperties,表明该类是一个配置类且配置需要从配置文件中读取;然后该类中还包含了需要装配的Bean中需要配置的全部字段。

至此,在SpringBoot启动的时候,就可以通过SpringFactoriesLoader完成对starter包进行自动装配了。

7、为什么SpringBoot应用可以直接通过main函数启动?

8、知道什么是数据库范式吗?

数据库范式分为:第一范式、第二范式、第三范式及BCNF范式。

  • 第一范式:当且仅当所有域只包含原子值,即每个分量都是不可再分的数据项,则称实体满足第一范式
  • 第二范式:当且仅当实体满足第一范式,且每一个非键属性完全依赖主键时,满足第二范式
  • 第三范式:当且仅当实体满足第三范式,且实体中没有非主属性传递依赖时,满足第三范式
  • BCNF范式:
    • 所有非主属性对每一个码都是完全函数依赖
    • 所有的主属性对每一个不包含它的码,也是完全函数依赖
    • 没有任何属性完全函数依赖于非码的任何一组属性

BCNF举例,如下图:

img

  1. 新增加一个仓库,不存放物品,只分配管理员
  2. 对某一个仓库清仓
  3. 某个仓库更改管理员

9、Set和List的区别是什么?

  1. Set不可以插入重复元素,List可以插入重复元素
  2. Set只能插入一个null元素,List可以插入多个null元素
  3. Set元素在容器内无序,List在容器内有序

10、HashMap是线程安全的吗?和hashtable、concurrenthashmap的区别是什么?

HashMap不是线程安全的。

Hashtable

  1. 底层是数组+链表实现
  2. key和value都不能为null
  3. 线程安全,实现线程安全的方式是给所有方法添加synchronized关键字
  4. 计算下标方法为index = (hash & 0x7FFFFFFF) % tab.length
  5. 默认初始值为11,扩容因子为0.75
  6. 扩容方式为newCapacity = (oldCapacity << 1) + 1;

HashMap

  1. 底层是数组+链表实现
  2. key和value都可以是null
  3. 线程不安全
  4. 初始size为16,初始扩容因子为0.75
  5. 扩容方式为newThr = oldThr << 1,扩容时重新计算整个hash表,重新插入
  6. 插入元素后才判断扩容
  7. 下标计算方式为index = hash & (tab.length – 1)

ConcurrentHashMap

  1. 底层是分段的数组+链表
  2. 线程安全,实现线程安全的方式是锁分段技术,即允许多个修改操作并发进行
  3. 把Map分为N个段,在保证线程安全的前提下,效率提升N倍
  4. 某些方法例如size(),需要锁定所有段
  5. 扩容方式是段内扩容,不会对整个Map进行扩容
  6. 插入前检测需不需要扩容

11、说几种你用过的设计模式?单例模式知道吗?单例可以被破坏吗?

装饰器模式、迭代器模式、桥接模式

单例模式分为:懒汉型单例模式、饿汉型单例模式、双重校验锁(DCL)单例模式、登记式/静态内部类单例模式、枚举单例模式

除了枚举单例模式以外,其他的单例模式都可以被破坏,下面是demo

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main{
public staatic void main(String...args){
Class clazz = Class.forName("Singleton");
Constructor c= clazz.getDeclaredConstructor(null);

c.setaccessible(true);

Singleton s1 = c.newInstance();
Singleton s2 = c.newInstance();

System.out.println(s1==s2);
// 输出为false
}
}

12、HTTP常见错误码说几个,404、500、403等等。

  • 404:找不到资源,前端找不到界面或后端找不到相应的接口
  • 500:服务器处理请求的时候出现了故障
  • 403:请求自愿被拒绝,可以不用给出原因的拒绝
  • 401:请求自愿需要认证

13、OSI七层模型说一下,TCP/UDP区别?知道什么是HTTPS吗?

OSI七层模型由上到下依次是:应用层、表现层、会话层、传输层、网络层、链路层、物理层

TCP/IP四层模型由上到下一次是:应用层、传输层、网络层、链路层

TCP/UDP的区别

  1. TCP使用数据流传输、UDP使用数据包传输
  2. TCP连接需要进行3次握手,断开连接需要4次挥手;UDP不需要握手
  3. TCP是可靠的数据传输,UDP是不可靠的数据传输
  4. TCP传输效率低、UDP传输效率高
  5. TCP仅支持1对1的链接,UDP支持1对多、多对一等连接

HTTPS

指安全的HTTP通道。在HTTP的基础上通过加密和身份确认保证了传输过程的安全性。

HTTPS是在HTTP的基础上添加了SSL证书,默认使用443端口。

加密流程:

  1. 客户端第一次访问服务器
  2. 服务器收到请求,并将存储在服务器中的公钥返回给客户端
  3. 客户端收到公钥(即数字证书),对齐进行合法验证。验证完毕后,生成一串随机的client key并使用公钥进行加密,发送给服务器
  4. 服务器接收到加密后的client key后,使用私钥进行解密,得到client key
  5. 服务器使用client key加密响应报文,发送给客户端
  6. 客户端收到服务器发来的加密响应报文,解密后,得到实际响应数据。

14、用过LINUX吗?常见的命令知道吗?

top命令

类似于Windows的任务管理器,能够实时显示系统中各个进程的资源占用状况。如下图所示

image-20200220130424428

  • 第一行:任务队列信息,通uptime命令的执行结果

    • 系统时间:13:04:14
      • 运行时间:up 55 days
      • 当前登录用户:1
      • 负载均衡(uptime):0.01,0.00,0.00(分别表示1分钟、5分钟、15分钟负载情况)
  • 第二行:Tasks——任务(进程)

    • 总进程106 total
    • 运行 1 running
    • 休眠 69 sleeping
    • 停止 0 stopped
    • 僵尸进程 0 zombie
  • 第三行:CPU状态信息

    • 0.7 us 用户空间占用CPU百分比
    • 0.7 sy 内核空间占用CPU百分比
    • 0.0 ni 改变过优先级的进程占用CPU百分比
    • 98.1 id 空闲CPU百分比
    • 0.0 wa IO等待占用CPU百分比
    • 0.0 hi 硬中断CPU占用百分比
    • 0.0 st 软中断CPU占用百分比
  • 第四行:内存状态

    • 总容量:204876 total
    • 空闲容量: 107504 free
    • 使用中的容量:1394324 used
    • 缓存的内存量:539132 buff/cache
  • 第五行:交换分区信息

    • aaa
  • 第六行:空行

  • 第七行:各个进程的状态监控

    • PIO:进程ID

    • USER:进程所有者

    • PR:进程优先级

    • NI:nice值,负值表示高优先级,正值表示低优先级

    • VIRT:进程使用的虚拟内存总量,单位kb,VIRT=SWAP+RES

    • RES:进程使用的,未被换出的物理内存大小,单位kb。RES=CODE+DATA

    • SHR:共享内存大小,单位kb

    • S:进程状态,D=不可中断的睡眠状态,R=运行,S=睡眠,I=空闲状态,T=跟踪/停止,Z=僵尸进程

    • %CPU:上次更新到现在的CPU时间占用百分比

    • %MEM:进程使用的物理内存百分比

    • TIME+:进程实用的CPU时间总计,单位10ms

    • COMMAND:进程名称

      top运行中可以通过top的内部命令对进程的显示方式进行控制,内部命令如下

      s–改变画面更新频率

      I–关闭/开启状态监控栏

      t–关闭/开启第一部分第二行Tasks和第三行Cpus信息的表示

      m–关闭/开启第一部分第四行Men和第五航Swap信息的表示

      N–以PID的大小顺序排序进程列表

      P–以CPU占用率大小的顺序排序进程列表

      M–以内存占用率大小的顺序排列进程列表

      h–显示帮助
      n–设置在进程列表所显示进程的数量

      q–退出top

chmod

改变文件/文件夹的权限

语法:chmod [-cfvR] [--help] [--version] mode file...

mode为权限字符串,格式为[ugoa...][[+-=][rwxX]...][,...]

u表示该文件的拥有者,g表示与该文件的拥有者同处于一个组的用户,o表示其他人,a表示全部

+表示增加权限,-表示减少权限,=表示唯一设定权限

r表示可读,w表示可写入,x表示可执行

chmod还可以通过3位数字来表示用户、用户组、其他人的权限

r=4(100),w=2(010),x=1(001)

例如chmod 777 file

ping

语法:ping [-dfnqrRv][-c<完成次数>][-i<间隔秒数>][-I<网络界面>][-l<前置载入>][-p<范本样式>][-s<数据包大小>][-t<存活数值>][主机名称或IP地址]

15、知道maven、git等工具吗?介绍下作用?

maven是一款项目构建和项目依赖管理的工具

git是一个开源的分布式版本控制系统。

maven的作用:

  • 通过代码进行项目依赖管理,比如引入log4j2依赖
  • 通过代码进行项目模块化管理,比如spring boot
  • 轻松打包项目生成可执行包(jar或war)
  • 可以集中进行代码测试,例如junit
  • 可以将自己的代码打包发布到maven仓库

git的作用:

  • 将自己的代码上传到云端仓库,比如上传到github
  • 可以再不同的计算机上从云端仓库下载代码
  • 进行版本控制
  • 进行分支管理

16、www.taobao.com在浏览器中访问的全过程

  1. 用户输入在浏览器地址栏中输入完www.taobao.com后,浏览器会检查缓存中有没有这个域名对应的DNS解析IP,如果缓存中有,解析过程结束;如果没有,继续检查用户操作系统的hosts文件中有没有对域名的DNS解析,如果有,解析过程结束;如果没有,就先向DNS服务器发送一个数据包,DNS服务器找到后将解析所得的IP地址返回给用户。
  2. 应用层:浏览器给服务器发送一个HTTP请求,请求头为GET http://www.taobao.com/ HTTP/1.1
  3. 传输层:

17、 事务概念以及事务隔离级别

事务是一个不可分割的工作单位,事务中的操作,要么都做,要么都不做。

事务的特征:原子性、一致性、隔离性、持久性

事务的等级:

  1. 读未提交
  2. 读提交
  3. 重复读
  4. 序列化

18、 字符串拼接的几种方式

  • 拼接方式
    • 使用+拼接字符串
    • 使用concat方法拼接字符串
    • StringBuffer.append()
    • StringBuilder.append()
    • StringUtils.join()
    • StringJoiner.add()

19、进程与线程的区别

  1. 进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位
  2. 进程最少要包含一个线程
  3. 系统会为进程分配独立的内存空间,而不会为线程分配内存
  4. 进程可以理解为线程的容器

20、描述一下TCP的三次握手、四次挥手

三次握手

  1. 客户端向服务器发送请求,包头为SYN=1,seq=x
  2. 服务器接收到客户端发来的请求,响应请求,包头为SYN=1,ACK=1,ack=x+1,seq=y
  3. 客户端收到服务器的响应,再向服务器发送请求ACK=1,seq=x+1,ack=y+1

四次挥手

  1. 客户端向服务器发送断开连接请求,包头为FIN=1,seq=u
  2. 服务器收到客户端发来的断开连接请求,响应请求,包头为ACK=1,seq=v,ack=u+1
  3. 服务器想客户端发送断开连接请求,包头为FIN=1,ACK=1.seq=w,ack=u+1
  4. 客户端收到服务器发来的断开连接请求,响应请求,包头为ACK=1,seq=u+1,ack=w+1

为什么不是两次握手或者四次握手?

如果是两次握手,服务器无法知道客户端是否已经和自己建立起连接

如果是四次握手,会额外多出一次tcp通信,造成不必要的资源浪费

下面是一个很好的解释案例

三次握手:
“喂,你听得到吗?”
“我听得到呀,你听得到我吗?”
“我能听到你,今天balabala……”

两次握手:
“喂,你听得到吗?”
“我听得到呀”
“喂喂,你听得到吗?”
“草,我听得到呀!!!!”
“你TM能不能听到我讲话啊!!喂!”
“……”

四次握手:
“喂,你听得到吗?”
“我听得到呀,你听得到我吗?”
“我能听到你,你能听到我吗?”
“……不想跟傻逼说话”

21、快排代码

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static void quickSort(int[] array, int low, int high) {
// 如果左边界大于右边界
if (low >= high) {
return;
}
// 定义左右边界的临时变量
int left = low, right = high;
// 保存左边界的值
int temp = array[low];
// 循环条件就是左边界小于右边界
while (left < right) {
// 从右到左边找到一个比temp小的
while (left < right && temp < array[right]) {
right--;
}
// 如果找到了的话
if (left < right) {
// 让当前的左节点等于右节点
array[left] = array[right];
// 左边界+1
left++;
}
// 从左到右知道一个比temp大的
while (right > left && temp > array[left]) {
left++;
}
// 如果找到了的话
if (left < right) {
// 让当前的右节点等于左节点
array[right] = array[left];
// 右边界-1
right--;
}
}
array[left] = temp;
// 递归左边
quickSort(array, low, left - 1);
// 递归右边
quickSort(array, left + 1, high);
}

22、Redis单线程为什么这么快

  1. Redis绝大部分请求是纯粹的内存操作
  2. Redis的数据结构比较简单,对数据的操作也比较简单
  3. 单线程可以避免多线程下的各种资源竞争、上下文切换的问题
  4. 使用多路IO复用模型
  5. Redis自己构建了VM机制
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论