1.JAVA 中的几种基本数据类型是什么,各自占用多少字节。
byte(1字节)、short(2字节)、int(4字节)、long(8字节)、boolean()、char(主要看编码unicode2个字节,UTF-8不定长度)、double(8字节)、float(4字节)
2.String 类能被继承吗,为什么。
不能,String是final类不能被继承
3.String,Stringbuffer,StringBuilder 的区别。
String不可变字符串
StringBuffer可变字符串,线程安全效率低
StringBuilder可变字符串,线程不安全效率高
4.ArrayList 和 LinkedList 有什么区别。
ArrayList底层是数组结构,LinkedList底层是双链表结构
ArrayList相较于LinkedList,前者查询和修改效率快,后者新增和删除效率快
5.讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当 new 的时候,他们的执行顺序。
当new一个对象时,首先:父类的静态数据->父类静态代码块->子类静态数据->子类静态代码块->子类构造函数->父类构造函数->父类字段->子类字段
当new一个对象时,先检查该对象的父类是否已经初始化,如果未初始化,则先加载父类的静态常量,再加载父类的静态代码块紧接着对子类的静态常量和静态代码块以及构造函数进行加载,再到父类的构造函数、父类的字段、然后是子类的字段。
6.用过哪些 Map 类,都有什么区别,HashMap 是线程安全的吗,并发下使用的 Map 是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。
Map主要分为HashMap、TreeMap、HashTable,ConcurrentHashMap其中HashMap和TreeMap是线程不安全,HashTable,ConcurrentHashMap是线程安全的,并发下用的是ConcurrentHashMap
HashMap和ConcurrentHashMap的基本类似,其数据结构都是链表+红黑树的结构,默认容量是16,通过hash与表大小经过位运算计算出数据散落的位置,扩容量为当前的2倍,你可以理解为ConcurrentHashMap是HashMap线程安全的实现类
TreeMap的底层实现是红黑树结构,其没有什么默认容量,扩容的概念,通过hash的大小来判断数据存放在树的左子节点还是右子节点。
HashTable的底层是一个单链表,通过hash对表大小取模来确定数据的散列位置,其默认表大小为11,扩容量为当前表大小的2倍+1
7.HashMap的底层实现原理
java8中HashMap的底层采用了链表加红黑树的结构,在Hash表大于64,链表长度大于8的时候,链表会转换成红黑树且java8将链表的头插法改为尾插法(在并发的情况下头插法会造成死循环,尾插法虽然不会造成死循环但也是线程不安全的),在将key散列的时候用key的hash值和hash的高16位值进行异或运算,将其结果和hash表的size-1进行&运算,以此来减少hash碰撞的概率,在扩容的时候,扩容量为当前容量的2倍,同时扩容后对数据进行重新散列时,能够减少数据位置移动。
8.JAVA8 的 ConcurrentHashMap 为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。
java7使用的是分段锁,用Segment继承ReentrantLock实现锁的功能,每个Segment控制一段链表,他能解决不同段的数据并发,无需锁定。
缺点:在同段链表中数据量变大,锁的粒度就变大了,性能就会受到影响,同时每个节点都需要通过继承AQS来获得同步,但实际上并不需要每个节点都需要同步,只有链表的头,树的根节点需要同步,这无疑会浪费很多空间。
java8弃用了分段锁,使用synchronized来进行控制,因为synchronized是jvm层的关键字,其会跟着java版本的升级而不断优化。
9.有没有有顺序的 Map 实现类,如果有,他们是怎么保证有序的。
TreeMap是一种有顺序的实现类,其通过插入对象的hash来和父节点的hash进行对比,如果小就放入左子节点,如果大就放入右子节点,其有序是基于hash的有序。
10 正常情况下,当在 try 块或 catch 块中遇到 return 语句时,finally语句块在方法返回之前还是之后被执行?
在return之前执行。
11. 介绍下你理解的操作系统中线程切换过程
个人理解:多个线程会争抢cpu资源,争抢到的线程就会执行。如果线程A抢占到了CPU资源那么线程A就会执行,此时线程B进来和线程A同时争抢一个CPU资源,如果线程B争抢到了,那么线程A的程序计数器就会记录当前线程执行的位置,然后线程B处于执行,线程A处于等待状态。
12、Synchronized和Lock的区别?
synchronized是java关键字,lock是juc包下的api锁;synchronized执行时无法中断,但是lock可以中断,ReentrantLock可以借助condition做到精确唤醒,synchronized会自动释放锁,ReentrantLock需要手动释放锁
13.volatile 的原理
volatile有两个特点,可见性、禁止指令重排,注意volatile并不保证原子性。
多线程在执行的时候,每个线程都有自己独立的栈空间,当操作共享变量的时候,会拷贝一个共享变量的副本到自己的工作空间,这样每个线程对该共享变量的操作相互独立,互相无法感知。这就是不可见性,使用volatile关键字修饰共享变量能够使共享变量具有可见性,也即当A线程修改完共享变量副本,通知主内存中的共享变量进行修改, 同时通过 CPU总线嗅探机制告知其他线程该变量副本已经失效,需要重新从主内存中读取。
在不影响语义的情况下,编译器和处理器通常会对指令做出重排序已达到优化的目的,volatile被编译器编译成字节码时会在指令序列中插入内存屏障指令来禁止指令重排。
14.什么情况下会发生栈内存溢出。
大量循环或死循环;List、Map过大;变量过多
15.JVM 的内存结构,Eden 和 Survivor 比例。
默认Eden:Survivor0:Survivor1->8:1:1
16.JVM 内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为 Eden 和 Survivor。
因为java中大部分对象用过一次就会被回收,还有些经常使用需要很长时间才回收的对象,这样分的话可以使用不同的垃圾回收算法,便于对象的分代回收。
新生代中Eden和survivor的划分为了便于对象从新生代晋升到老年代。
17.JVM 中一次完整的 GC 流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的 JVM 参数。
java中大部分的对象都会分配到Eden区,当Eden满了就会触发GC,可存活的数据将会被标记,清除掉可回收的对象,还剩下的对象会被放到from Survivor区,from区满了时再次触发GC,存活的数据再次被标记,然后被复制到to Survivor区,此时from和to交换,即to变成from,from变成to,当对象被标记一定次数时,该对象就会进入老年代。
18.怎么理解栈、堆?堆中存什么?栈中存什么?
栈是线程私有,堆是所有线程共享。其中堆主要存放对象,栈存放局部变量表、动态连接、操作栈等等。
19.反射的原理,反射创建类实例的三种方式是什么。
容器启动的时候java代码会被编译成二进制字节码加载到内存,反射通过全限定类名找到对应的类,通过类就可以获得类的方法,字段等信息。创建类的实例可以用class.forName(),c.getClass(),Calss clazz = Test.class。
20.描述动态代理的几种实现方式,分别说出相应的优缺点。
动态代理实现有两种方式分别是JDK动态代理和Cglib,cglib由第三方提供,java中使用,要引入cglib的jar包,cglib可以继承任何类对任意类都能生成代理对象。
jdk动态代理Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h),最后一个参数要实现InvocationHandler接口做具体的实现操作
21.消息队列的使用场景。
可用于流量的削峰填谷,系统间的解耦,消息的广播和监听
22.消息的重发,补充策略。
消息的重发看具体情况,这里以rabbitmq举例,1、消息发送失败,那么需要开启数据做持久化,并设置channal为confirm模式成功返回ack,失败返回nack,保证数据的不丢失,然后根据返回的nack去具体实现重发的逻辑
23.如何保证消息的有序性
如果是kafka则可以给每条消息附带一个时间戳的数据,或者一个topic设置一个分区。
24.top 命令之后有哪些内容,有什么作用?
load average有3个值,3个值分别 是1、5、15分钟系统的平均负载值,如果三值加起来的平均数大于0.6,说明系统的负载高
各个进程占用的cpu,内存等等
25. 线上 CPU 爆高,请问你如何找到问题所在
用Top命令查到具体占用CPU高的进程的PID,然后可以用jstack pid的命令查看该java进程栈信息,根据栈信息定位出具体代码问题
26.说说BIO、NIO和AIO有什么区别
BIO:同步并阻塞,服务器实现模式为一个连接一个线程。即客户端有连接请求后服务端就要启动一个线程进行处理,如果这个连接不做任何事就会造成资源的浪费
NIO:同步非阻塞,服务器实现模式为一个请求一个线程,客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时,才会启动一个线程进行处理
AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS完成了再通知服务器应用去启动线程进行处理。
27.MQ 有可能发生重复消费,如何避免,如何做到幂等
第一种,大部分消息队列都支持消息的发送模式(最多一次,最少一次,精确一次),我们可以设置消息最多一次这个模式,但是这个模式虽然能够保证消息不被重复消费但是可能会出现数据丢失,不建议
第二种就需要从业务的角度来做到保持消息的幂等性,如果数据新增一般可以通过数据库表的唯一字段,如果修改可以添加一张表记录修改操作
28.MQ 的消息延迟了怎么处理,消息可以设置过期时间么,过期了你们一般怎么处理。
在并发高的情况下,消费端来不及消费,MQ里面就会堆积好多消息,我们可以给消息设置过期时间,消息一旦过期,就将其移到死信队列里面,由监听死信队列的线程去消费它,具体做什么处理看具体的业务。
29. Redis 集群方案应该怎么做?都有哪些方案?
redis可配置为主从复制集群架构,一主多从或者多主多从,主服务器处理读写业务,从服务器处理读业务,但是redis本身并不支持主备切换,我们可以引入sentinel,来构建redis高可用集群,实现主备切换。另外codis和官方提供的cluster也可以实现高可用redis集群。
30.常见的高并发场景有哪些,对应的架构设计方案是什么?
秒杀,秒杀的高并发量是一瞬间的,我们可以用消息队列来做流量的削峰填谷。
淘宝的双十一,首先淘宝的架构一定是一个集群架构,双十一当天并发量一定比平时高,所以集群的动态扩展性能要好,可以选用zk做分布式协调机制,zk的动态扩展性能好,对于用户行为的收集用kafka,kafka的吞吐量较高,缓存可以用redis集群,redis集群属于高可用集群,能够防止缓存宕机导致缓存雪崩,为防止缓存穿透可以添加布隆过滤器,付款阶段可以添加消息队列进行削峰填谷,搭建数据库集群,根据业务进行分库分表等等
31.在n个数里面找出连续m个数和最大的值
public Integer getMaxNumber(int[] arrs, int m) {
if (m > arrs.length) {
return 0;
}
int sum = 0;
int index = 0;
for (int i = 0; i < arrs.length - m; i++) {
if (arrs[index] > arrs[i + m])
continue;
if (arrs[i] > arrs[i + m]) {
index = i;
} else {
index = i + 1;
}
}
for (int i = 0; i < m; i++) {
sum += arrs[index + i];
}
return sum;
}
32.用 java 自己实现一个 LRU
/**
* LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
**/
public class LRU<K,V> {
private static final float hashLoadFactor = 0.75f;
private LinkedHashMap<K,V> map;
private int cacheSize;
public LRU(int cacheSize) {
this.cacheSize = cacheSize;
int capacity = (int)Math.ceil(cacheSize / hashLoadFactory) + 1;
map = new LinkedHashMap<K,V>(capacity, hashLoadFactory, true){
private static final long serialVersionUID = 1;
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > LRU.this.cacheSize;
}
};
}
public synchronized V get(K key) {
return map.get(key);
}
public synchronized void put(K key, V value) {
map.put(key, value);
}
public synchronized void clear() {
map.clear();
}
public synchronized int usedSize() {
return map.size();
}
public void print() {
for (Map.Entry<K, V> entry : map.entrySet()) {
System.out.print(entry.getValue() + "--");
}
System.out.println();
}
}
感兴趣的读者朋友可以 关注本公众号,和我们一起学习探究。
本人因所学有限,如有错误之处,望请各位指正!