1. 金三银四跳槽季
2. 两年Java的面试总结
Java基础
- HashMap的源码,实现原理,JDK8中对HashMap做了怎样的优化。
jdk1.6 1.7,HashMap 采用位桶+链表来实现的,链表用来处理hash冲突,
参考
- HaspMap扩容是怎样扩容的,为什么都是2的N次幂的大小。
- HashMap,HashTable,ConcurrentHashMap的区别。
HasTable 是jdk1 遗留下来的类初始值是11扩容newsize =oldsize*2+1,而HashMap是后来增加的初始容量是16;
HashTable 是线程安全的类,操作效率低,而HashMap是线程不安全的,操作效率高;
HashTable 不允许null键null值,HashMap 允许null键null值
concurrentHashMap底层采用的是数组加链表线程安全,
java 中数据存储方式分为两种:一种是数组,另外一种是链表,前者的特点内存是连续的空间,寻址快,但是增加删除的时候会有较大幅度的移动,所以数组的特点是查询快增删慢;而链表由于内存地址是不连续的,寻址困难,增删元素只修改指针,所以链表的贴的,查询慢,增删快
参考
- 极高并发下HashTable和ConcurrentHashMap哪个性能更好,为什么,如何实现的。
ConcurrentHashMap在多线程下效率更高
HashTable使用一把锁处理并发问题,当有多个线程访问时,需要多个线程竞争一把锁,导致阻塞
ConcurrentHashMap则使用分段,相当于把一个HashMap分成多个,然后每个部分分配一把锁,这样就可以支持多线程访问
- HashMap在高并发下如果没有处理线程安全会有怎样的安全隐患,具体表现是什么。
Hashmap在并发环境下,可能出现的问题:
1、多线程put时可能会导致get无限循环,具体表现为CPU使用率100%;
原因:在向HashMap put元素时,会检查HashMap的容量是否足够,如果不足,则会新建一个比原来容量大两倍的Hash表,然后把数组从老的Hash表中迁移到新的Hash表中,迁移的过程就是一个rehash()的过程,多个线程同时操作就有可能会形成循环链表,所以在使用get()时,就会出现Infinite Loop的情况
- java中四种修饰符的限制范围。
范围 private default protected public
同一类
同一个包中的类
不同包的子类
其他类
- Object类中的方法。
clone getClass toString equalse haseCode wait 是当前线程等待该对象的锁,wait()方法一直等直到获得说或者被中断
notify 该方法唤醒该对象的某个线程 notifyall该方法唤醒该对象的所有线程
- 接口和抽象类的区别,注意JDK8的接口可以有实现。
Java8中的接口中的默认方法是可以被多重继承的。而抽象类不行
- 动态代理的两种方式,以及区别。
程序调用的是代理类而不是目标类 ,代理类和目标类对外具有相同的方法声明,有两种可以实现相同的方法声明,一个是实现相同的方法声明,一个是作为目标的子类
jdk 中采用的是proxy类参数动态代理的方式为某个接口产生实现类,如果某个产生了子类就是CGLIB
- Java序列化的方式。
序列化就是把Java对象储存在某一地方(硬盘、网络),也就是将对象的内容进行流化
反序列化:就是把二进制数据反序列化成对象数据
- 传值和传引用的区别,Java是怎么样的,有没有传值引用。
传值:传递的是值的副本。方法中对副本的修改,不会影响到调用方。
传引用:传递的是引用的副本,共用一个内存,会影响到调用方。此时,形参和实参指向同一个内存地址。对引用副本本身(对象地址)的修改,如设置为null,重新指向其他对象,不会影响到调用方
- 一个ArrayList在循环过程中删除,会不会出问题,为什么。
- @transactional注解在什么情况下会失效,为什么。
数据结构和算法
- B+树
- 快速排序,堆排序,插入排序(其实八大排序算法都应该了解
- 一致性Hash算法,一致性Hash算法的应用
JVM
- JVM的内存结构。
参考
- JVM方法栈的工作过程,方法栈和本地方法栈有什么区别。
- JVM的栈中引用如何和堆中的对象产生关联。
- 可以了解一下逃逸分析技术。
- GC的常见算法,CMS以及G1的垃圾回收过程,CMS的各个阶段哪两个是Stop the world的,CMS会不会产生碎片,G1的优势。
- 标记 - 清除算法
标记清除算法是最基础的收集算法,其他收集算法都是基于这种思想。标记清除算法分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。
它的主要缺点:①.标记和清除过程效率不高 。
②.标记清除之后会产生大量不连续的内存碎片
- 复制算法
复制算法采用的方式为从根集合进行扫描,将存活的对象移动到一块空闲的区域
- 标记 - 整理算法
- 标记清除和标记整理算法的理解以及优缺点。
- eden survivor区的比例,为什么是这个比例,eden survivor的工作过程。
- JVM如何判断一个对象是否该被GC,可以视为root的都有哪几种类型。
jvm要做垃圾回收时,首先要判断一个对象是否还有可能被使用。那么如何判断一个对象是否还有可能被用到?
- Java是否可以GC直接内存。
- Java类加载的过程。
classLoader 的作用就是将class文件加载到jvm中,然后执行一系列的方法,加载二进制数据,在堆内存中创建class对象,验证准备初始化,验证文件格式,字节码准备是为为类的静态变量分配空间,然后使用,new出对象程序中使用,卸载,执行垃圾回收
- 双亲委派模型的过程以及优势。
启动类加载器: BootStrap
扩展类加载器:
应用程序类加载器:负责加载用户类路径
- 常用的JVM调优参数。
-Xms设置堆的最小空间大小。
-Xmx设置堆的最大空间大小。
-XX:NewSize设置新生代最小空间大小。
-XX:MaxNewSize设置新生代最大空间大小。
-XX:PermSize设置永久代最小空间大小。
-XX:MaxPermSize设置永久代最大空间大小。
-Xss设置每个线程的堆栈大小。
- dump文件的分析。
- Java有没有主动触发GC的方式(没有)。
- 你能保证 GC 执行吗?
不能,虽然你可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC 的执行。
- Java 中堆和栈有什么区别?
JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。
多线程>
- Java实现多线程有哪几种方式。
public class MyThreadDemo extends Thread {
public MyThreadDemo(){}
public MyThreadDemo(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(getName());
}
}
}
/**
* 获取线程名称
*public final String getName();
*/
class Test{
public static void main(String[] args) {
MyThreadDemo myThreadDemo1 = new MyThreadDemo();
myThreadDemo1.setName("tom");
myThreadDemo1.run();
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
// 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
/*
* 方式2:实现Runnable接口
* 步骤:
* A:自定义类MyRunnable实现Runnable接口
* B:重写run()方法
* C:创建MyRunnable类的对象
* D:创建Thread类的对象,并把C步骤的对象作为构造参数传递
*/
class MyRunnableDemo {
public static void main(String[] args) {
// 创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "林青霞");
Thread t2 = new Thread(my, "刘意");
t1.start();
t2.start();
}
}
- Callable和Future的了解。
- 线程池的参数有哪些,在线程池创建一个线程的过程。
- volitile关键字的作用,原理。
内存可见性: 所用线程都能看到共享内存的最新状态。
每个线程都有自己的本地空间,线程执行时,会先把变量,从内存中,读取到自己的内存中,然后对变量进行操作,对变量操作完成后,在某个时间再把变量刷新到主内存中
- synchronized关键字的用法,优缺点。
同步代码块 同步方法
synchronized(对象) 对象可以随意(使用上帝就行)
{
需要被同步的代码(哪些代码在操作共享数据)
}
public static synchronized Singleton getInstance(){
....
}
- Lock接口有哪些实现类,使用场景是什么。
- 可重入锁的用处及实现原理,写时复制的过程,读写锁,分段锁(ConcurrentHashMap中的segment)。
- 悲观锁,乐观锁,优缺点,CAS有什么缺陷,该如何解决。
- ABC三个线程如何保证顺序执行。
- 线程的状态都有哪些。
准备就绪运行死亡
- sleep和wait的区别。
sleep()必须指定时间,不释放锁。wait() 可以指定时间和不指定时间,释放锁
- notify和notifyall的区别。
notify 该方法唤醒该对象的某个线程 notifyall该方法唤醒该对象的所有线程
- ThreadLocal的了解,实现原理。
线程局部变量,用于实现线程内的数据共享
数据库相关
- 常见的数据库优化手段
- 索引
- 分表
- 读写分离
- 缓存
- 定位慢查询
1.索引: 普通索引,允许重复键出现
唯一索引,不允许重复键出现
全文索引,针对表中的文本域
缺点: 吃内存
索引的数据结构:B-Tree物理文件大多都是以二叉树结构来存储到本地的,也就是说,实际所需要的数据库,都是存在树的叶子节点上,而且任何一个叶子节点,长度都是相同的
2.分表:水平分表,垂直分表
因为mysql表数据达到百万级别,查询效率会很低,容易造成死锁,甚至堆积很多连接,导致数据库挂掉
垂直分表:如果一个表中的字段非常多(长文本),而且很少情况下回使用,这时可以通过外键关联一张表例如(考试表 分数 和 详情)
水平分表:
水平分表策略:
1. 按照时间分割:这种分表有一定的局限性,当数据有较强的时效性,如微信聊天记录,这种数据用户很少会查询几个月前的数据可以按照月份分割
2. 按照区间分割:一般有严格的Id自增,如果按照userId水平分割,table_1 1-100w数据
3.按照hash分割:
通过一个原始的目标ID或者名称通过一定的hash算法,计算数据库存储的表名,然后访问相应的表。
数据库优化之读写分离:
一台数据库支持的最大并发是有限的,如果用户并发访问太多,一台服务器满足不了,就可以集群处理,mysql最常用的就是读写分离
1、主从同步,数据库最终会把数据持久化到硬盘中,如果集群必须确保,服务器的数据一致性,该表数据库操作的都是在主数据库,而其他数据库是从主数据库同步的
2. 读写分离,使用负载均衡来实现,写的操作,在主数据库读,在其他书库写
数据库优化之缓存:
在持久层(dao)和数据库(db)之间加一个缓存层,如果用户访问的是缓存起来的数据,那么用户之间从缓存中取,不用访问数据库,而缓存的操作是内存级的,访问速度快
作用: 减少数据库的压力,减少访问时间
常用的是hibernate 二级缓存,该缓存不能完成分布式缓存,可以使用redis作为主缓存
- 索引的优缺点,什么字段上建立索引
优点:索引可以加快数据的检索速度;加快表与表的连接;字段添加索引可以加快分组和排序的效率
缺点: 创建索引和维护索引需要增加时间成本;数据越大越占据物理空间
什么字段需要索引: 表的主键和外键;超过300的表的数据;经常出现where的子句
- 数据库连接池。
数据库连接不用每次创建和销毁,减少开销,增快响应速度
限定连接个数,不用因为数据库连接过多导致,数据库运行缓慢和崩溃
使用缓存,访问缓存(内存级的)的数据要比访问数据库的数据要快的多
Ehcache Gava cache redis
- durid的常用配置。
计算机网络
- TCP,UDP区别。
- 三次握手,四次挥手,为什么要四次挥手。
- 长连接和短连接。
- 连接池适合长连接还是短连接
设计模式
- 观察者模式
- 代理模式
- 单例模式,有五种写法,可以参考文章单例模式的五种实现方式
- 可以考Spring中使用了哪些设计模式
分布式相关
- 分布式事务的控制。
- 分布式锁如何设计。
- 分布式session如何设计。
- dubbo的组件有哪些,各有什么作用。
- zookeeper的负载均衡算法有哪些。
- dubbo是如何利用接口就可以通信的。
缓存相关
reids
reids 是一个key-value的nosql数据库,先保存到内存中根据一定的策略,持久化到磁盘中,即使断电,数据也不会丢失,可以用来缓存数据,web集群,共享seesion
用途:
缓存:高性能,高并发,因为内存天然支持高并发,,需要查询的数据修改少,
计数器: redis的计数器,是原子的,内存操作
分布式锁: setnx key value,当key不存在时,将 key 的值设为 value ,返回1。若给定的 key 已经存在,则setnx不做任何动作,返回0。
当setnx返回1时,表示获取锁,做完操作以后del key,表示释放锁,如果setnx返回0表示获取锁失败,整体思路大概就是这样
redis 淘汰机制(内存大小有限,保存有效数据):在redis中,允许用户设置最大内存,但是内存大小是有限的,redis内存数据,上升到一定的大小会运行淘汰机制,6种
已设置失效时间的 最近最少使用的 任意淘汰
- redis和memcached的区别。
Memcached仅支持简单的key-value结构的数据
- redis支持哪些数据结构。
string: string是最简单的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value,其上支持的操作与Memcached的操作类似。但它的功能更丰富。
list: list是一个链表结构,主要功能是push、pop、获取一个范围的所有值等等。之所以说它是双向的,因为它可以在链表左,右两边分别操作
set: set是集合,和我们数学中的集合概念相似,对集合的操作有添加删除元素,有对多个集合求交并差等操作。操作中key理解为集合的名字
zset: zset是set的一个升级版本,他在set的基础上增加了一个顺序属性,这一属性在添加修改元素的时候可以指定,每次指定后,zset会自动重新按新的值调整顺序。 可以对指定键的值进行排序权重的设定,它应用排名模块比较多
hash: Redis能够存储key对多个属性的数据(比如user1.uname user1.passwd),当然,你完成可以把这些属性以json格式进行存储,直接把它当作string类型进行操作,但这样性能上是对影响的,所以redis提出的Hash类型。
- redis是单线程的么,所有的工作都是单线程么。
redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理
- redis如何存储一个String的。
- redis的部署方式,主从,集群。
- redis的哨兵模式,一个key值如何在redis集群中找到存储在哪里。
- redis持久化策略。
Redis的数据是存在内存中的,如果Redis发生宕机,那么数据就会全部丢失,因此必须提供持久化机制。
Redis持久化机制有两种,第一种是快照(RDB),第二种是AOF日志,快照是一次全量备份,AOF 日志是连续的增量备份。快照是内存数据的二进制序列化形式,在存储上非常紧凑,而 AOF 日志记录的是内存数据修改的指令记录文本。AOF 日志在长期的运行过程中会变的无比庞大,数据库重启时需要加载 AOF 日志进行指令重放,这个时间就会无比漫长。所以需要定期进行 AOF 重写,给 AOF 日志进行瘦身。
RDB是通过Redis主进程fork子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化,AOF 日志存储的是 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录。即RDB记录的是数据,AOF记录的是指令
- RDB和AOF到底该如何选择?
1、不要仅仅使用 RDB,因为那样会导致你丢失很多数据,因为RDB是隔一段时间来备份数据。
2、也不要仅仅使用 AOF,因为那样有两个问题,第一,通过 AOF 做冷备没有RDB恢复速度快; 第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug。
3、用RDB恢复内存状态会丢失很多数据,重放AOP日志又很慢。Redis4.0推出了混合持久化来解决这个问题。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小。于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。
- Redis在互联网公司一般有以下应用:
String: 缓存,限流,计数器,分布式锁,分布式session
Hash: 储存用户信息,用户主页访问量,组合查询
List: 简单的队列,微博关注的人的时间轴列表
set:赞、踩、标签、好友关系
Zset:排行榜
参考
缓存雪崩是什么?
假设有如下一个系统,高峰期请求为5000次/秒,4000次走了缓存,只有1000次落到了数据库上,数据库每秒1000的并发是一个正常的指标,完全可以正常工作,但如果缓存宕机了,每秒5000次的请求会全部落到数据库上,数据库立马就死掉了,因为数据库一秒最多抗2000个请求,如果DBA重启数据库,立马又会被新的请求打死了,这就是缓存雪崩。
如何解决缓存雪崩
事前:redis高可用,主从+哨兵,redis cluster,避免全盘崩溃
事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL被打死
事后:redis持久化,快速恢复缓存数据
缓存穿透是什么?
假如客户端每秒发送5000个请求,其中4000个为黑客的恶意攻击,即在数据库中也查不到。举个例子,用户id为正数,黑客构造的用户id为负数,
如果黑客每秒一直发送这4000个请求,缓存就不起作用,数据库也很快被打死。
如何解决缓存穿透
查询不到的数据也放到缓存,value为空,如set -999 “”
总而言之,缓存雪崩就是缓存失效,请求全部全部打到数据库,数据库瞬间被打死。缓存穿透就是查询了一个一定不存在的数据,并且从存储层查不到的数据没有写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义
框架相关
- SpringMVC的Controller是如何将参数和前端传来的数据一一对应的。
- Mybatis如何找到指定的Mapper的,如何完成查询的。
- Quartz是如何完成定时任务的。
- 自定义注解的实现。
- Spring使用了哪些设计模式。
- Spring的IOC有什么优势。
- Spring如何维护它拥有的bean。
一些较新的东西
- JDK8的新特性,流的概念及优势,为什么有这种优势。
- 区块链了解
- 如何设计双11交易总额面板,要做到高并发高可用。