0、杂谈
0.1 有人问我,作为一名程序员,你觉得最重要的能力是什么?是技术吗?
可以看看如下博客
https://blog.csdn.net/weixin_47955802/article/details/110353249?spm=1001.2014.3001.5501
我的看法:
技术员,有技术的从业人员,口才也是能力的一种,这是基石。首先我觉得最重要的是心态,抗压能力,遇到问题冷静面对;其次是技术+管理能力,这样能提高一个人的工作效率;最后结束在前面的基础上,高效解决问题的能力。
0.2 工作六年总结的java面试题和经验
一般面试包括下面几方面知识类型:
Java基础、多线程、IO与NIO、虚拟机、设计模式
数据结构与算法(要有手写算法的能力)
计算机网络(TCP三次握手和四次挥手)
数据通信(RESTful、RPC、消息队列)
操作系统(Linux的基本命令以及使用)
主流框架(Spring底层原理与源码问的很多)
数据存储(最常见的是MySQL、Redis)
分布式
其他问题:
实际场景题
生活方面的问题
性格/其他方面的问题
二、面试常问的知识点
1)集合相关问题(必问)
HashMap、LinkedHashMap、ConcurrentHashMap、ArrayList、LinkedList的底层实现
HashMap和Hashtable的区别
ArrayList、LinkedList、Vector的区别
HashMap和ConcurrentHashMap的区别
HashMap和LinkedHashMap的区别
HashMap是线程安全的吗
ConcurrentHashMap是怎么实现线程安全的
HashMap 的长度为什么是2的幂次方
2)多线程并发相关问题(必问)
创建线程的3种方式
什么是线程安全
Runnable接口和Callable接口的区别
wait方法和sleep方法的区别
synchronized、Lock、ReentrantLock、ReadWriteLock
介绍下CAS(无锁技术),什么是悲观锁和乐观锁
volatile关键字的作用和原理
什么是ThreadLocal
创建线程池的4种方式
ThreadPoolExecutor的内部工作原理
分布式环境下,怎么保证线程安全
synchronized和lock区别以及volatile和synchronized的区别
3)JVM相关问题
介绍下垃圾收集机制(在什么时候,对什么,做了什么)。
垃圾收集有哪些算法,各自的特点。
类加载的过程。
双亲委派模型。
有哪些类加载器。
能不能自己写一个类叫java.lang.String。
4)设计模式相关问题(必问)
设计模式比较常见的就是让你手写一个单例模式(注意单例模式的几种不同的实现方法)或者让你说一下某个常见的设计模式在你的项目中是如何使用的。
另外面试官还有可能问你抽象工厂和工厂方法模式的区别、工厂模式的思想这样的问题。
建议把代理模式、观察者模式、(抽象)工厂模式好好看一下,这三个设计模式很有用。
5)数据库相关问题,针对MySQL(必问)
给题目让你手写SQL。
有没有SQL优化经验。
MySQL索引的数据结构。
SQL怎么进行优化。
SQL关键字的执行顺序。
有哪几种索引。
什么时候该(不该)建索引。
Explain包含哪些列。
6)框架相关问题
Hibernate和Mybatis的区别。
Spring MVC和Struts2的区别。
Spring用了哪些设计模式。
Spring中AOP主要用来做什么。
Spring注入bean的方式。
什么是IOC,什么是依赖注入。
Spring是单例还是多例,怎么修改。
Spring事务隔离级别和传播性。
介绍下Mybatis/Hibernate的缓存机制。
Mybatis的mapper文件中#和$的区别。
Mybatis的mapper文件中resultType和resultMap的区别。
7)其他问题
介绍下栈和队列。
IO和NIO的区别。
接口和抽象类的区别。
int和Integer的自动拆箱/装箱相关问题。
常量池相关问题。
==和equals的区别。
什么是JDK?什么是JRE?什么是JVM?三者之间的联系与区别
Java和C++的区别
重载和重写的区别。
String和StringBuilder、StringBuffer的区别。
静态变量、实例变量、局部变量线程安全吗,为什么。
try、catch、finally都有return语句时执行哪个。
介绍下B树、二叉树。
分布式锁的实现。
分布式session存储解决方案。
常用的linux命令。
三、其他经验分享
1)不要一开始就去面试自己最想去的公司,把面试当作一次技术的交流,面试的越多,经验越多,等面出了心得再去投理想的公司。
2)不熟悉的技术不要主动提。
3)如果没有明白面试官的问题,不要不懂装懂,可以礼貌地让对方重复一遍,也让自己多一点时间思考。
4)在面试的日子里,要保持每天学习,无论是学习新东西还是复习旧东西。
5)如果超过3-5天还没有得到结果,自己又很想去这家公司,可以主动联系HR询问面试结果,就算面试没有通过,也可以问问面试失败的原因,总结经验。
1.1 知识点明细总结
1、Java基础
Java 中的参数传递时传值呢?还是传引用?
Java 的参数是以值传递的形式传入方法中,而不是引用传递。
当传递方法参数类型为基本数据类型(数字以及布尔值)时,一个方法是不可能修改一个基本数据类型的参数。
当传递方法参数类型为引用数据类型时,一个方法将修改一个引用数据类型的参数所指向对象的值。即使 Java 函数在传递引用数据类型时,也只是拷贝了引用的值罢了,之所以能修改引用数据是因为它们同时指向了一个对象,但这仍然是按值调用而不是引用调用。
什么是面向对象?
面向对象是一种思想,万物皆对象。Java 是一个支持并发、基于类和面向对象的计算机编程语言。面向对象软件开发具有以下优点:
代码开发模块化,更易维护和修改。
代码复用性强。
增强代码的可靠性和灵活性。
增加代码的可读性。
JDK、JRE、JVM 分别是什么关系?
JDK包含JRE、JVM,JRE包含JVM
JDK 即为 Java 开发工具包,包含编写 Java 程序所必须的编译、运行等开发工具以及 JRE
JRE 即为 Java 运行环境,提供了运行 Java 应用程序所必须的软件环境
JVM 即为 Java 虚拟机,提供了字节码文件(.class)的运行环境支持,也是java与平台无关语言的一种表现
Lambda表达式?
Lmabda表达式的语法总结: () -> ( );
无参数无返回值:() -> System.out.println(“Hello WOrld”)
有一个参数无返回值 : (x) -> System.out.println(x)
有且只有一个参数无返回值:x -> System.out.println(x)
有多个参数,有返回值,有多条lambda体语句:(x,y) -> {System.out.println(“xxx”);return xxxx;};
有多个参数,有返回值,只有一条lambda体语句:(x,y) -> xxxx
String s = new String(“xyz”) 会创建几个对象?
首先,在 String 池内找,找到 “xyz” 字符串,不创建 “xyz” 对应的 String 对象,否则创建一个对象。
然后,遇到 new 关键字,在内存上创建 String 对象,并将其返回给 s ,又一个对象。
所以,总共是 1 个或者 2 个对象。
讲讲类的实例化顺序?
先静态后非静态最后构造函数,先父类后子类,静态在非静态之前;
初始化顺序如下:
父类静态变量
父类静态代码块
子类静态变量、
子类静态代码块
父类非静态变量(父类实例成员变量)
父类构造函数
子类非静态变量(子类实例成员变量)
子类构造函数
2、集合相关问题(必问)
HashMap、LinkedHashMap、ConcurrentHashMap、ArrayList、LinkedList的底层实现?
HashMap和Hashtable的区别
1、非同步(线程不安去)和同步(线程安全),允许控制和不允许控制,默认带线16和11,扩容不一样
LinkedHashMap的有序性
基于链表存储,基于插入排序和顺序访问,链表的特性是,插入删除快,查询慢。
LinkedHashMap与HashMap的区别
有序和无序,单链表和双链表
LinkedList与ArrayList的区别
LinkedList底层是双向链表
ArrayList底层是可变数组LinkedList不允许随机访问,即查询效率低
ArrayList允许随机访问,即查询效率高LinkedList插入和删除效率快
ArrayList插入和删除效率低
Vector和ArrayList的区别
Vector同步、线程安全的
ArrayList异步、线程不安全Vector 需要额外开销来维持同步锁,性能慢
ArrayList 性能快
什么是ConcurrentHashMap
基于链表和双数组实现,key值唯一,value值允许重复,key,value不能控制,无序的。
什么是fail-fast(快速失败)机制
fail-fast(快速失败)是java集合一种容错机制,工作中遇到产生的情况,list和map采用集合remove()方法移除元素时经常出现,
查看源码发现,modCount 发生改变后,expectedModCount会出现重新赋值的情况,导致 modCount(修改计数) = expectedModCount (预期修改计数
ConcurrentHashMap的数据结构
ConcurrentHashMap的数据结构为一个Segment数组,Segment的数据结构为HashEntry的数组,而HashEntry存的是我们的键值对,可以构成链表。可以简单的理解为数组里装的是HashMap
从上面的结构我们可以了解到,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长,但是带来的好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment。正是因为其内部的结构以及机制,ConcurrentHashMap在并发访问的性能上要比Hashtable和同步包装之后的HashMap的性能提高很多。在理想状态下,ConcurrentHashMap 可以支持 16 个线程执行并发写操作(如果并发级别设置为 16),及任意数量线程的读操作
- ConcurrentHashMap 底层采用分段的数组+链表实现,线程安全
- 通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
- Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
HashMap和ConcurrentHashMap的区别
HashMap不支持并发操作,没有同步方法,ConcurrentHashMap支持并发操作,通过继承 ReentrantLock(JDK1.7重入锁)/CAS和synchronized(JDK1.8内置锁)来进行加锁(分段锁),每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
JDK1.8之前HashMap的结构为数组+链表,JDK1.8之后HashMap的结构为数组+链表+红黑树;JDK1.8之前ConcurrentHashMap的结构为segment数组+数组+链表,JDK1.8之后ConcurrentHashMap的结构为数组+链表+红黑树 (空间+时间)。
HashMap 的长度为什么是2的幂次方
1、为了能让HashMap存取高效,尽量减少碰撞,也就是要尽量把数据分配均匀,2、也就是说hash%length=hash&(length-1),但前提是length是2的n次方,并且采用&运算比%运算效率高,这也就解释了HashMap的长度为什么是2的幂次方。
3 多线程并发相关问题(必问)
创建线程的3种方式?
1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和Future创建线程
什么是线程安全?
原子性(Synchronized, Lock):要么都成功,那么都不成功
通过Volatile修饰变量可以保证有序性和可见性
有序性(Volatile,Synchronized, Lock) :当前线程内,执行是有序的
可见性(Volatile,Synchronized,Lock):多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值
什么是线程不安全?
1、存在共享可不变量,如静态变量 2、存在资源竞争
说说单例模式的双重锁机制?
为什么需要两次判断if(singleton ==null)?
分析:第一次校验:由于单例模式只需要创建一次实例,如果后面再次调用getInstance方法时,则直接返回之前创建的实例,因此大部分时间不需要执行同步方法里面的代码,大大提高了性能。如果不加第一次校验的话,那跟上面的懒汉模式没什么区别,每次都要去竞争锁。
第二次校验:如果没有第二次校验,假设线程t1执行了第一次校验后,判断为null,这时t2也获取了CPU执行权,也执行了第一次校验,判断也为null。接下来t2获得锁,创建实例。这时t1又获得CPU执行权,由于之前已经进行了第一次校验,结果为null(不会再次判断),获得锁后,直接创建实例。结果就会导致创建多个实例。所以需要在同步代码里面进行第二次校验,如果实例为空,则进行创建。
需要注意的是,private static volatile SingleTon3 singleTon=null;需要加volatile关键字,否则会出现错误。问题的原因在于JVM指令重排优化(在Java中看似顺序的代码在JVM中,可能会出现编译器或者CPU对这些操作指令进行了重新排序)的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。
(4)静态内部类:同样也是利用了类的加载机制,它与饿汉模式不同的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。
public class Singleton { private volatile static Singleton singleton; private Singleton() { } public static Singleton getInstance() { // 第一次非空判断,保证有实例后会创建新的对象 if (singleton == null) { synchronized (Singleton.class) { //第二非空判断? //多线程环境,多个线程(a\b\b)进入同步代码块(synchronized),a获得锁,b、c线程进入阻塞队列, // a 创建对象后释放锁,b或c进入同步块,如果同步块没有非空判断,会继续创建对象,无法保证只返回一个实例 if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
Runnable接口和Callable接口的区别?
- Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
- Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
- 多线程执行是不可控的,Callable接口通过返回值,更好的观察线程执行
wait方法和sleep方法的区别?
- sleep是Thread类的方法,wait是Object类中定义的方法
- sleep:线程睡眠,到时间后自动唤醒,会占有对象锁;
- wait: 线程等待,会释放对象锁,需要使用notify()或者notifyAll()唤醒
- 注意:当有多个线程等待时,notify()唤醒的不一定是当前线程(这个可以去看看公平和非公平锁)
synchronized、Lock、ReentrantLock、ReadWriteLock
Lock和synchronized比较
1、Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2、synchronized由程序自动释放锁,而Lock需要程序员手动释放,避免死锁;
3、Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4、Lock可以知道是否成功获得锁,但synchronized不行;
5、Lock支持可重入锁,但synchronized不行;
6、synchronized锁的范围是整个方法或代码块;而Lock是方法调用的方式,灵活性更大;
7、ReadWriteLock可以提升多个线程进行读操作的效率,而synchronized做不到;
AQS()和ReentrantLock(可重入锁)
重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁
ReadWriteLock
作为读写分离锁,可以有效的帮助减少锁的竞争,提升系统性能。
用锁分离的机制来提升性能比较好理解。举个例子,有三个线程A1、A2、A3进行写操作,三个线程B1、B2、B3进行读的操作。如果使用重入锁或者synchronized(内部锁),理论上所有的读之间、读与写之间、写与写之间都是串行操作。当B1进行读取时,B2、B3则必须进行等待。由于读操作并不对数据的完整性进行破坏,所以这种等待是不合理的。因此,读写分离锁就派上了用场,它能支持多个读的操作并行执行。
需要注意的是,读写分离锁只是针对读读之间能够并行,在读写和写写之间依然会互斥,总结起来就是这三种情况:
- 读-读不互斥:读读之间不阻塞;
- 读-写互斥:读阻塞写,写也会阻塞读;
- 写-写互斥:写写阻塞;
介绍下CAS(无锁技术),什么是悲观锁和乐观锁?
1、CAS:Compare and Swap/Set,即比较再交换/设置
2、CAS算法的过程是这样:它包含三个参数CAS(V,E,N): V表示要更新的变量,,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。
ABA问题:
线程一需要修改A为B,线性二修改B为A,线程,
乐观锁 (认为多线程没问题,不上锁)
总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
悲观锁 (每次执行都加锁)
总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁
volatile关键字的作用和原理?
作用 1、保证内存可见性(如多线程修改后的变量结果可见),防止java指令重排;
原理:通过volatile的变量会被加载到主内存
当系统运行时,CPU执行计算的过程如下:
- 程序以及数据被加载到主内存
- 指令和数据被加载到CPU缓存
- CPU执行指令,把结果写到高速缓存
- 高速缓存中的数据写回主内存
什么是ThreadLocal?
提供线程之间内存隔离,使用完成后需要采用remove()方法释放内存,否则容易导致内存泄漏。
在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本
hreadLocal类接口很简单,只有4个方法,我们先来了解一下:
- void set(Object value)
设置当前线程的线程局部变量的值。
- public Object get()
该方法返回当前线程所对应的线程局部变量。
- public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
- protected Object initialValue()
创建线程池的4种方式?
1、ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2、 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
3、ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的
4、ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
创建一个定长(固定时间周期)的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
ThreadPoolExecutor的内部工作原理?
如果线程池大小poolSize小于corePoolSize,则创建新线程执行任务。
如果线程池大小poolSize大于corePoolSize,且等待队列未满,则进入等待队列。
如果线程池大小poolSize大于corePoolSize且小于maximumPoolSize,且等待队列已满,则创建新线程执行任务。
如果线程池大小poolSize大于corePoolSize且大于maximumPoolSize,且等待队列已满,则调用拒绝策略来处理该任务。
线程池里的每个线程执行完任务后不会立刻退出,而是会去检查下等待队列里是否还有线程任务需要执行,如果在keepAliveTime里等不到新的任务了,那么线程就会退出。
分布式环境下,怎么保证线程安全?
避免并发
在分布式环境中,如果存在并发问题,那么很难通过技术去解决,或者解决的代价很大,所以我们首先要想想是不是可以通过某些策略和业务设计来避免并发。比如通过合理的时间调度,避开共享资源的存取冲突。另外,在并行任务设计上可以通过适当的策略,保证任务与任务之间不存在共享资源,比如在以前博文中提到的例子,我们需要用多线程或分布式集群来计算一堆客户的相关统计值,由于客户的统计值是共享数据,因此会有并发潜在可能。但从业务上我们可以分析出客户与客户之间 数据是不共享的,因此可以设计一个规则来保证一个客户的计算工作和数据访问只会被一个线程或一台工作机完成,而不是把一个客户的计算工作分配给多个线程去 完成。这种规则很容易设计,例如可以采用hash算法。
时间戳
分布式环境中并发是没法保证时序的,无论是通过远程接口的同步调用或异步消息,因此很容易造成某些对时序性有要求的业务在高并发时产生错误。比如系统A需要把某个值的变更同步到系统B,由于通知的时序问题会导致一个过期的值覆盖了有效值。对于这个问题,常用的办法就是采用时间戳的方式,每次系统A发送变更给系统B的时候需要带上一个能标示时序的时间戳,系统B接到通知后会拿时间戳与存在的时间戳比较,只有当通知的时间戳大于存在的时间戳,才做更新。这种方式比较简单,但关键在于调用方一般要保证时间戳的时序有效性。
串行化
有的时候可以通过串行化可能产生并发问题操作,牺牲性能和扩展性,来满足对数据一致性的要求。比如分布式消息系统就没法保证消息的有序性,但可以通过变分布式消息系统为单一系统就可以保证消息的有序性了。另外,当接收方没法处理调用有序性,可以通过一个队列先把调用信息缓存起来,然后再串行地处理这些调用。
数据库
分布式环境中的共享资源不能通过Java里同步方法或加锁来保证线程安全,但数据库是分布式各服务器的共享点,可以通过数据库的高可靠一致性机制来满足需求。比如,可以通过唯一性索引来解决并发过程中重复数据的生产或重复任务的执行;另外有些更新计算操作也尽量通过sql来完成,因为在程序段计算好后再去更新就有可能发生脏复写问题,但通过一条sql来完成计算和更新就可以通过数据库的锁机制来保证update操作的一致性。
行锁
有的事务比较复杂,无法通过一条sql解决问题,并且有存在并发问题,这时就需要通过行锁来解决,一般行锁可以通过以下方式来实现:
对于Oracle数据库,可以采用select ... for update方式。这种方式会有潜在的危险,就是如果没有commit就会造成这行数据被锁住,其他有涉及到这行数据的任务都会被挂起,应该谨慎使用
在表里添加一个标示锁的字段,每次操作前,先通过update这个锁字段来完成类似竞争锁的操作,操作完成后在update锁字段复位,标示已归还锁。这种方式比较安全,不好的地方在于这些update锁字段的操作就是额外的性能消耗
统一触发途径
当一个数据可能会被多个触发点或多个业务涉及到,就有并发问题产生的隐患,因此可以通过前期架构和业务设计,尽量统一触发途径,触发途径少了一是减少并发的可能,也有利于对于并发问题的分析和判断。
如何保证高并发时线程安全?
对于商城一类系统中,单点登录、购物车、订单这些都有并发。
用AtomicInteger、synchronized、Lock、ThreadLocal等类来保证在代码层面上的线程安全;如果是功能上需要自主多线程处理,那么也会使用线程池ThreadPool来提高并发效率。
对高并发的处理会使用Redis的分布式锁(setnx),将对于服务器的承载力达到一定数量后,之后的请求全部加入队列处理。
负载均衡:在代码层级上对不同的业务进行读写分离;而数据库上进行集群和主从复制。在应用服务器上对应的对每个服务器都运用lvs+keepalive模式进行服务器集群;如果硬件资源足够的话那么可以对集群节点更加多和更加分散提高并发能力和系统稳定性。
Redis是一个开源,先进的key-value存储,并用于构建高性能,可扩展的Web应用程序的完美解决方案,是线程安全的。
Redis三个主要特点:
Redis数据库完全在内存中,使用磁盘仅用于持久性。
相比许多键值数据存储,Redis拥有一套较为丰富的数据类型(list,string,sort,set,hash)。
Redis可以将数据复制到任意数量的从服务器。
4、JVM相关问题
介绍下垃圾收集机制(在什么时候,对什么,做了什么)?
所谓“要回收的垃圾”无非就是那些不可能再被任何途径使用的对象,采用如下算法进行垃圾回收
引用计数法
给每个对象添加一个计数器,当有地方引用该对象时计数器加1,当引用失效时计数器减1。用对象计数器是否为0来判断对象是否可被回收。缺点:无法解决循环引用的问题。
可达性分析算法
通过GC ROOT
的对象作为搜索起始点,通过引用向下搜索,所走过的路径称为引用链。通过对象是否有到达引用链的路径来判断对象是否可被回收(可作为GC ROOT
的对象:虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中JNI
引用的对象)
通过可达性算法,成功解决了引用计数所无法解决的循环依赖问题,只要你无法与GC Root
建立直接或间接的连接,系统就会判定你为可回收对象。那这样就引申出了另一个问题,哪些属于GC Root。
JVM内存模型?
类加载的过程?
1. 什么是类加载
类加载就是将class文件加载入内存。j加载过程包括加载、验证、准备、解析、初始化
双亲委派模型?
双亲委派模型要求所有内加载器,处理顶层的BootStrp类加载之外,都要有自己的父类加载器。
tomcat、JDBC为什么打破双亲委派模型,需要相互隔离,不能一个链接/应用影响到其他应用。
有哪些类加载器?
能不能自己写一个类叫java.lang.String?
5、设计模式相关问题(必问)
设计模式比较常见的就是让你手写一个单例模式(注意单例模式的几种不同的实现方法)或者让你说一下某个常见的设计模式在你的项目中是如何使用的。
另外面试官还有可能问你抽象工厂和工厂方法模式的区别、工厂模式的思想这样的问题。
建议把代理模式、观察者模式、(抽象)工厂模式好好看一下,这三个设计模式很有用。
单例模式?
代理模式?
https://www.runoob.com/design-pattern/proxy-pattern.html
观察者模式?
https://www.runoob.com/design-pattern/observer-pattern.html
6、数据库相关问题,针对MySQL(必问)
给题目让你手写SQL。
有没有SQL优化经验,SQL怎么进行优化?
- 尽量避免使用select *,返回无用的字段会降低查询效率
- 使用连接(JOIN)来代替子查询(Sub-Queries)
- 使用联合(UNION)来代替手动创建的临时表
- 创建索引,优先考虑where、group by使用到的字段
- 避免索引失效的写法:包含有NULL值的列做索引,使用like全模糊匹配("%条件%")使用后半部分模糊匹配代替("条件%"),使用or条件,不使用NOT IN和<>操作,尽量避免在where条件中等号的左侧进行表达式、函数操作,。
- 避免多表关联查询
MySQL索引的数据结构?
SQL关键字的执行顺序?
有哪几种索引?
什么时候该(不该)建索引?
Explain包含哪些列,用来做什么?
7、框架相关问题
Hibernate和Mybatis的区别?
Spring MVC和Struts2的区别?
Spring用了哪些设计模式?
Spring中AOP主要用来做什么?
Spring注入bean的方式?
什么是IOC,什么是依赖注入,依赖注入实现原理?
Spring是单例还是多例,怎么修改?
Spring事务隔离级别和传播性?
介绍下Mybatis/Hibernate的缓存机制?
Mybatis的mapper文件中#和$的区别?
Mybatis的mapper文件中resultType和resultMap的区别?
8、其他问题
介绍下栈和队列?
IO和NIO的区别?
接口和抽象类的区别?
int和Integer的自动拆箱/装箱相关问题?
常量池相关问题?
==和equals的区别?
什么是JDK?什么是JRE?什么是JVM?三者之间的联系与区别?
Java和C++的区别?
重载和重写的区别?
String和StringBuilder、StringBuffer的区别?
静态变量、实例变量、局部变量线程安全吗,为什么?
try、catch、finally都有return语句时执行哪个?
介绍下B树、二叉树?
分布式锁的实现?
分布式session存储解决方案?
常用的linux命令?
三、其他经验分享
1)不要一开始就去面试自己最想去的公司,把面试当作一次技术的交流,面试的越多,经验越多,等面出了心得再去投理想的公司。
2)不熟悉的技术不要主动提。
3)如果没有明白面试官的问题,不要不懂装懂,可以礼貌地让对方重复一遍,也让自己多一点时间思考。
4)在面试的日子里,要保持每天学习,无论是学习新东西还是复习旧东西。
5)如果超过3-5天还没有得到结果,自己又很想去这家公司,可以主动联系HR询问面试结果,就算面试没有通过,也可以问问面试失败的原因,总结经验。