参考 https://javaguide.cn/home.html

操作系统:

什么是系统调用?

用户态程序请求内核态程序的服务, 用户态请求调用系统资源时必须通过系统调用

进程和线程的区别?

进程: 正在运行的程序的实例

线程: 进程中的并发运行处理的各种功能

注意资源、通信、切换开销等;

进程的生命周期?

创建态 ==> 就绪态 ==> 运行态 ==> 阻塞态 ==> 终止态

进程间通信方式及各自的优缺点?

  • 共享内存通信: 通过系统调用开辟一块共享内存可以两个进程共享访问, 很低级的通信方式, 没有办法进行复杂的通信, 需要考虑进程安全问题和互斥访问问题.

共享内存的优点: 方便, 简单, 高效, 快捷.

  • 消息队列: 相较管道通信的固定内存, 消息队列采用链表的形式更加灵活, 进程传递消息只需加入消息队列就可以返回, 效率更高, 但是每次传递的消息大小也有限制, 没办法实现很复杂的通信
  • 管道通信: 内存分配有限, 并且必须依次读取, 通信进程对管道的读写访问互斥导致效率降低, 单向通讯(可以半双工管道), 由于固定内存的分配, 通信的数据无格式, 也无法完成复杂的通信
  • 邮箱通信: 向操作系统申请一个内存区作为信箱代理, 允许多个进程向一个信箱发消息, 双向通信需要两个进程两个信箱.
  • 本地套接字协议, 与网络协议通信几乎一致, 在两个进程之间通过套接字协议完成复杂消息的通信.

线程间同步的方式?

通过信号量完成线程间的同步, 将必须先完成的线程作为一种临界资源, 只有线程完成释放信号量才成允许后续的线程继续执行.

管道、消息传递相对共享内存的优势 mmap()

共享内存只能完成一问一答的通信, 并且不安全

管道可以进行多问多答的消息传递, 进程可以传递多个消息, 两个进程之间进行消息流通信

消息传递相较于管道, 克服了缓冲区大小受限, 只能传递无格式字节流消息的缺点, 可以传递简单的有消息格式的信息。

管道等通信方式数据是安全的

线程越多程序处理能力一定越强吗?

不一定, 线程越多, 进程的并发处理能力越强, 但是线程过多会挤压其他进程的处理能力

主要考虑切换开销(cpu是有限的), 还有为保证数据安全的同步开销;

同步和互斥的区别

同步是线程之间特定指令之间的前后顺序问题, 是进程间时间上的竞争关系, 互斥是线程对共享资源的竞争关系

死锁,死锁的必要条件,如何解决死锁?

死锁: 单车道辆车相向相遇, 就是多个线程, 访问同一个有限的共享资源, 在各自占有一部分资源的情况下, 循环等待其他线程的资源的情况

解决死锁好像目前系统都是等待加无差别终止策略, 重点应该都放在避免上

线程崩溃了,进程也会崩溃吗?

线程崩溃不一定会导致进程的崩溃, 线程的崩溃只是进程的某部分服务的崩溃.

C语言会崩溃,jvm下不会,可以看看发给你的操作系统博客有相关解析;

内存管理有哪几种方式?

内存管理有块式管理,页式管理,段式和段页式管理。 现在常用段页式管理

快表和多级页表分别解决了页表管理中的什么问题?

快表: 常用内存的访问速度问题

多级页表: 进程过多导致的页表过于冗长的问题, 并且多级块表切成小块后可以放到缓存提高速度;

分页机制和分段机制的共同点和区别?

分页和分段都是对进程的内存进行分割成小块来管理

分页是固定将内存分割成同大小的小块, 避免出现外部碎片的问题, 但是会出现内部碎片的问题.

分页是根据内存的各个属性(栈段, 主程序, 子程序, 数据段) 进行连续分割, 不会出现内部碎片但是会出现外部碎片

解释一下逻辑地址和物理地址

逻辑地址是程序编程时使用的虚拟内存的地址, 物理地址是程序实际运行时在内存中的实际地址.

为什么需要虚拟地址空间?

对于用户来说, 便于编程.

对于操作系统来说, 虚拟地址可以扩展物理地址, 隔离进程, 提高安全性, 简化内存管理

什么是虚拟内存?

虚拟内存就是编程时程序的逻辑物理内存, 为了拓展内存和简化内存管理的虚拟内存空间

什么是局部性原理?swap

时间局部性: 由于程序中大量的循环和重复, 对某个页的访问会在时间上连续多次执行.

空间局部性: 由于程序中的数据和指令是连续存放的, 访问某个内存页的时候往往会顺带访问到相邻的页

页面置换算法的作用?常见的页面置换算法

作用: 进行高效的内存管理

常见: FIFO LRU/LFU NRU

CPU处理流水线技术,流水线技术存在什么问题

就是 CPU 的不同电路单元组成一条指令处理流水线, 然后让将指令分割成不同的执行阶段, 交给不同的单元依次处理, 可以实现在同一时钟周期内同步执行多条指令的效果, 提高CPU主频.

问题: 多个指令之间的依赖问题, 各个单元的同步执行问题, 并且CPU功耗更高


计算机网路:

TCP五层模型,各层主要作用

应用层: 用户程序之间进行消息通信, 主要是用户程序消息的处理

传输层(TCP层): 保证消息通信的质量, 决定消息传输的方式

网络链路层(IP层): 通过网络路由算法决定消息在网络中经过的路径和到目标主机的位置

数据链路层: 将消息转换为物理层链路传输的信息格式(比如比特流), 并进行消息压缩, 校验, 纠错的工作

物理层: 将数据链路层信息转化为物理信息在物理链路中传输

ARP工作过程

首先检查自己的路由缓存中有没有相应 IP 的 MAC 信息, 没有则对其他主机进行广播, 询问其他主机有没有相应IP的MAC信息, 其他主机找到相关信息后, 更新自己的路由信息并将信息返回给询问的主机, 接收到消息的主机也会更新自己的路由缓存并进行通信.

TCP三次握手过程

发启方发送一个 SYN 给接收方, 接收方接收到以后返回一个 ACK 和 SYN 给发起方, 发起方接收到之后再返回一个 ACK 后, 连接正式建立

TCP四次挥手过程

提出断开连接的客户端发送一个 FIN 给服务端, 服务端收到后返回一个 ACK 给客户端, 然后客户端进入只接受消息的状态, 让服务端把剩余的消息传完, 服务端传完消息后发送一个 FIN 给客户端, 这时客户端接受到 FIN 后发送一个 ACK 给服务端并进入 Time-wait 状态, Time-wait 状态结束后四次挥手完成, TCP 断开连接

TCP协议借助了哪些手段保证可靠传输

基于数据块传输: 将数据重新划分成合适TCP传输的数据块大小

校验和: 检验数据的准确性

重传机制: 解决网络问题导致的信息丢失

流量控制(滑动窗口): 适配接收方的处理效率, 保证通信的质量, 并且可以解决数据包乱序的问题

拥塞控制: 适配网络的处理能力, 保证通信的效率

滑动窗口和拥塞控制工作过程

滑动窗口: 接收端将自己可以接收的缓冲区大小放入TCP首部中的“窗口大小”字段,通过ACK来通知发送端, 发送端根据窗口大小调整自己发送消息的速率

拥塞控制: 接收端根据网络状况调整自己的发送速率, 尽可能利用网络带宽但不对网络造成太大负担. 发送端通过拥塞窗口实现拥塞控制, 流程为 慢启动 --> 拥塞避免(线性增长) --> 触发重传时尝试解决拥塞. 解决拥塞分为两种: 超时重传触发时, 将门限降低一半, 并且重新慢启动; 快速重传触发时, 门限降低一半, 进行快恢复.

TCP和UDP的区别,使用场景

TCP 是面向连接的, 保证传输质量的协议, 常用于保障连接质量的场景, 比如 HTTP

UDP 是无连接的协议, 不保证传输的稳定性和质量, 但是发包大小小于 TCP , 速度显著比 TCP 快, 适用于网络多人在线的场景, 比如网络直播, 网络游戏

DNS域名系统工作过程

主机将域名 host 发送给 DNS , DNS 解析域名后再将域名的 IP 地址返回给主机, 主机根据 IP 地址与域名对应的主机通信

TCP的超时和重传

超时重传: 设定一个超时门限(两倍MSL), 超过后便触发重传机制, 将没有收到 ACK 的报文重新传输

TCP的保活定时器,短连接和长连接,心跳

为了避免 TCP 长时间连接但不传递消息给服务器带来的维护 TCP 的负担, 设定一个保活计时器, 从建立连接和上一次通信为基准, 超过设定的时间门限就释放主动断开 TCP 连接, 减少服务器负担.

短连接: TCP 只再发送信息时建立连接, 发完就断开连接, 避免给服务器带来维护TCP连接的负担, 适用于非频繁通信和短时间大量用户短时间内发送简单消息的场景. 但是对服务器 CPU 处理 TCP 的性能有要求, CPU 性能消耗大

长连接: TCP 建立连接后哪怕不发消息也保持连接不断开, 保持连接, 长连接占用大量资源.

心跳通信是为了避免自己被服务器的 TCP 保活计时器关闭连接, 心跳就是发送端固定时间发送一个非常简单短小的 TCP 报文刷新计时器的机制

TCP如何保证包的顺序性的

通过发送端和接收端各自的 SEQ 和 时间戳机制, 标记包的序号,

滑动窗口协议, 发送方通过滑动窗口指针顺序发包, 接收方通过滑动窗口指针控制接收到了顺序的数据包才交付给应用层

如何解决零窗口恢复问题

两种方式: 一种是客户端定时询问服务端是否有新窗口可以发送数据, 另一种是服务端处理完之后通知客户端有剩余窗口可以发送数据(为避免丢包导致的死锁等待, 通过设定计时器来进行超时重传)

有疑问, TCP 丢包不是会触发重传机制吗, 为什么会有零窗口恢复时的丢包等待死锁问题

Nagle算法

核心思想: 最多只能有一个未被确认的小分组, 不要一有数据就发送,我们要尽量的积攒一些数据然后发送

算法逻辑:

1、如果没有未确认的包,立马发送当前包。

2、否则累积缓冲区的数据,直到没有未确认的包或者累积的数据达到 MSS 的大小,立马发送数据

会带来数据发送的时延增大, (缓冲区积累导致的)

快重传和快恢复

快速重传: 前提是发送端采用连续发送的方式, 而不是等 ACK 才发送下一个, 当连续三次收到相同的 ACK 后触发重传

快速恢复: 当触发快速重传时, 认定网络发送拥塞, 但是网络还没特别糟糕, 此时拥塞门限和拥塞窗口减半, 进行快恢复: 拥塞窗口+3 保证原来的包能尽早传到接收端, 然后拥塞窗口大小直接从拥塞门限开始增长, 而不是从 1 开始慢启动.


JAVA基础:

静态成员变量、静态代码块、代码块、构造函数加载顺序

(类被加载时) --> 静态成员变量 --> 静态代码块 --> (当类的实例被new的时候) --> 构造函数 --> 代码块

JDK和JRE有什么区别

JDK 中包含 JRE, JRE(Java Runtime Environment) 是 java 运行环境, 提供 java 程序的必要的运行环境和工具库, 主要包括 JVM 和 java 基础库, JDK(Java Development Kit) 是提供给开发者的功能齐全的 java SDK. 用于创建, 开发 java 程序和调试, 其中包括 javac 编译器, JRE Java运行环境, 调试工具,

为什么浮点数运算的时候会有精度丢失的风险?如何解决精度丢失的问题 bigdecimal = > 字符串=》

计算机在处理浮点数时使用的是二进制, 而不是十进制. 在二进制中,一些小数无法精确表示(0.1),所以在进行计算时可能会发生舍入误差,导致精度丢失。

解决方案:

大型浮点数: 使用更长字节更高精度的浮点数, 只要精度足够高, 就可以在实际工作中忽略微小的精度丢失

字符串模拟浮点运算: 采用字符算模拟十进制数来进行浮点运算, 极大降低浮点运算的效率, 并且增加大量的内存开销

方法重载和重写的区别

重载: 方法名相同, 方法传入参数不同, 可以重载父类的方法.

重写: @override 注释进行重写, 对继承的父类的方法进行从写, 但是不能改变传入参数, 重写可以提高方法的复用性.

访问修饰符public,private,protected,以及不写(默认)时的区别?

public: 谁都可以访问;

private: 只有自己类的内部方法可以访问, 其他所有实例和类禁止访问;

protected: 自己同包内的可以访问, 自己的子类继承之后可以通过子类实例访问.

default: 只有同包的可以访问

java参数传递是值传递还是引用传递

基本类型值传递, 引用类型是引用传递

float f=3.4;是否正确?

不正确, 3.4 默认 double 类型, 转换会导致精度问题, 应该 float f = 3.4f;

Java多态是什么,有什么意义;

多态概括就是用父类实现子类, 可以提高方法的复用性

构造器(constructor)是否可被重写(override)?

不能

抽象类(abstract class)和接口(interface)有什么异同?

接口比抽象类更抽象, 抽象类里面可以有抽象方法和实例方法, 还可以有成员变量. 接口里面只有函数声明, 必须全部重写.

从工程化含义来说,==抽象类是模版化实例做复用,接口是定义标准化流程==

Java 中会存在内存泄漏吗,请简单描述

会, 当用户编程时自己忘记释放一些连接资源时, 比如 File Connection 没有使用 close() 方法关闭, 但又一直不使用, 或者一些死循环重复申请缓存, 就会导致内存泄露.

是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?

不可以, 静态是类的, 非静态是实例的, 一个类有多个实例, 无法访问调用

如何实现对象克隆?(浅拷贝,深拷贝)

浅拷贝: 地址拷贝, 多个变量指向同一个地址; 深拷贝: 值拷贝, 多个变量在不同地址的内存中但是值相等.

String s = new String(“xyz”);创建了几个字符串对象?

有疑问: 两个吗? 在常量池创一个 "xyz" 对象, 然后新建对象是对 "xyz" 拷贝

Java 中的final关键字有哪些用法?

final static 表示常量, 被赋值一次不能被更改; final 修饰方法表示该方法不能被继承; final 修饰类表示类不能被继承

finally 中的代码一定会执行吗?

爆 ERROR , JVM 崩溃, 线程死循环的时候不会被执行

equals和”==”区别,什么时候需要hashcode

一个是运算符,一个是方法。 ==操作符专门用来比较变量的值是否相同,引用类型对象变量其实是一个引用,它们的值是指向对象所在的内存地址。 equals方法常用来比较对象的内容是否相同,equals()方法存在于Object类中。

hashCode()被设计是用来使得哈希容器能高效的工作, 比如 hashmap 和 hashtable 这些容器会先采用 hashcode 进行弱比较提高效率.

这也意味着, 假如两个类的 equals 方法相同, 那么必须要保证两个类的 hashcode 也必须相等, 不然在哈希容器中会出现各种问题.

单例模式中懒汉和饿汉模式的优缺点

单例模式: 每个类只有一个实例, 这样保证一些方法类的实例不会被重复创建.

饿汉模式: 类被加载时自动创建一个实例; 懒汉模式: 类加载时不会创建实例, 但是类被使用的时候就会创建一个实例

字符串常量池的作用

保存类的静态字符串常量, 保存被多个对象引用的字符串实例

抽象类和接口的区别?抽象类设计的意义

(前面答过了), 抽象类设计的意义: 模板模式和组合模式, 抽象类提供一个模板来提高代码复用的效率, 接口将功能作为接口要求类来实现, 实现功能和类的解耦, 规范类的行为, 提高类的扩展性.

匿名内部类的意义

简单来说, 不为一些只在内部生效的类生成一个 Class 文件占文件资源

匿名类可提高代码的复用性和可扩展能力

ArrayList和LinkedList区别,各自的优缺点

ArrayList 的底层是数组, 可以支持 O(1) 的随机访问, 但是增删的开销很大 O(n) , LinkedList 的底层实现是双向链表, 随机访问需要的时间开销为 O(n) 但是增删的开销小 O(1)

Map内部结构实现原理key value HotingRing

Map 内部为一个 hash 数组加上链表或红黑树, 实现原理: 将 key 传入 hashcode() 内得到一个 hash 值确定键值对在哈希数组的位置, 当多个 key 发生哈希碰撞时, 将多个键值对连成链表或者连连成红黑树

说一说 PriorityQueue

优先队列, 入队后元素按优先级排列, 优先级高的先出, 优先级低的后出, 元素通过实现 Comparable 接口判断优先级

java的异常体系结构,受检异常是什么含义

异常体系结构: 通过 Throwable 实现 Error 和 Exception, 然后 Error 子类有 OOM, IOError, Exception 的子类有 RunTimeException 和其他受检异常等等.

受检异常((checkedException), 是指需要显示通过 Catch 捕获的异常. 在Java中, 除了RuntimeException以外的异常, 都属于受检异常.

Error和Exception有什么区别?

出现 Error 不是 JVM 能处理的, 只要出现程序就会崩溃终止, Exception 是程序可以在内部处理的, 有时也需要通过异常完成一些业务逻辑

try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行

一定会执行的

TreeMap和TreeSet在排序时如何比较元素?

看元素是否实现了 comparable 方法

泛型上界运算符和下届运算符是什么含义?修改和查询分别有什么限制

上届:用 extends 关键字声明, 表示参数化的类型可能是所指定的类型, 或者是此类型的子类。

下界: 用 super 进行声明, 表示参数化的类型可能是所指定的类型, 或者是此类型的父类型, 直至 Object

反射的含义及实现原理,反射为什么慢

对于任何一个类,在"运行的时候"都可以直接得到这个类全部成分, 得到一个类的全部成分然后操作,破坏封装性,也可以破坏泛型的约束性。

因为反射只能在运行时进行动态解析, 解析 Class 字节码文件需要时间, 并且 JVM 也没办法对反射的动态解析进行代码优化

StringBuilder、StringBuffer、String区别

StringBuilder、StringBuffer 是可变字符串, String 是不可变字符串. StringBuilder 线程不安全, StringBuffer 线程安全

HashMap源码分析

https://javaguide.cn/java/collection/hashmap-source-code.html#%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95

包装类的缓存机制

就是 JVM 在启动时将一些常用的包装类(Integer, Long, Byte, ...)加载在内存里, 当需要使用时直接引用就行, 避免创建新对象, 减少内存开销

try-with-resources的含义

try 语句后面的括号中的资源仅在 try 代码块中需要, 只要 try 代码块结束(正常结束或者抛出异常), 一定会进行 .close() 方法关闭资源. 主要是为了降低开发人员进行资源管理的负担

HashMap 的长度为什么是 2 的幂次方

因为哈希值是 int 类型底层是二进制序列, 当插入 hashmap 的时候需要进行取模运算, 当 hashmap 是长度的 2 次幂的时候, 取模运算可以简化为位运算, 提高 hashmap 的插入效率

HashMap元素碰撞较多时为什么不用B+树,而是红黑树

1、B+ 树的算法实现复杂, 并且 Java 已经实现了红黑树了;

2、修改效率较低, 红黑树修改节点只需要更改左右父节点, 但是 B+ 树是块状储存的, 修改节点需要大量移动元素, 开销过大

HashMap中多线程导致的死循环问题 https://coolshell.cn/articles/9606.html

多线程扩容hash头插入导致的链表循环问题, 因为 hashmap 在多值哈希碰撞时会通过链表实现, 插入时采用头插入法, 在多线程扩容时, 两个线程同时保留了父子节点的信息, 但是第一个线程在更新扩容后的新哈希表的链表时, 头插入导致链表的顺序和原来的链表相反, 此时另一个线程保留的原来父子节点信息和目前的节点的顺序相反, 再次进行头插入时便会成环。

以上问题在 Java 1.8 解决,并且为了线程安全考虑,建议使用 CurrentHashMap

软引用和弱引用分别有什么用

软引用: 软引用对象被 GC 发现了不会立刻主动标记回收, 但是一旦空间不足就会回收, 可以用于对缓存敏感的高速缓存应用中, 比如浏览器的网页回退

弱引用: 弱引用的对象只要被 GC 发现了就会立刻回收

String里面的字符数组设置成final的原因

保证 String 的字符数据只能被赋值一次, 保证 String 的不变性

switch(String str) 实现原理

本质是对 int 类型的 switch , 对传入的 String 进行 hashcode() 方法得到一个 int 的哈希值进行匹配, 当哈希值匹配后, 再继续调用 equals 方法进行确认.