java面试

计算机网络

TCP的拥塞控制

  1. 拥塞

拥塞:即对资源的需求超过了可用的资源。若网络中许多资源同时供应不足,网络的性能就要明显变坏,整个网络的吞吐量随之负荷的增大而下降。

拥塞控制:防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提:网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、路由器,以及与降低网络传输性能有关的所有因素。

流量控制:指点对点通信量的控制,是端到端正的问题。流量控制所要做的就是抑制发送端发送数据的速率,以便使接收端来得及接收。

  1. 几种拥塞控制方法

    慢开始( slow-start )、拥塞避免( congestion avoidance )、快重传( fast retransmit )和快恢复( fast recovery )。

2.1 慢开始和拥塞避免

发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞。

发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。

慢开始算法:当主机开始发送数据时,如果立即所大量数据字节注入到网络,那么就有可能引起网络拥塞,因为现在并不清楚网络的负荷情况。因此,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。通常在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理。

数据库结构

如何设计一个关系型数据库?

程序实例模块

  • 存储模块:逻辑关系转化成物理关系的存储管理
  • 缓存机制:优化执行效率
  • SQL解析:进行SQL语句的解析
  • 日志管理:记录操作日志
  • 权限划分:进行多用户管理的权限划分
  • 容灾机制:灾难恢复模块
  • 索引管理:优化数据查询效率
  • 锁管理:使得数据库支持并发操作

存储模块(文件系统)

  • 磁盘或者固态硬盘存储所有数据

链接:https://www.pianshen.com/article/65811074520/

索引的数据结构

  • 生成索引,建立二叉查找树进行二分查找
    • 时间复杂度o(logn),可能变成o(n)
    • 还要考虑io
  • 生成索引,建立b-tree结构进行查找
    • 平衡多路查找树
  • 生成索引,建立b+tree结构进行查找
    • b树的变体
  • 生成索引,建立hash结构进行查找

myisam和innodb关于锁方面的区别是什么

  • myisam支持表级锁,不支持行级锁
  • innodb都支持
1
2
3
4
// 共享锁
select * from person_info_large where id = 3 lock in share mode;
// 排它锁
select * from person_info_large where id = 3 for update;

Ps:

  • innodb没有用到索引的时候,用的是表级锁

  • 用到索引的时候,用的是行锁

数据库锁的分类

  • 锁粒度划分:表级锁、行级锁、页级锁
  • 锁级别划分:共享锁、排它锁
  • 按加锁方式划分:自动锁、显式锁
  • 按操作划分:DML锁(对数据上的操作,数据的增删改查)、DDL锁(表结构,alter table)
  • 按使用方式划分:乐观锁(认为数据一般情况下,不会冲突,如果冲突则处理。实现方式:版本号、时间戳)、悲观锁(往往依赖数据库的锁机制,先取锁,再操作)

事务隔离级别以及各级别下的并发访问问题

事务并发访问引起的问题以及如何避免

  • 更新丢失–mysql所有事务隔离级别在数据库层面上均可避免
  • 脏读–read-committed事务隔离级别以上可避免
  • 不可重复读–repeatable-read事务隔离级别以上可避免
  • 幻读–serializable事务隔离级别可避免

innodb可重复读隔离级别下如何避免幻读

  • 表象:快照读(非阻塞读)–伪MVCC
  • 内在:next-key锁(行锁+gap锁)

当前读:select…lock in share mode,select…for update

当前读:update, delete,insert

rc,rr级别下的innodb的非阻塞读如何实现

  • 数据行里的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID字段
  • undo日志
  • read view

对主键索引或者唯一索引会用gap锁吗

  • 如果where条件全部命中,则不会用gap锁,只会加记录锁
  • 如果where条件部分命中或者全不命中,则用gap锁

gap锁会用在非唯一索引或者不走索引的当前读中

  • 非唯一索引
  • 不走索引

关键语法

having

  • 通常和group by 一起使用
  • Where过滤行,having过滤组
  • sql顺序:where> group by > having

Redis

  • String

String类是是二进制安全的。意思是redis的String可以包含任何数据。比如jpg图片或者序列化的对象。

String类型是Redis最基本的数据类型,一个redis中value最多可以是512M。

  • List

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部或者尾部。它的底层是一个链表。

 总结:

    它是一个字符串链表,left,right都可以插入添加

    如果键不存在,创建新的链表,如果键已经存在则新增内容

    如果内容全部移除。对应的键也消失。

    链表的操作无论是在头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡。

  • Set

Redis的Set的histring类型的无序集合。他是通过HashTable实现的。

  • Redis哈希(Hash)

Redis hash是一个键值对集合。

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

类似Java里面Map<String, Object>。

  • Redis有序集合Zset(sorted set)

Redis zset和set一样也是string类型元素的集合,而且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。

redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。

在Redis里,如何从海量key中查询出某一个固定前缀所有的key?

每次只返回一小部分的键,这样不会阻塞服务器,一下子在网络传输大量数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Autowired
private RedisTemplate redisTemplate;
public void getKey() {
long start = System.currentTimeMillis();
redisTemplate.keys("cart*");
long end = System.currentTimeMillis();
System.out.println(end - start);

// 推荐的做法
RedisConnection connection = RedisConnectionUtils.getConnection(redisTemplate.getConnectionFactory());
Cursor<byte[]> result = connection.scan(new ScanOptions.ScanOptionsBuilder().count(10).match("cart*").build());
long start1 = System.currentTimeMillis();
//cursor有id和position这两个属性,id则对应 scan cursor 的cursor的值,poisition则是当前遍历到第几个
while (result.hasNext()) {//这里可以改用for循环来获取指定数量的key
String key=new String(result.next());
//对key的操作,或者先放到一个集合里面,然后再进行后续操作
}
long end1 = System.currentTimeMillis();
System.out.println(end1 - start1);
}

Redis持久化方式

方式

Redis支持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数 据恢复。理解掌握持久化机制对于Redis运维非常重要

  1. RDB持久化
    RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发

1)触发机制

手动触发分别对应save和bgsave命令

·save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存 比较大的实例会造成长时间阻塞,线上环境不建议使用

·bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子 进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短

2)自动触发RDB的持久

1)使用save相关配置,如“save m n”。表示m秒内数据集存在n次修改 时,自动触发bgsave。

2)如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点,更多细节见6.3节介绍的复制原理。

3)执行debug reload命令重新加载Redis时,也会自动触发save操作。

4)默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则 自动执行bgsave。

RDB的优缺点

RDB的优点:

  • RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据 快照。非常适用于备份,全量复制等场景。比如每6小时执行bgsave备份, 并把RDB文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复。

  • Redis加载RDB恢复数据远远快于AOF的方式。

RDB的缺点:

  • RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运 行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。

  • RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式 的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。

  • 针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决。

AOF持久化

AOF(append only file)持久化:以独立日志的方式记录每次写命令, 重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用 是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式

1)使用AOF
开启AOF功能需要设置配置:appendonly yes,默认不开启。AOF文件名 通过appendfilename配置设置,默认文件名是appendonly.aof。保存路径同 RDB持久化方式一致,通过dir配置指定。AOF的工作流程操作:命令写入 (append)、文件同步(sync)、文件重写(rewrite)、重启加载 (load)。

AOF过程:

1)所有的写入命令会追加到aof_buf(缓冲区)中。

2)AOF缓冲区根据对应的策略向硬盘做同步操作。

AOF为什么把命令追加到aof_buf中?Redis使用单线程响应命令,如 果每次写AOF文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负 载。先写入缓冲区aof_buf中,还有另一个好处,Redis可以提供多种缓冲区同步硬盘的策略,在性能和安全性方面做出平衡

3)随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。

重写后的AOF文件为什么可以变小?有如下原因:

1)进程内已经超时的数据不再写入文件。

2)旧的AOF文件含有无效命令,如del key1、hdel key2、srem keys、set a111、set a222等。重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。

3)多条写命令可以合并为一个,如:lpush list a、lpush list b、lpush list c可以转化为:lpush list a b c。

AOF重写降低了文件占用空间,除此之外,另一个目的是:更小的AOF 文件可以更快地被Redis加载。

AOF重写过程可以手动触发和自动触发

·手动触发:直接调用bgrewriteaof命令。

·自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机

加载AOF文件进行数据恢复

流程说明:

1)AOF持久化开启且存在AOF文件时,优先加载AOF文件,打印如下日志:

* DB loaded from append only file: 5.841 seconds

2)AOF关闭或者AOF文件不存在时,加载RDB文件,打印如下日志:

* DB loaded from disk: 5.586 seconds

3)加载AOF/RDB文件成功后,Redis启动成功。

4)AOF/RDB文件存在错误时,Redis启动失败并打印错误信息。

Redis管道技术(pipeline)

  作用:执行命令简单的,更加快速的发送给服务器;
   一个client可以通过一个socket连接发起多个请求命令。每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client。如果没有pipeline那么redis就会处理完一个请求之后返回响应报文,client再发送下一个请求。

Redis的同步机制

全同步过程

  • slave发送sync命令给master;
  • master启动一个后台进程,将Redis中的数据快照保存到文件中;
  • master将保存数据快照数据期间接收到的命令缓存起来;
  • master完成文件写操作后,将文件发送给slave;
  • 使用新的AOF文件替换掉旧的AOF文件;
  • master将这期间收集的增量写命令发送给slave端。

注:全量同步操作完成后,后续所有写操作都是在master上进行,slave上进行读操作,虽然master也能进行读操作,但一般不会使用;为了提升性能,一般都将读操作放在slave上,因此用户的写操作需要及时扩散到slave上,以保证数据最大程度上的同步。

增量同步过程

  • master收到用户的操作指令,判断是否需要传播到slave;(一般增删改才需要)
  • 将操作记录追加到AOF文件;
  • 将操作传播到其他slave:a)对齐zhu主从库;b)往响应缓存写入指令 ;
  • 将缓存中的数据发给slave。

Redis sentinel:解决主从同步master宕机后的主从切换问题

监控:检查主从服务器是否运行正常;

提醒:通过API向管理员或者其他应用程序发送故障通知;

自动故障迁移:主从切换;

Redis 的集群原理

如何从海量的数据中尽快找到所需?

  分片:按照某种规则去划分数据,分散储存在多个节点上;
  常规的按照哈希划分无法实现节点的动态增减

一致性哈希算法

  对2^32取模,将哈希值空间组织成虚拟的圆环

将数据key使用相同的函数hash计算出哈希值

Linux知识考点

Linux的体系结构

  • 体系结构主要分为用户态(用户上层活动)和内核态
  • 内核:本质是一段管理计算机硬件设备的程序
  • 系统调用:内核的访问接口,是一种能再简化的操作
  • 公用函数库:系统调用的组合拳
  • Shell:命令解释器,可编程

查找特定文件

1
2
3
4
5
语法 find path [options] params		
// 精确查找
find ~ -name "target.java"
// 模糊查找, 并且忽略大小写
find ~ -iname "target*"

检索文件内容

grep命令

grep [pattern] [file]

1
grep "mooc" target*

管道操作符 |

1
find ~ | grep "target"
  • 前一个命令必须正确
  • 右边必须能够接受标准输入流,否则数据会被抛弃
  • Sed,awk,grep,cut,head,top,less,more,wc,join,sort,split等
1
2
3
4
查看包含某个字段--> 筛选出符合正则表达式的内容
grep "pattern\[true\]" test.log | grep -o "engine\[[0-9a-z]*\]"
过滤掉包含字符串的内容
grep -v "grep"

对文件内容进行统计

awk命令

适合对列处理

1
语法 awk [options] 'cmd' file
  1. 一次读取一行文本,按输入分隔符进行切片,切成多个组成部分
  2. 将切片直接保存在内建的变量中,$1,$2…($0表示行的全部)
  3. 支持对单个切片的判断,支持循环判断,默认分隔符为空格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
文件说明:
netstat.txt
文本内容为格式化数据:tcp 0 48 ip:ssh
命令:
// 第一列 和 第四列输出
awk '{print $1,$4}' netstat.txt
// 判断
awk '$1=="tcp" && $2==1{print $0}' netstat.txt
// 表头
awk '($1=="tcp" && $2==1) || NR==1 {print $0}' netstat.txt

文件说明:
test.txt
sdfsd,123
dsfsdf,456
// 第2列
awk -F "," 'print{ $2}' test.txt
// 统计
grep "pattern\[true\]" test.log | grep -o "engine\[[0-9a-z]*\]" | awk 'enginearr[$1++]END{for(i in enginearr)print i "\t" enginearr[i]}'

批量替换文件中的内容

sed命令

适合对行处理

1
语法:sed [option] 'sed command' filename
  1. 全名stream editor,流编辑器
  2. 适合用于对文本的行内容进行处理
1
2
3
4
5
6
7
8
9
sed 's/^Str/String/' replace.java
// 会修改文件
sed -i 's/^Str/String/' replace.java
// $代表以什么结尾
sed -i 's/\.$/\;/' replace.java
// 全局替换加g(默认是替换第一个)
sed -i 's/Jack/me/g' replace.java
// 删除加d 删除空行
sed -i '/^ *$/d' replace.java

Java底层知识:JVM

谈谈对java的理解

  • 平台无关性
  • GC
  • 语言特性(泛型、反射、lambda表示)
  • 面向对象(封装、继承、多态)
  • 类库(集合库,并发库,网络库,io)
  • 异常处理

Compile Once,Run anyway如何实现

jvm如何加载.class文件

  • ClassLoader: 依据特定格式,加载class文件到内存;
  • Execution Engine: 对命令进行解析;
  • Native Interface: 融合不同开发语言的原生库为java所用;
  • Runtime data area: Jim内存空间结构模型。

反射

java反射机制在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能为java语言的反射机制。

类从编译到执行的过程

  • 编译器将robot.java源文件编译为robot.class字节码文件
  • ClassLoader将字节码转换为JVM中的Class<Robot>对象
  • JVM利用Class<Robot>对象实例化robot对象

ClassLoader

ClassLoader在java中有着非常重要的作用,它主要工作在Class装载的加载阶段,其主要作用是从系统外部Class二进制数据流。它是java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件中的二进制数据流装载进系统,然后交给JVM进行连接、初始化等操作。

  • BootstrapClassLoader:c++编写,加载核心库java.*
  • ExtClassLoader:java编写,加载扩展库java.ext.*
  • AppClassLoader:java编写,加载程序所在目录
  • 自定义ClassLoader:java编写,定制化加载

自定义ClassLoader

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
41
public class MyClassLoader extends ClassLoader {
private String path;
private String classloaderName;

public MyClassLoader(String path,String classloaderName){
this.path = path;
this.classloaderName = classloaderName;
}

// 用于寻找类文件
@Override
public Class findClass(String name){
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}

// 用于加载类文件
private byte[] loadClassData(String name){
name = path + name + ".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try{
in = new FileInputStream(new File(name));
out = new ByteArrayOutputStream();
int i = 0;
while((i = in.read()) != -1) {
out.write(i);
}
} catch (Exception e){
e.printStackTrace();
} finally {
try {
out.close();
in.close();
} catch (Exception e){
e.printStackTrace();
}
}
return out.toByteArray();
}
}

为什么使用双亲委派机制去加载类

避免多份同样字节码加载

loadClass和forName有什么区别

类加载方式

  • 隐式加载:new
  • 显示加载:loadClass, forName等

类加载过程

加载–通过ClassLoader加载class文件字节码,生成class对象

链接

  • 校验:检查加载的class的正确性与安全性
  • 准备:为类变量分配存储空间并设置类变量初始值
  • 解析:JVM将常量池内的符号引用转换为直接引用

初始化–执行类变量赋值和静态代码块

区别

  • Class.forName得到的class是已经初始化完成的
  • ClassLoader.loadClass得到的class是没有链接的

java内存模型

线程私有:程序计数器、虚拟机栈、本地方法栈

线程共享:MetaSpace、java堆

程序计数器:存储下一条需要执行的字节码指令,地址

虚拟机栈:

  • java方法执行的内存模型
  • 包含多个栈帧:局部变量表、操作栈、动态连接、返回地址
    • 局部变量表:方法执行过程中的所有变量
    • 操作栈:出栈、入栈

递归为什么会引起stack overflow异常

  • 递归过深,栈帧数超出虚拟机栈深度

虚拟机栈过多会引发java.lang.OutOfMemoryError异常

元空间和永久代的区别

  • 元空间使用本地内存,而永久代使用的是jvm内存
  • 元空间没有字符串常量池

java堆

  • 对象实例的分配区域
  • gc管理的主要区域

JVM三大性能调优参数 -xms -xmx -xss含义

  • -xss: 规定了每个线程虚拟栈(堆栈)的大小
  • -xms:堆的初始值
  • -xmx:堆能达到的最大值

java内存模型中的堆和栈的区别–内存分配策略

  • 静态存储:编译时确定每个数据目标在运行时的存储空间需求
  • 栈式存储:数据区需求在编译时未知,运行时模块入口前确定
  • 堆式存储:编译时或运行时模块入口都无法确定,动态分配

java内存模型中堆和栈的区别

  • 联系:引用对象、数组时,栈里定义变量保存堆中目标的首地址

  • 管理方式:栈自动释放,堆需要gc

  • 空间大小:堆比栈小

  • 碎片相关:堆产生的碎片远小于堆

  • 分配方式:栈支持静态和动态分配,而堆仅支持动态分配

  • 效率:栈的效率比堆高

不同jdk版本之间的intern()方法的区别-jdk6 vs jdk6+

Java底层知识:GC相关

判定对象是否为垃圾的算法

  • 引用计数法(缺点:无法检测循环引用的情况,导致内存泄漏)
  • 可达性分析法

可以作为gc root的对象

  • 虚拟机栈中引用的对象(栈帧中的本地变量表)
  • 方法区中的常量引用的对象
  • 方法区中的类静态属性引用的对象
  • 本地方法栈中JNI(Native 方法)的引用对象
  • 活跃线程的引用对象

垃圾回收算法

标记-清除

  • 标记:从根集合进行扫描,对存活的对象进行标记
  • 清除:对堆内存从头到尾进行线性遍历,回收不可达对象内存

特点:

  • 碎片化

复制

  • 分为对象面和空闲面
  • 对象在对象面上创建
  • 存活的对象被从对象面复制到空闲面
  • 将对象面所有对象内存清除

特点:

  • 解决碎片化
  • 顺序分配内存,简单高效
  • 适用于对象存活率低的场景

标记整理

  • 标记:从根集合进行扫描,对存活的对象进行标记
  • 清除:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。

gc的分类

  • minor gc(复制)
  • full gc

分代收集算法

对象如何晋升到老年代
  • 经历一定minor次数依然存活的对象
  • Survivor区中存放不下的对象
  • 新生成的大对象(-xx:+PretenuerSizeThreshold)
常用调优参数

触发full gc的条件

stop-the-world

  • JVM由于要执行gc而停止了应用程序的执行;
  • 任何一种gc算法中都会发生
  • 多数gc优化通过减少stop-the-world发生的时间来提高性能

safepoint

  • 分析过程中对象引用关系不会发生变化的点
  • 产生safepoint的地方:方法调用;循环跳转;异常跳转等
  • 安全点数量得适中

jvm的运行模式

  • client(启动较快,轻量级)
  • Server(启动较慢,重量级虚拟机,做了很多优化,运行稳定后,性能更高)

年轻代收集器

serial收集器(-xx:UseSerialGC,复制算法)

parnew收集器(-xx:+UseParNewGC,复制算法)

parallel scavenge收集器(-xx:+UseParallelGC,复制算法)

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)

老年代

serial old收集器(-xx:+UseSerialOldGC,标记整理算法)

parallel old收集器(-xx:+UseParrallelOldGC,标记整理算法)

cms收集器(-xx:+UseConcMarkSweepGC,标记-清除算法)

  • 初始标记:stop-the-world
  • 并发标记:并发追溯标记,程序不会停顿
  • 并发预清理:查找执行并发标记阶段从年轻代晋升到老年代的对象
  • 重新标记:暂停虚拟机,扫描cms堆中剩余对象
  • 并发清理:清理垃圾对象,程序不会停顿
  • 并发重置:重置cms收集器的数据结构

g1收集器(-xx:+UseG1GC,复制+标记-整理算法)年轻代+老年代

Object的finalize()方法的作用是否与c++的析构函数作用相同

  • 与c++的析构函数不同,析构函数调用确定,而它的是不确定的
  • 将未被引用的对象放置于f-queue队列
  • 方法执行随时可能会被终止
  • 给予对象最后一次重生的机会

java中的强引用、软引用、弱引用、虚引用有什么用

引用队列

  • 无实际存储结构,存储逻辑依赖于内部节点之间的关系来表达
  • 存储关联的且被gc的软引用,弱引用以及虚引用

Java多线程与并发

链接:https://blog.csdn.net/qq_37128049/article/details/90080074

Java多线程与并发-原理

链接:https://blog.csdn.net/qq_37128049/article/details/90080074

reentrantlock的condition原理

https://blog.csdn.net/zxd8080666/article/details/83214089

java常用类库与技巧

链接:https://blog.csdn.net/qq_37128049/article/details/90097438

Error和Exception的区别

Error: 程序无法处理的系统错误,编译器不做检查。

Exception: 程序可以处理的异常,捕获后可能恢复。

总结:前者是程序无法处理的错误,后者是可以处理的异常。

RuntimeException: 不可预知的,应当自行避免。空指针、数据越界

非RuntimeException: 可预知,从编译器校验的异常。

Hashmap:put方法的逻辑

hash算法

ConcurrentHashmap:别的需要注意的点

  • size()方法和mapping count()方法的异同,两者计算是否准确?
  • 多线程环境下如何进行扩容?

Java框架-Spring

链接:https://blog.csdn.net/qq_37128049/article/details/90140899