整理面试题,以及部分答案,2018

版权声明:版权所有 盗版爆菊 https://blog.csdn.net/u012888052/article/details/83504908

个人总结


个人瞎写,仅供参考;部分转载,逐渐完善。

  • Java基础
  • Java 并发
  • Spring
  • Netty
  • 分布式相关
  • 数据库
  • 缓存
  • JVM
  • 系统优化
  • HTTP、TCP
  • 手写代码
  • ES

Java基础

1. List、Set、Map的区别

list和set是实现了collection接口,map不是collection的子接口或者实现类,Map是一个接口。

List:
1.可以允许重复的对象。
2.可以插入多个null元素。
3.是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
4.常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。

Set:
1.不允许重复对象
2. 无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator  或者 Comparable 维护了一个排序顺序。
3. 只允许一个 null 元素
4.Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。

Map:
1.Map不是collection的子接口或者实现类。Map是一个接口。
2.Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。
3. TreeMap 也通过 Comparator  或者 Comparable 维护了一个排序顺序。
4. Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
5.Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)

2. HashSet 是如何保证不重复的

首先会调用Object的hashCode方法判hashCode是否已经存在,如不存在则直接插入元素;
如果已存在则调用Object对象的equals方法判断是否返回true,如果为true则说明元素已经存在,如为false则插入元素。

3.HashMap 是线程安全的吗,为什么不是线程安全的(最好画图说明多线程环境下不安全)?

不是线程安全的,当A、B两个线程,将数据同时put到同一个HashMap中,会引起碰撞,造成数据被覆盖。

4.HashMap 的扩容过程

5.JDK 1.7 与 1.8 的 区别,说明 1.8 做了哪些优化,如何优化的?

switch支持String类型;
int、short、long、byte 可以用二进制表示;
新增Lambda表达式;
HashMap中,1.7中当HashCode值相同时,会将数据排成链表,查询时间复杂度比较高,需要遍历链表;1.8中当链表过长时,会将链表扩展成红黑树,使查询效率变高。

6.final finally finalize

final关键字,可以修饰类、方法、变量。修饰类,这个类不能被继承;修饰方法,这个方法不能被重写;修饰变量,需要在声明变量时,赋初始值,之后不能修改。
finally是在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出、捕获,finally块都会被执行。
finalize是方法名。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

7.强引用 、软引用、 弱引用、虚引用

强引用实际使用中大多数是强引用,String str = “abc”; list.add(str);
软引用,jvm在内存中有高速缓存,将对象放到里面,读取数据时,提高运行效率;

8.Java反射

Java利用现有对象方法,获取当前对象的某些属性,例如:Object类的getClass()方法

9.Arrays.sort 实现原理和 Collection 实现原理

10.LinkedHashMap的应用

LinkedHashMap是在HashMap基础上新增了双向链表,用于保存迭代顺序。

12、异常分类以及处理机制

Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。

runtimeException子类:
    java.lang.ArrayIndexOutOfBoundsException数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
    java.lang.NullPointerException空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等
    java.lang.ClassNotFoundException找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
IOException子类:
    IOException:操作输入流和输出流时可能出现的异常。
    FileNotFoundException   文件未找到异常
其他类:
    SQLException:操作数据库异常类
    StringIndexOutOfBoundsException 字符串索引超出范围抛出的异常

13、wait和sleep的区别

wait()方法属于Object类;sleep()方法属于Thread类;
调用wait()方法的过程中,线程会释放对象锁;调用sleep()方法的过程中,线程不会释放对象锁。

14、数组在内存中如何分配

//定义第一个数组
int[] arr=new int[3];
arr[0]=10;
arr[1]=20;
arr[2]=70;

第一步:栈存储局部变量(在方法定义中或方法声明上的变量),所以int[] arr 存放在了栈中;
第二步:new出的变量放在堆中,所以new int【3】在堆中。
第三步:每一个new出来的东西都有地址值(系统随机分配),所以new int【3】的地址值为0x001;把0x001赋给arr,在栈中的数组通过地址值找到堆中相应的地址。用数组名和编号的配合就可以找到数组中指定编号的元素,这种方法就叫做索引。
第四步:int类型的数据默认值为0
第五步:给数组中的每个元素赋值,把原先的默认值0干掉。

延伸:
当加上static修饰,变成 static int[] arr=new int[3];
arr 会被放在方法区(持久代/非堆)中。

Java 并发

1、synchronized 的实现原理以及锁优化?

2、volatile 的实现原理?

4、synchronized 在静态方法和普通方法的区别?

修饰静态方法,保证多个线程执行时,进行加锁操作,使之相互不干扰;
修饰普通方法,没有用处,因为普通方法,存储在单独的 本地方法栈中,是私有的。

5、怎么实现线程顺序执行?

闭锁CountDownLatch,适用一组线程执行完,再执行后面的线程
闭锁是典型的等待事件发生的同步工具类,将闭锁的初始值设置1,所有线程调用await方法等待,当事件发生时调用countDown将闭锁值减为0,则所有await等待闭锁的线程得以继续执行。

join()方法,保证线程执行顺序。

6、Java线程池中submit() 和 execute()方法有什么区别?

两个方法都可以向线程池提交任务,
execute()方法的返回类型是void,它定义在Executor接口中;
submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

7、synchronized 和 lock 有什么区别?

synchronized是java关键字;Lock是一个类;
synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象;
多个线程进行读操作,用synchronized锁,当一个线程在进行读操作时,其他线程只能等待无法进行读操作;用lock锁,线程之间不会发生冲突,可以进行读操作;
通过Lock可以知道线程有没有成功获取到锁,synchronized没办法知道;

8、Java中的ReadWriteLock是什么?

读写锁是用来提升并发程序性能的锁分离技术的成果。
Java中的ReadWriteLock是Java 5 中新增的一个接口,一个ReadWriteLock维护一对关联的锁,一个用于只读操作一个用于写。在没有写线程的情况下一个读锁可能会同时被多个读线程持有。写锁是独占的,你可以使用JDK中的ReentrantReadWriteLock来实现这个规则,它最多支持65535个写锁和65535个读锁。

8、ThreadLocal和Synchonized区别?

ThreadLocal和Synchonized都用于解决多线程并发訪问。
可是ThreadLocal与synchronized有本质的差别。
synchronized是利用锁的机制,使变量或代码块在某一时该仅仅能被一个线程訪问。它用于在多个线程间通信时可以获得数据共享。
ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

9、HashMap 的并发问题?

HashMap在高并发下可能引起死循环,造成cpu占用过高。
假如有两个线程P1、P2,以及链表 a=》b=》null
    P1先运行,运行完"Entry<K,V> next=e.next;"代码后发生堵塞,或者其它情况不再运行下去,此时e=a。next=b
    而P2已经运行完整段代码,于是当前的新链表newTable[i]为b=》a=》null
    P1又继续运行"Entry<K,V> next=e.next;
    "之后的代码,则运行完"e=next;"后,newTable[i]为a《=》b。则造成回路,while(e!=null)一直死循环

个人理解翻译下:
口->a->b->c->null
A线程执行remove(a)方法,将a节点拆除,将b节点拼接到根节点上;
B线程执行put(a)方法,将a节点put到c节点后,这个时候,结构变为:口->b->c->a->b->c->a....;//后面造成循环结构
get()方法;当查询时,进行链表循环,由于是循环结构,所以会一直查询,造成死循环。

10、ConcurrenHashMap 介绍?1.8 中为什么要用红黑树?

ConcurrenHashMap锁方式是稍微细粒度的,内部采用分离锁(分段锁)的设计。它默认将Hash表分为16个分段,get,put,remove等常用操作只锁当前需要用到的分段;
JDK1.8相较1.7,当链表长度大于8时,会将链表结构转成红黑树,便于大数据量的查询,使时间复杂度由O(n)变成O(logN)

12、如何检测死锁?怎么预防死锁?

分析代码;
用jstack工具查找,jstack是java虚拟机自带的一种堆栈跟踪工具;
加锁顺序(线程按照一定的顺序加锁)
加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)

13、Java 内存模型?

JVM内存模型

14、如何保证多线程下 i++ 结果正确?

使用锁机制,实现i++原子操作:
public static ReentrantLock lock = new ReentrantLock();
或者
synchronized (SynchronizedTest.class) {
    count++;
}

15、线程池的种类,区别和使用场景?

newCachedThreadPool:执行很多短期异步的小程序或者负载较轻的服务器
newFixedThreadPool:执行长期的任务,性能好很多
newSingleThreadExecutor:一个任务一个任务执行的场景
NewScheduledThreadPool:周期性执行任务的场景

16、分析线程池的实现原理和线程的调度过程?

17、线程池如何调优,最大数目如何确认?

N为CPU核心数
CPU密集型:N + 1
IO密集型:2N + 1

18、ThreadPoolExecutor 的内部工作原理

如果当前池大小 poolSize 小于 corePoolSize ,则创建新线程执行任务。
如果当前池大小 poolSize 大于 corePoolSize ,且等待队列未满,则进入等待队列
如果当前池大小 poolSize 大于 corePoolSize 且小于 maximumPoolSize ,且等待队列已满,则创建新线程执行任务。
如果当前池大小 poolSize 大于 corePoolSize 且大于 maximumPoolSize ,且等待队列已满,则调用拒绝策略来处理该任务。
线程池里的每个线程执行完任务后不会立刻退出,而是会去检查下等待队列里是否还有线程任务需要执行,如果在 keepAliveTime 里等不到新的任务了,那么线程就会退出。

18、ThreadLocal原理,用的时候需要注意什么?

19、最常见的ThreadLocal使用场景为 用来解决数据库连接、Session管理等。如:

    数据库连接:
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  
    public Connection initialValue() {  
        return DriverManager.getConnection(DB_URL);  
    }  
};  

public static Connection getConnection() {  
    return connectionHolder.get();  
}  

    Session管理:

private static final ThreadLocal threadSession = new ThreadLocal();  
  
public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
}  

19、CountDownLatch 和 CyclicBarrier 的用法,以及相互之间的差别?

23、分段锁的原理,锁力度减小的思考

Spring

1、BeanFactory 和 FactoryBean?

2、Spring IOC 的理解,其初始化过程?

3、BeanFactory 和 ApplicationContext?

4、Spring Bean 的生命周期,如何被管理的?

在这里插入图片描述

5、Spring Bean 的加载过程是怎样的?

6、Spring 注解

@Controller对应表现层的Bean,也就是Action;
@Service对应的是业务层Bean;
@Repository用于标注数据访问组件,即DAO组件;
@Autowired 默认按类型装配;
@Resource默认按名称装配;//这个是java的注解,不是Spring的。
@Async异步方法调用;

Netty

1、BIO、NIO和AIO

BIO:同步阻塞I/O    1.4之前只有BIO  适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中
NIO:同步非阻塞I/O  1.4开始有NIO    适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器
AIO:异步非阻塞I/O  1.7开始有AIO    适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器

分布式相关

1、Dubbo的底层实现原理和机制

2、描述一个服务从发布到被消费的详细过程

3、分布式系统怎么做服务治理

4、接口的幂等性的概念

5、消息中间件如何解决消息丢失问题

6、Dubbo的服务请求失败怎么处理

7、重连机制会不会造成错误

8、对分布式事务的理解

9、如何实现负载均衡,有哪些算法可以实现?

10、Zookeeper的用途,选举的原理是什么?

11、数据的垂直拆分水平拆分。

12、zookeeper原理和适用场景

13、zookeeper watch机制

14、redis/zk节点宕机如何处理

15、分布式集群下如何做到唯一序列号

16、如何做一个分布式锁

17、用过哪些MQ,怎么用的,和其他mq比较有什么优缺点,MQ的连接是线程安全的吗

18、MQ系统的数据如何保证不丢失

19、列举出你能想到的数据库分库分表策略;分库分表后,如何解决全表查询的问题

20、zookeeper的选举策略

21、全局ID

数据库

1、mysql分页有什么优化

2、悲观锁、乐观锁

3、组合索引,最左原则

4、mysql 的表锁、行锁

5、mysql 性能优化

6、mysql的索引分类:B+,hash;什么情况用什么索引

7、事务的特性和隔离级别

原子性;一致性;隔离性;持久性;
Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
Repeatable read (可重复读):可避免脏读、不可重复读的发生。
Read committed (读已提交):可避免脏读的发生。
Read uncommitted (读未提交):最低级别,任何情况都无法保证。

8、数据库的底层大体结构

在这里插入图片描述

现在最常用的存储引擎是InnoDB,它从MySQL 5.5.5版本开始成为了默认存储引擎。
客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数wait_timeout控制的,默认值是8小时。
MySQL 8.0版本直接将查询缓存的整块功能删掉了,也就是说8.0开始彻底没有这个功能了。
show processlist;  //查看当前链接的客户端信息。

9、MySQL日志

redo log(重做日志/物理日志,InnoDB引擎才会有,在存储引擎层)、bin log(归档日志/逻辑日志,sever层)

redo log 日志结构:

在这里插入图片描述

这两种日志有以下三点不同。
redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1 ”。
redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

10、InnoDB引擎存储日志时,两阶段,如何保证两份日志一样的?

1 prepare阶段 2 写binlog 3 commit
当在2之前崩溃时
重启恢复:后发现没有commit,回滚。备份恢复:没有binlog 。
一致
当在3之前崩溃
重启恢复:虽没有commit,但满足prepare和binlog完整,所以重启后会自动commit。备份:有binlog. 一致

11、基于主键索引和普通索引的查询有什么区别?

如果语句是select * from T where ID=500,即主键查询方式,则只需要搜索ID这棵B+树;
如果语句是select * from T where k=5,即普通索引查询方式,则需要先搜索k索引树,得到ID的值为500,再到ID索引树搜索一次。这个过程称为回表。
也就是说,基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。

12、避免长事物,需要监控的表,超过指定时间,就报警发邮件给开发:

SELECT * from information_schema.Innodb_trx;

13、MySQL索引的两个原则:

最左原则;//组合索引的面试题原理
索引下推;//5.6版本以后才有

缓存

1、Redis用过哪些数据类型,以及Redis底层怎么实现

字符串对象:SDS简单动态字符串
列表对象:压缩链表(ziplist)、双向链表(linkedlist)
哈希对象:ziplist或者hashtable
集合对象:intset或者hashtable
有序集合对象:ziplist或者tiaoyueb

2、Redis缓存穿透,缓存雪崩

缓存穿透解决方案:
方案1、使用互斥锁排队,分布式环境中要使用分布式锁,单机的话用普通的锁(synchronized、Lock)
方案2、布隆过滤器,就类似于一个hash set,用于快速判某个元素是否存在于集合中
缓存雪崩解决方案:
方案1、也是像解决缓存穿透一样加锁排队,实现同上;
方案2、建立备份缓存,缓存A和缓存B,A设置超时时间,B不设值超时时间,先从A读缓存,A没有读B,并且更新A缓存    和B缓存;
方案3、设置缓存超时时间的时候加上一个随机的时间长度,比如这个缓存key的超时时间是固定的5分钟加上随机的2分钟,酱紫可从一定程度上避免雪崩问题;

3、布隆过滤器

在这里插入图片描述

每个布隆过滤器对应到 Redis 的数据结构里面就是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的 hash 值算得比较均匀。
向布隆过滤器中添加 key 时,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。
向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位置是否都为 1,只要有一个位为 0,那么说明布隆过滤器中这个 key 不存在。如果都是 1,这并不能说明这个 key 就一定存在,只是极有可能存在,因为这些位被置为 1 可能是因为其它的 key 存在所致。如果这个位数组比较稀疏,判断正确的概率就会很大,如果这个位数组比较拥挤,判断正确的概率就会降低。具体的概率计算公式比较复杂,感兴趣可以阅读扩展阅读,非常烧脑,不建议读者细看。
使用时不要让实际元素远大于初始化大小,当实际元素开始超出初始化大小时,应该对布隆过滤器进行重建,重新分配一个 size 更大的过滤器,再将所有的历史元素批量 add 进去 (这就要求我们在其它的存储器中记录所有的历史元素)。因为 error_rate 不会因为数量超出就急剧增加,这就给我们重建过滤器提供了较为宽松的时间。...

https://juejin.im
掘金 — 一个帮助开发者成长的社区

3、如何使用Redis来实现分布式锁

加锁代码:
public class RedisTool {

private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
}

解锁代码:
public class RedisTool {

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}

4、Redis的并发竞争问题如何解决

redis自带的incr命令
在代码里要对redis操作的时候,针对同一key的资源,就先进行加锁(java里的synchronized或lock)
利用redis的setnx实现内置的锁

5、Redis持久化的几种方式,优缺点是什么,怎么实现的

RDB持久化可以在指定的时间间隔内生成数据集的时间点快照。
    优点:适合备份;适用于灾难恢复;可以最大化 Redis 的性能,父进程只需分出一个子进程进行备份;在恢复大数据集时的速度比 AOF 的恢复速度要快
    缺点:在服务器故障时可能丢失数据;当数据集比较大时,分出子线程比较耗时,甚至会影响客户端使用
AOF持久化记录服务器执行的所有写操作命令
    优点:AOF 持久化会让 Redis 变得非常耐久;Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写,重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合,使文件体积缩小;
    缺点:对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积;AOF 的速度可能会慢于 RDB;

6、Redis的缓存失效策略

定时删除、惰性删除、定期删除
目前常用的策略是 惰性删除 + 定期删除

7、Redis集群,高可用,原理

8. Redis有哪些数据结构?

字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。 
如果你是Redis中高级用户,还需要加上下面几种数据结构HyperLogLog、Geo、Pub/Sub。 
如果你说还玩过Redis Module,像BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了。

9. 使用过Redis分布式锁么,它是什么回事?

高版本的Redis的Set命令支持多个参数,jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
这是加锁,执行一条命令,使之进行原子化加锁。
解锁命令利用Lua脚本,再结合eval命令,一次性地进行原子解锁。

9. Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?

使用keys指令可以扫出指定模式的key列表。 
对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题? 
这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

9. 如果有大量的key需要设置同一时间过期,一般需要注意什么?

如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。

9. Redis如何做持久化的?

bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。在redis实例重启时,优先使用aof来恢复内存的状态,如果没有aof日志,就会使用rdb文件来恢复。 
如果再问aof文件过大恢复时间过长怎么办?你告诉面试官,Redis会定期做aof重写,压缩aof文件日志大小。如果面试官不够满意,再拿出杀手锏答案,Redis4.0之后有了混合持久化的功能,将bgsave的全量和aof的增量做了融合处理,这样既保证了恢复的效率又兼顾了数据的安全性。这个功能甚至很多面试官都不知道,他们肯定会对你刮目相看。 
如果对方追问那如果突然机器掉电会怎样?取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

10、Redis为什么不支持事物回滚?为什么 Redis 先执行指令再记录aof日志而不是像其它存储引擎一样反过来呢?

不支持回滚操作是因为redis是先执行指令然后做日志,所以即使发生异常,没有可以用来执行回滚操作的日志。
传统的数据库都是先做日志然后再做操作,这样可以用于事物回滚。

11、主从要求redis版本一致吗?

要求从库至少和主库一样新,否则主库的新指令同步过去从库不能识别,同步就会出错,所以升级版本时应该先升级从库,再升级主库。

12、sentinel(哨兵模式)、codis、cluster 的区别?

sentinel : 主从同步; codis:国人写的集群中间件; cluster:官方的集群方案

JVM

1、详细jvm内存模型

2、JVM优化参数

在这里插入图片描述

2、讲讲什么情况下回出现内存溢出,内存泄漏?

对象内存过大;
资源释放;
static关键字的使用。

4、JVM 年轻代到年老代的晋升过程的判断条件是什么呢?

年轻代分三个区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制 到年老区。

5、JVM 出现 fullGC 很频繁,怎么去线上排查问题?

引起原因:年轻代空间不足;永久代空间满了;
可以通过kill -3 查看内存快照,进行排查。

6、类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?

A类继承B类,当类加载器加载A类时,需要先加载B类,再加载到B类时,由于已经加载过了,所以不再加载。自己定义新的类加载器可以不使用双亲委派模式。

7、类的实例化顺序

父类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
子类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
父类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
父类构造方法
子类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
子类构造方法

初始化的顺序,先静态方法,再构造方法,每个又是先基类后子类。

8、JVM垃圾回收机制,何时触发MinorGC等操作

Minor GC触发条件:(复制-清除)
    当Eden区满时,触发Minor GC;
Full GC触发条件:(标记-清除)
    调用System.gc时,系统建议执行Full GC,但是不必然执行;
    老年代空间不足;
    方法区空间不足;

9、如何优化(重构)现有系统

优化SQL
数据库配置低,加配置
数据库负载高,加机器做集群
将数据放到缓存层,redis、es
代码优化,例:循环查询数据库
静态资源放到专门的OSS服务器,并用CDN加速
合理减少中间服务的远程调用

10、java类加载机制

加载.class文件的方式
– 从本地系统中直接加载
– 通过网络下载.class文件
– 从zip,jar等归档文件中加载.class文件
– 从专有数据库中提取.class文件
– 将Java源文件动态编译为.class文件

11、类的生命周期

在这里插入图片描述

	类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。

加载:查找并加载类的二进制数据

加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

1、通过一个类的全限定名来获取其定义的二进制字节流。

2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。

加载阶段完成后,虚拟机外部的 二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。

•连接

– 验证:确保被加载的类的正确性

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:

文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。

元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。

字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

符号引用验证:确保解析动作能正确执行。

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

– 准备:为类的静态变量分配内存,并将其初始化为默认值

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:

1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。

2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。

假设一个类变量的定义为:public static int value = 3;

那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类构造器()方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。

复制代码
· 这里还需要注意如下几点:
· 对基本数据类型来说,对于类变量(static)和全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的零值,而对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。
· 对于同时被static和final修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过;而只被final修饰的常量则既可以在声明时显式地为其赋值,也可以在类初始化时显式地为其赋值,总之,在使用前必须为其显式地赋值,系统不会为其赋予默认零值。
· 对于引用数据类型reference来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的零值,即null。
· 如果在数组初始化时没有对数组中的各元素赋值,那么其中的元素将根据对应的数据类型而被赋予默认的零值。
复制代码
3、如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。

假设上面的类变量value被定义为: public static final int value = 3;

编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为3。回忆上一篇博文中对象被动引用的第2个例子,便是这种情况。我们可以理解为static final常量在编译期就将其结果放入了调用它的类的常量池中

– 解析:把类中的符号引用转换为直接引用

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。

直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

•初始化
初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

①声明类变量是指定初始值

②使用静态代码块为类变量指定初始值

JVM初始化步骤

1、假如这个类还没有被加载和连接,则程序先加载并连接该类

2、假如该类的直接父类还没有被初始化,则先初始化其直接父类

3、假如类中有初始化语句,则系统依次执行这些初始化语句

类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

– 创建类的实例,也就是new的方式

– 访问某个类或接口的静态变量,或者对该静态变量赋值

– 调用类的静态方法

– 反射(如Class.forName(“com.shengsiyuan.Test”))

– 初始化某个类的子类,则其父类也会被初始化

– Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类

结束生命周期

•在如下几种情况下,Java虚拟机将结束生命周期

– 执行了System.exit()方法

– 程序正常执行结束

– 程序在执行过程中遇到了异常或错误而异常终止

– 由于操作系统出现错误而导致Java虚拟机进程终止

12、类加载器的层次关系

在这里插入图片描述

站在Java开发人员的角度来看,类加载器可以大致划分为以下三类:
	启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
	扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
	应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
	应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:
		1)在执行非置信代码之前,自动验证数字签名。
		2)动态地创建符合用户特定需要的定制化构建类。
		3)从特定的场所取得java class,例如数据库中和网络中。

13、JVM类加载机制

	•全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
	•父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
	•缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

14、双亲委派模型

	双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
	双亲委派机制:
		1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
		2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
		3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
		4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

15、双亲委派模型意义:

-系统类防止内存中出现多份同样的字节码
-保证Java程序安全稳定运行

16、ES和Sola的区别

在这里插入图片描述

17、什么是 TCP/IP

TCP/IP 被认为是一个四层协议
(1)链路层,有时也称作数据链路层或网络接口层,通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡。
(2)网络层,有时也称作互联网层,处理分组在网络中的活动。网络层协议包括IP协议(网际协议),ICMP协议(internet互联网控制报文协议),以及IGMP协议(Internet组管理协议)
(3)运输层,包含协议TCP(传输控制协议)和UDP(用户数据报协议)。TCP把数据分成小块,交给网络层。UDP则为应用层提供服务,把数据报的分组从一台主机发送到另一台主机,但并不保证发送到另一台主机。
(4)应用层,负责处理特定的应用程序细节。Telnet远程登录,FTP文件传输协议,SMTP简单邮件传送协议,SNMP简单网络管理协议。

18、http1.0和http1.1区别

1)缓存处理策略不同
2)带宽优化和网络连接的使用
3)错误通知的管理
4)host头处理
5)长链接;1.0每次请求都要创建连接;1.1保持长链接

19、http请求get和post的区别

1)get请求可被缓存,post不能被缓存
2)get请求被保存在浏览器历史记录中,post不会保留
3)get请求可以被收藏在书签中,post不能
4)get请求安全性低于post
5)get请求有长度限制,post没有
6)post不限制提交的数据类型,post可以提交文件

20、session与cookie的区别

1)cookie保存在客户端,关闭浏览器cookie被删除;cookie子客户端可以被伪造,敏感数据不易保存。session保存在服务端,过多会消耗服务器资源,尽量少使用
2)session是服务器用来跟踪用户的一种手段,每个session都有唯一标识id,生成后发送 到客户端cookie保存,发起请求后根据id来匹配session
3)存储数据类型不同;session可以存储任意java对象,cookie只能存储String
4)长于10k的数据,不要用到cookie

10、手写代码

//快速排序代码
public class QuickSort {  
    public static void main(String[] args) {  
        int[] a = {1, 2, 4, 5, 7, 4, 5 ,3 ,9 ,0};  
        System.out.println(Arrays.toString(a));  
        quickSort(a);  
        System.out.println(Arrays.toString(a));  
    }  
  
    public static void quickSort(int[] a) {  
        if(a.length>0) {  
            quickSort(a, 0 , a.length-1);  
        }  
    }  
  
    private static void quickSort(int[] a, int low, int high) {  
        //1,找到递归算法的出口  
        if( low > high) {  
            return;  
        }  
        //2, 存  
        int i = low;  
        int j = high;  
        //3,key  
        int key = a[ low ];  
        //4,完成一趟排序  
        while( i< j) {  
            //4.1 ,从右往左找到第一个小于key的数  
            while(i<j && a[j] > key){  
                j--;  
            }  
            // 4.2 从左往右找到第一个大于key的数  
            while( i<j && a[i] <= key) {  
                i++;  
            }  
            //4.3 交换  
            if(i<j) {  
                int p = a[i];  
                a[i] = a[j];  
                a[j] = p;  
            }  
        }  
        // 4.4,调整key的位置  
        int p = a[i];  
        a[i] = a[low];  
        a[low] = p;  
        //5, 对key左边的数快排  
        quickSort(a, low, i-1 );  
        //6, 对key右边的数快排  
        quickSort(a, i+1, high);  
    }  
}  

//冒泡排序代码
public class Sort {
    public static void main(String[] args){
        int[] arr = {6,3,2,1,7};
        for(int i = 0;i<arr.length-1;i++){//外层循环n-1
            for(int j = 0;j<arr.length-i-1;j++){//内层循环n-i-1
                if(arr[j]>arr[j+1]){//从第一个开始,往后两两比较大小,如果前面的比后面的大,交换位置
                    int tmp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = tmp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

//单例模式(饿汉)
public class Singleton {
   	private final static Singleton INSTANCE = new Singleton();
   	private Singleton(){}
   	public static Singleton getInstance(){
       	return INSTANCE;
   	}
}

//单例模式(懒汉)
public class Singleton {
	private static Singleton singleton;
	private Singleton() {}
	public static Singleton getInstance() {
       	if (singleton == null) {
           	singleton = new Singleton();
       	}
       	return singleton;
   	}
}

//工厂模式


ES

1、Elasticsearch是如何实现Master选举的?

Elasticsearch的选主是ZenDiscovery模块负责的,主要包含Ping(节点之间通过这个RPC来发现彼此)和Unicast(单播模块包含一个主机列表以控制哪些节点需要ping通)这两部分;
对所有可以成为master的节点(node.master: true)根据nodeId字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master节点。
如果对某个节点的投票数达到一定的值(可以成为master节点数n/2+1)并且该节点自己也选举自己,那这个节点就是master。否则重新选举一直到满足上述条件。

2、

后面还需要研究的方向

● 系统架构
○ 系统架构设计
○ 分布式及负载均衡
■ php分布式
■ redis分布式
■ mysql分布式
■ hbase分布式
○ 数据同步
■ 通过rsync同步数据
○ 数据备份
■ 文件备份
■ 数据库备份
○ 代码同步
■ 自动拉取git代码
■ 代码发布系统
■ 代码审核系统
○ 分布式服务
■ 基于zookeeper实现分布式服务
○ 监控服务
■ 抓取进程监控
■ 数据入库监控
■ 索引监控
■ API接口监控
■ 磁盘挂载监控
■ 主从同步监控
■ 数据一致性监控
■ 数据备份监控
■ 代码发布监控

猜你喜欢

转载自blog.csdn.net/u012888052/article/details/83504908