Java八股文-01
1.8之后的新特性?
Lambda表达式,函数式接口,Consumer, Function, Optional, Filter, Stream
日期类 LocalDate;
接口的默认方法(default),静态方法;
并发编程类,CompletableFuture;
HashMap底层红黑树代替链表,元空间替代永久代。
多线程,线程池的管理,避免把内存撑爆;
在多线程中,每个线程做的事务不同,且线程间存在任务传递,在运维和排查时,如何更好地查看日志和定位问题?
Redis中有哪些数据结构?可以使用什么结构来实现延迟消息队列?
数据结构:String,List,Set,Hash,Bitmaps,HyperLogLog(基数统计),Geospatial(地理空间),Stream 消息队列;
延迟消息队列的核心需求是:消息在指定的延迟时间之后才能被消费。
HTTP为什么是无状态的
-
HTTP每次请求都是独立的,不需要考虑之前和之后的请求,简化服务器设计;
-
每次请求完成后就释放资源,提高并发性能;
-
极高的可伸缩性,请求之间无关联,任何一个服务器都可以处理客户端的任何请求;
-
负载均衡非常简单,请求时,服务器之间不需要传递信息;
无状态的请求如何解决登录和购物车的问题
Cookie和Session
用户第一次访问,服务器为用户创建唯一Session ID,并在服务器上开辟一块存储空间 Session;
服务器在HTTP响应中Set-Cookie头,将Session ID 发给浏览器;Set-Cookie:sessionid=abc123;Path=/;
浏览器收到响应后,保存Session ID,并在后续每次请求时,都通过Cookie头带上Session ID; 如Cookie:sessionid=abc123;
服务器通过Session ID来识别用户。
死锁发生的条件
1、互斥:每个资源只能被一个线程占用;
2、占有和等待:线程在持有至少一个资源时,等待获取其他资源;
3、不可抢占:线程无法抢占其他线程未释放的资源;
4、循环等待:多个线程形成一种头尾相接的循环等待资源关系;
打破以上任一条件,即可避免死锁;
1、打破占有和等待,线程在执行前,必须一次性申请它所需要的所有资源;
2、打破不可抢占条件,已持有资源的线程申请获取其他资源失败时,必须释放它所占有的所有资源。
- 使用带有超时机制的锁,Lock.tryLock(long time, TimeUnit unit);
- 线程在等待其他锁超时,则释放已持有的锁;回滚操作,延迟后重试。
3、打破循环等待条件,对资源进行排序,线程必须按照规定顺序申请资源,
死锁发生后,如何检测和解决?
检测
1、**JVM工具和监控工具 **
- jstack用
jstack <pid>打印 Java 进程的线程堆栈快照 - 分析堆栈信息,jstack 通常会明确地指出 “Found one Java-level deadlock”
- JConsole、JVisualVM:在运行的 JVM中直观地查看线程状态,检测死锁。
- APM 工具:如 Arthas(阿里开源)、SkyWalking、Pinpoint 等,它们提供了强大的运行时诊断和监控能力,可以辅助定位死锁。
解决
- 基于工具定位到死锁的代码位置,涉及的资源和线程
- 重启服务
- 修复代码,测试上线;
浏览器在键入网址后,整个过程是怎样的?
浏览器解析URL,根据请求信息生成对应的HTTP请求报文;
DNS解析,通过域名找IP地址,先检查本地缓存、操作系统缓存、路由器缓存,未命中时查询DNS服务器;DNS服务器递归查询:本地DNS服务器-根服务器-顶级域服务器-权威服务器,最终返回IP。
内网IP地址:10,192,172开头;
TCP三次握手
客户端Client和服务器Server
第一次:C发送 ,SYN:1; 随机序列号x;
第二次,S接收后,S发送:SYN:1;ACK:1;随机序列号y;确认号:x+1;
第三次,C接收S,C发送:序列号x+1; 确认号y+1; 状态从 SYN-RCVD 变为 ESTABLISHED
子网掩码的作用
1、用IP地址和子网掩码做按位与运算,提取网络地址,若网络地址相同,则在同一子网内,二者直接通过交换机进行通信,若网络地址不同,要通过路由器转发到外部网络。
2、子网划分,将大网络划分为多个小网络,隔离数据和访问权限。
进程和线程
进程是操作系统资源分配的基本单位,线程是CPU任务调度和执行的基本单位。
多进程比多线程要健壮,一个线程崩溃可能导致该进程崩溃,而一个进程崩溃往往不会影响其他进程。
进程中各线程所共享的内存空间:堆,方法区,代码,数据;
各线程独有的:栈(函数运行时信息),程序计数器(存放下一个指令);
MYSQL事务隔离级别
-
读未提交;(存在脏读问题)
-
读已提交;(存在不可重复读问题)
-
可重复读;(默认级别;存在幻读问题)
-
串行化(避免所有并发问题,但并发性能很差),定义:并发执行的SQL事务的操作,其效果与这些SQL事务按照某种顺序串行执行的效果相同。指每个SQL事务在下一个SQL事务开始之前完成其全部操作。
MySql的事务隔离的基础是MVCC
MVCC(Muti-Version Concurrency Control) 多版本并发控制,允许多个事务同时读写数据库,提高数据库的并发性能。
MVCC中,数据库为每个事务创建一个数据快照,当数据被修改时,不会覆盖原有记录,而是生成新版本记录。多版本指的是数据行的多个历史记录,存储在undolog中,如果事务回滚,则基于undolog日志回滚。undolog中的数据记录是链表;
为什么Mysql使用B+树的结构?
1、B+树是自平衡树,每个叶子节点到根节点的路径长度相等,插入和删除时通过分裂和合并操作来保持树的平衡,同时存在一定冗余节点,使得删除时树结构变化小,更高效;
CURD时间复杂度为O(logn)
2、树的增长不会过快,使得磁盘IO次数较少
B+树是多叉树,非叶子节点只保存主键或索引值和页面指针,使得每一页能容纳更多记录,因此内存中能存放更多索引,容易命中缓存,使得磁盘IO次数减少。
3、范围查询能力强,B+树适合范围查询,叶子节点通过链表链接,从根节点定位到叶子节点的查找范围的起点后,只需要顺序扫描链表即可遍历后续数据,非常高效。
Mysql中,B+树和B树的区别?
1、B+树只有叶子节点存储数据,B树非叶子节点也存储数据;
2、B树可在非叶子节点找到数据,查询速度较快;
3、B树范围查找较慢,需要不断中序遍历,多次磁盘IO;B+树叶子节点通过双向链表链接,范围查找较快,全表遍历较快。
4、B树非叶子节点存储数据,占用内存较大,导致B树层数较多;B+树非叶子节点只存储索引;相同数据量下,B树高瘦,B+树矮胖。
内存泄露和内存溢出
内存泄露:未清理不再使用的对象的内存,久而久之,jvm可用内存越来越少,FULL GC越来越频繁;
本该死亡的对象依然被引用:
- 静态集合类 static list; static hashMap。
- 连接未关闭;数据库、网络、IO流;
- 内部类持有外部引用;
- 缓存,用强引用的缓存容易导致内存泄露,建议使用弱引用或专门的缓存;
内存溢出:jvm堆内存设置得太小;申请非常大的对象;将大文件,过多的数据加载到内存中。
服务器内存满了怎么办?gc怎么配置的?
紧急处理:
存在负载均衡,将该节点从集群中摘掉,
输出日志,保留现场;导出堆内存Dump文件,jmap -F -dump
重启服务,回滚预案;
问题分析:
分析堆dump文件,用专业工具(Eclipse MAT, JProfiler, VisualVM)打开 .hprof 文件;重点关注内存泄露和内存溢出;
配置GC日志:-Xlog:gc*或-XX:+PrintGCDetails;
FULL GC后查看老年代空间和回收的空间大小;若老年代空间几乎不变,则是内存泄露;若回收空间太小,则是堆内存空间太小,或新生代/老年代设置比例不合理。
解决方案
内存泄露:修复代码,释放无用对象引用,检查关闭资源,使用弱引用处理缓存。
内存容量问题:
- -Xmx 增大堆内存空间;
- 优化程序,用分页查询代替一次性加载,用流式处理代替全量处理;
优化GC参数:
- 调整新生代大小,survivor区比例,减少对象过早进入老年代;
GC配置
- 设置堆内存 Xmx Xms一致,避免扩容抖动;
- 启用GC日志;
- oom时自动dump日志
-XX:MaxGCPauseMillis=150 :设置最大暂停时间150ms, 该参数是延迟和吞吐量的平衡。
设置过小,低延迟,full gc 停顿较短,但会导致full gc 频繁,吞吐量减少,因为CPU的一部分算力用来执行gc,而非执行应用线程。
设置过大,高吞吐量,full gc 频次较低,但单次full gc的停顿时间较长,导致高延迟。
-XX:InitiatingHeapOccupancyPercent=40:在堆的内存占用到40%时,开启并发线程开启GC回收周期,即并发线程去标记无用对象,掌握基本情况,然后再进行混合GC,即标记-清除&整理;
两个参数配合使用,来确定合适清扫,以及每次清扫的时间。
Java Docker中,内存爆满通常是由于容器内存限制和JVM内存配置不匹配导致的。
1、容器内存设置过低,JVM实际使用内存超出容器限制,会触发OOM kill;JVM堆内存设置过高;
解决方法:docker命令或Dockerfile,设置Java堆内存-Xms -Xmx 起始和最大值,设置为略小于容器内存限制的值。
微管平台-服务实例-打开命令行:
- 检查 /.dockerenv 文件是否存在(最经典的判断方法)
ls -la /.dockerenv
- 检查 /proc/1/cgroup 内容
cat /proc/1/cgroup
- ps aux
或top` 命令,检查进程。
# 1. 检查JVM进程
ps aux | grep java# 2. 使用jcmd查看内存概况(假设Java进程ID是1)
jcmd 1 VM.native_memory summary# 3. 获取线程堆栈
jstack 1# 4. 检查容器内内存使用
cat /sys/fs/cgroup/memory/memory.usage_in_bytes
