Java面试题整理

一、Java 基础

1.JDK 和 JRE 有什么区别?

jdk是Java开发工具,包括jre和编译运行工具
jre是Java程序的运行环境

2.== 和 equals 的区别是什么?

对于基本类型来说,只有==可以使用,用于比较数值是否相等
对于引用类型来说, ==比较引用地址,equals默认实现和等号一样,重写后根据重写规则比较两个对象是否相等。

3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

规则:重写equals方法必须重写hashcode

  • 两个对象相等,则hashcode必须相等
  • 两个对象不等,则hashcode可等可不等
  • 两对象hashcode相同(哈希冲突),对象可等可不等
  • 两对象hashcode不同,对象一定不相等
4.final 在 java 中有什么作用?
  • 修饰类,不能被继承
  • 修饰方法,不能被子类重写,private默认带final属性
  • 修饰变量,基本类型数值不可修改,引用类型地址不能变化
4.1 static在java中有什么作用?
  • 静态变量或类变量,在内存中只有一个拷贝,加载类的时候完成内存分配,可以用类名直接访问。
  • 静态方法,静态方法,只能访问静态变量和静态方法。
  • 静态代码块,加载类时按出现的先后顺序执行。
5.java 中的 Math.round(-1.5) 等于多少?

round()方法在实现四舍五入的时候实际上是不论正负的,它会先将这个数加上0.5然后在像下取整。-1.5+0.5 = -1.0,向下取整为-1.

6.String 属于基础的数据类型吗?

基础数据类型有:char、byte、short、int、long、float、double、boolean

7.java 中操作字符串都有哪些类?它们之间有什么区别?
  • String:不可变字符序列,字符串变化则需要生成新的String对象
  • StringBuilder:可变字符序列,效率高、线程不安全
  • StringBuffer:可变字符序列,效率低、线程安全
8.String str="abc"与 String str2=new String(“abc”)一样吗?
  • 前者在编译期查找常量池,找到或创建“abc”字符串,在栈中创建str名字,存储常量池中字符串地址。
  • 后者在编译阶段JVM先去常量池中查找是否存在“abc”,如果不存在,则在常量池中开辟一个空间存储“abc”。在运行时期,通过String类的构造器在堆内存中new了一个空间,然后将String池中的“abc”复制一份存放到该堆空间中,在栈中开辟名字为str2的空间,存放堆中new出来的这个String对象的地址值。
9.如何将字符串反转?
new StringBuilder(str).reverse().toString();
10.String 类的常用方法都有那些?
11.抽象类必须要有抽象方法吗?
  1. 如果一个类使用了abstract关键字修饰,那么这个类就是一个抽象类。
  2. 抽象类可以没有抽象方法
  3. 一个类如果包含抽象方法,那么这个类必须是抽象类,否则编译就会报错。
  4. 最关键的一点就是如果一个类是抽象类,那么这个类是不能被实例化的。
12.普通类和抽象类有哪些区别?
13.抽象类能使用 final 修饰吗?
  • 抽象类不能被实例化
  • 抽象类可以有抽象方法,抽象方法只需申明,无需实现
  • 含有抽象方法的类必须申明为抽象类
  • 抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类
  • 抽象方法不能被声明为静态
  • 抽象方法不能用private修饰
  • 抽象方法不能用final修饰
14.接口和抽象类有什么区别?

抽象类的实现是继承关系,是一个 "是不是"的关系,而接口实现则是 "有没有"的关系。比如狗是否能钻火圈,能则可以实现这个接口,不能就不实现这个接口。

15.java 中 IO 流分为几种?
  • 按照流的流向分,可以分为输入流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的角色划分为节点流和处理流。

InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

16.BIO、NIO、AIO 有什么区别?
  • BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。
  • NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。
  • AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
17.Files的常用方法都有哪些?

二、容器

18.java 容器都有哪些?

在这里插入图片描述

19.Collection 和 Collections 有什么区别?

1、java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
List,Set,Queue接口都继承Collection。
直接实现该接口的类只有AbstractCollection类,该类也只是一个抽象类,提供了对集合类操作的一些基本实现。List和Set的具体实现类基本上都直接或间接的继承了该类。
2、java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等),大多数方法都是用来处理线性表的。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

20.List、Set、Map 之间的区别是什么?
21.HashMap 和 Hashtable 有什么区别?

HashTable类和HashMap用法几乎一样,底层实现几乎一样,只不过HashTable的方法添加了synchronized关键字确保线程同步检查,效率较低。

  1. 版本不同:HashMap-JDK1.2 Hashtable-JDK1.0
  2. HashMap继承AbstractMap实现Map接口;Hashtable继承Dictionary实现Map接口
  3. HashMap允许key或value为null,但是key为null只允许有一个;Hashtable不予许出现null
  4. HashMap线程不同步;Hashtable线程同步
22.如何决定使用 HashMap 还是 TreeMap?

如果你需要得到一个有序的结果时就应该使用TreeMap(因为HashMap中元素的排列顺序是不固定的)。除此之外,由于HashMap有更好的性能,所以大多不需要排序的时候我们会使用HashMap。

23.说一下 HashMap 的实现原理?
  • HashMap的主干是HashMap.Entry数组,Entry是他的基本组成单元。
  • HashMap基于数组+链表+红黑树实现。
  • 数组的大小必须为2的n次方(为了快速取模)
  • 插入元素时,元素的hashcode对数组大小取模,数组index位如果有元素,则插入链表中(尾插),如果链表元素大于8个,转化为红黑树,小于6个时也会退化为链表。
  • 如果hashMap元素个数>阈值(当前容量*负载系数0.75),则会扩容,扩容后原来元素的索引要么不变,要么 新索引 = 原索引 + 原容量。
24.说一下 HashSet 的实现原理?

底层使用HashMap实现,存入元素作为HashMap的key。

25.ArrayList 和 LinkedList 的区别是什么?

ArrayList是动态数组实现,LinkedList是用双向链表实现。

26.如何实现数组和 List 之间的转换?
List<String> list = Arrays.asList(strs);

String[] array = list.toArray(new String[list.size()]);
int[] arr = list.stream().mapToInt(Integer::intValue).toArray();
27.ArrayList 和 Vector 的区别是什么?

1、Vector是线程安全的,ArrayList不是线程安全的。
2、ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。

关于线程安全,同样的类似关系的类还有HashMap和HashTable,StringBuilder和StringBuffer,后者是前者线程安全版本的实现。

28.Array 和 ArrayList 有何区别?

Array([])容量固定,确定后无法改变
ArrayList可以自动扩容

29.在 Queue 中 poll()和 remove()有什么区别?

queue中没有元素时,poll返回null,remove抛异常

30.哪些集合类是线程安全的?
  • Vector:就比Arraylist多了个同步化机制(线程安全)。
  • Hashtable:就比Hashmap多了个线程安全。
  • ConcurrentHashMap:是一种高效但是线程安全的集合。
  • Stack:栈,也是线程安全的,继承于Vector。
31.迭代器 Iterator 是什么?

首先说一下迭代器模式,它是 Java 中常用的设计模式之一。用于顺序访问集合对象的元素,无需知道集合对象的底层实现。
Iterator 是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历操作和底层实现,从而解耦。
缺点是增加新的集合类需要对应增加新的迭代器类,迭代器类与集合类成对增加。

32.Iterator 怎么使用?有什么特点?
33.Iterator 和 ListIterator 有什么区别?
  1. ListIterator有add()方法,可以向List中添加对象,而Iterator不能
  2. ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
  3. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
  4. 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
34.怎么确保一个集合不能被修改?

使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。

补充:红黑树

红黑树是一种自平衡的二叉查找树,Java的TreeMap和HashMap等都基于红黑树实现。

  1. 每个节点是黑色或者红色
  2. 根节点是黑色
  3. 每个叶子节点是黑色
  4. 如果一个节点是红色,他的子节点必须是黑色
  5. 从任意一个节点到叶子节点,经过的黑色节点数量相等
1.为什么要自平衡?

如果不平衡,二叉树可能是瘸腿的,退化成链表,复杂度达到O(n)。

2.为什么效率高?

从根到叶子最长深度不超过最短深度的两倍。所以高度h<=2log(n+1),查找时间复杂度O(logN)

3.为什么使用红黑树?

红黑树是近似平衡,并不是严格平衡,所以在维护平衡的成本上比AVL树要低。插入删除查找各种操作的性能相对比较稳定,所以工程上应用较多。

三、多线程

35.并行和并发有什么区别?
  • 并发的关键是你有处理多个任务的能力,但不是同时。
  • 并行的关键是你在同时处理多个任务。
36.线程和进程的区别?
  • 进程的执行过程是线状的,尽管中间会发生中断或暂停,但该进程所拥有的资源只为该线状执行过程服务。一旦发生进程上下文切换,这些资源都是要被保护起来的。
  • 线程的改变只代表了 CPU 执行过程的改变,而没有发生进程所拥有的资源变化。
  • 计算机内的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。
  • 进程拥有一个完整的虚拟地址空间,不依赖于线程而独立存在;反之,线程是进程的一部分,没有自己的地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。
  • 线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源。
37.守护线程是什么?

守护线程(即daemon thread),是个服务线程,比如负责垃圾回收的线程。

38.创建线程有哪几种方式?
  1. 创建Thread类,实现run方法,new一个Thread对象,调用其start()方法开始执行。
  2. 实现Runnable接口,实现其中的run方法,new一个Thread对象,把Runnable传进去,调用start()开始执行。
  3. 实现Callable接口的call方法,再new一个FutureTask实例,把Callable对象传进去。再new一个Thread对象,将Future实例传进去,调用start方法开始运行。
    此方法可以允许执行的方法有返回值,调用FutureTask的get即可得到返回值(阻塞等待)。
  4. 借助线程池创建
39.说一下 runnable 和 callable 有什么区别?
  • Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
  • Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
40.线程有哪些状态?
  1. 新生状态,new一个线程时,处于该状态
  2. 就绪状态,线程启动时,如start(),等待CPU服务,进入就绪态
  3. 运行状态,获得CPU资源后,处于运行状态
  4. 阻塞状态,join、yield、sleep 和Object的wait()方法等使得线程阻塞;阻塞解除后回到就绪状态
  5. 死亡状态,stop()/Destroy()方法,进入死亡状态的线程不能继续执行。
41.sleep() 和 wait() 有什么区别?
  • sleep()和yield()方法是定义在Thread类中,而wait()方法是定义在Object类中的
  • wait只能在同步(synchronize)环境中被调用,而sleep不需要。
  • 进入wait状态的线程能够被notify和notifyAll线程唤醒,但是进入sleeping状态的线程不能被notify方法唤醒。
  • wait通常有条件地执行,线程会一直处于wait状态,直到某个条件变为真。但是sleep仅仅让你的线程进入睡眠状态。
  • wait方法在进入wait状态的时候会释放对象的锁,但是sleep方法不会。
  • wait方法是针对一个被同步代码块加锁的对象,而sleep是针对一个线程
  • yield和sleep的主要是,yield方法会临时暂停当前正在执行的线程,来让有同样优先级的正在等待的线程有机会执行
42.notify()和 notifyAll()有什么区别?
  • 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
  • notifyAll会让所有处于等待池的线程全部进入锁池去竞争锁的机会
  • notify只会随机选取一个等待池的线程进入锁池去竞争获取锁的机会
43.线程的 run()和 start()有什么区别?

通过start()方法来启动的新线程,处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行相应线程的run()方法。

44.创建线程池有哪几种方式?
  • 通过ThreadPoolExecutor、ScheduledThreadPoolExecutor创建线程池
  • 通过Executors类提供的一系列方法创建线程池
  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
  2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
  4. Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)
44-1.ThreadPoolExecutor参数的含义

方法原型:

public ThreadPoolExecutor(int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory,
                           RejectedExecutionHandler handler) {
     // 省略...
 }
  1. corePoolSize
    核心线程数,线程池中始终存活的线程数
  2. maximumPoolSize
    最大线程数,线程池中允许存在的最大线程数量
  3. keepAliveTime
    最大线程数存活的时间,当线程中没有任务执行时,线程池会销毁一部分线程,最终保持在核心线程数
  4. unit
    时间单位,keepAliveTime配合使用,TimeUnit枚举值
  5. workQueue
    线程池的任务队列,通过线程池execute方法提供的Runnable对象会存储在这个队列中。有以下几种:
  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
  • SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
  • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  • DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
  1. threadFactory
    线程工厂,为线程池提供创建新线程的功能,ThreadFactory是一个接口,只有newThread一个方法,默认使用Executors.defaultThreadFactory()
  2. handler
    拒绝策略,当线程池无法执行新的任务时,可能是因为任务队列已满或其他原因执行失败,导致拒绝执行新的任务,这时候就会调用handler的rejectedExecution方法通知调用者。
    继承RejectedExecutionHandler方法的类有四种:
  • AbortPolicy:拒绝并抛出异常。
  • CallerRunsPolicy:使用当前调用的线程来执行此任务。
  • DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
  • DiscardPolicy:忽略并抛弃当前任务。
    默认使用AbortPolicy
44-2.线程池的处理流程

执行ThreadPoolExcutor的execute方法,可能会遇到以下情况:

  1. 如果线程池中的线程数未达到核心线程数,则创建核心线程处理任务。
  2. 如果线程数大于或者等于核心线程数,则将任务加入任务队列中,线程池中的空闲线程会不断的从任务队列中取出任务进行处理。
  3. 如果任务队列满了,并且线程数没有达到最大线程数,则创建非核心线程去处理任务。
  4. 如果线程数超过了最大线程数,则执行上面提到的几种饱和策略
44-3.如何配置线程池?
  1. CPU密集型任务:尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。
  2. IO密集型任务:可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。
45.线程池都有哪些状态?

线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated。

  1. RUNNING
    (1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
    (2) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!
  2. SHUTDOWN
    (1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
    (2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
  3. STOP
    (1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
    (2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
  4. TIDYING
    (1) 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
    (2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
    当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
  5. TERMINATED
    (1) 状态说明:线程池彻底终止,就变成TERMINATED状态。
    (2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
46.线程池中 submit()和 execute()方法有什么区别?
  • execute方法在Executor接口中定义,更基础,只能接收Runnable参数;
  • submit方法在ExecutorService中定义,submit有三个重载方法,分别可接收Runnable,Callable参数,底层都转化成RunnableFuture参数传给execute方法执行,执行结束后可以有返回值,通过Future#get方法获取,get方法会阻塞直到任务执行结束
47-0.JMM是什么

JMM是Java内存模型,是一种虚拟机规范,规范了虚拟机与计算机内存如何协同工作,用于屏蔽各种硬件和操作系统的内存访问差异,让java程序在各种平台上都达到一致的并发效果。

47.在 java 程序中怎么保证多线程的运行安全?

线程安全问题体现在:

  • 原子性:一个或多个操作在CPU执行过程中不被中断。线程切换会导致原子性问题。Java Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题。

  • 可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。缓存会导致可见性问题。synchronized、LOCK、volatile,可以解决可见性问题。

  • 有序性:程序执行顺序按照代码的先后顺序执行。编译优化会导致有序性问题。Happens-Before规则可以解决有序性问题。Happens-before是JMM面向程序设计者提供的规则保证。

48.多线程锁的升级原理是什么?

锁的升级过程:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

synchronized用的锁存在对象头里,对象头有threadid字段。

  • 第一次访问时字段为空,jvm使其持有偏向锁,并将threadid设置为线程id
  • 再次进入时会判断threadid是否与其线程id一致,一致则直接使用,如果不一致则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁
  • 如果执行一段时间后还没有获取到对象,或者这时候又有第三个线程来竞争锁,则将轻量级锁升级为重量级锁
49.什么是死锁?

如果一组进程中的每个进程都在等待一个事件,而这个事件只能由该组中的另一个进程触发,导致所有进程都无限期等下去,这种情况会导致死锁。
举例:当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。

50.怎么防止死锁?

资源死锁可能出现的情况主要有:

  1. 互斥条件:每个资源都被分配给了一个进程或者资源是可用的
  2. 保持和等待条件:已经获取资源的进程被认为能够获取新的资源
  3. 不可抢占条件:分配给一个进程的资源不能强制的从其他进程抢占资源,它只能由占有它的进程显示释放
  4. 循环等待:死锁发生时,系统中一定有两个或者两个以上的进程组成一个循环,循环中的每个进程都在等待下一个进程释放的资源。

总结方法:在并发程序中,避免了逻辑中出现复数个线程互相持有对方线程所需要的独占锁的的情况,就可以避免死锁。

51.ThreadLocal 是什么?有哪些使用场景?

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。

如上文所述,ThreadLocal 适用于如下两种场景:

  • 每个线程需要有自己单独的实例
  • 实例需要在多个方法中共享,但不希望被多线程共享
    对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLocal 可以以非常方便的形式满足该需求。
    对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。
52.说一下 synchronized 底层实现原理?

synchronized关键字最主要有以下3种应用方式,下面分别介绍

  • 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
  • 修饰静态方法,作用于当前类class对象加锁,进入同步代码前要获得当前类对象的锁
  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁

同步方法通过ACC_SYNCHRONIZED关键字隐式的对方法进行加锁。当线程要执行的方法被标注上ACC_SYNCHRONIZED时,需要先获得锁才能执行该方法。

同步代码块通过monitorenter和monitorexit执行来进行加锁。当线程执行到monitorenter的时候要先获得所锁,才能执行后面的方法。当线程执行到monitorexit的时候则要释放锁。

底层是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。

53.synchronized 和 volatile 的区别是什么?
  • volatile不需要加锁,比synchronized更轻量级,不会阻塞线程;
  • 从内存可见性角度,volatile读相当于加锁,volatile写相当于解锁;
  • synchronized既能够保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性。
54.synchronized 和 Lock 有什么区别?

两者都是锁,用来控制并发冲突,区别在于Lock是个接口,提供的功能更加丰富,除此之外,他们还有如下区别:

  • synchronize自动释放锁,而Lock必须手动释放,并且代码中出现异常会导致unlock代码不执行,所以Lock一般在Finally中释放,而synchronize释放锁是由JVM自动执行的。
  • Lock有共享锁的概念,所以可以设置读写锁提高效率,synchronize不能。(两者都可重入)
  • Lock可以让线程在获取锁的过程中响应中断,而synchronize不会,线程会一直等待下去。lock.lockInterruptibly()方法会优先响应中断,而不是像lock一样优先去获取锁。
  • Lock锁的是代码块,synchronize还能锁方法和类。
55.synchronized 和 ReentrantLock 区别是什么?

ReentrantLock实现了Lock接口

56.说一下 atomic 的原理?

Atomic类是通过无锁(lock-free)的方式实现的线程安全(thread-safe)访问。它的主要原理是利用了CAS:Compare and Set。
CAS是指,在这个操作中,如果AtomicInteger的当前值是prev,那么就更新为next,返回true。如果AtomicInteger的当前值不是prev,就什么也不干,返回false。通过CAS操作并配合do … while循环,即使其他线程修改了AtomicInteger的值,最终的结果也是正确的。

Atomic类中,值是存在一个volatile的int里面。volatile只能保证这个变量的可见性。不能保证他的原子性。如何保证原子性:自旋 +CAS(乐观锁)。在这个过程中,通过compareAndSwapInt比较更新value值,如果更新失败,重新获取旧值,然后更新。

四、反射

57.什么是反射?

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

58.什么是 java 序列化?什么情况下需要序列化?
59.动态代理是什么?有哪些应用?
60.怎么实现动态代理?

五、对象拷贝

61.为什么要使用克隆?
62.如何实现对象克隆?
63.深拷贝和浅拷贝区别是什么?

浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

六、Java Web

64.jsp 和 servlet 有什么区别?

65.jsp 有哪些内置对象?作用分别是什么?

66.说一下 jsp 的 4 种作用域?

67.session 和 cookie 有什么区别?

68.说一下 session 的工作原理?

69.如果客户端禁止 cookie 能实现 session 还能用吗?

70.spring mvc 和 struts 的区别是什么?

71.如何避免 sql 注入?

72.什么是 XSS 攻击,如何避免?

73.什么是 CSRF 攻击,如何避免?

七、异常

74.throw 和 throws 的区别?
75.final、finally、finalize 有什么区别?
76.try-catch-finally 中哪个部分可以省略?
77.try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
78.常见的异常类有哪些?

八、网络

79.http 响应码 301 和 302 代表的是什么?有什么区别?
80.forward 和 redirect 的区别?
81.简述 tcp 和 udp的区别?
82.tcp 为什么要三次握手,两次不行吗?为什么?
83.说一下 tcp 粘包是怎么产生的?
84.OSI 的七层模型都有哪些?
85.get 和 post 请求有哪些区别?
86.如何实现跨域?
87.说一下 JSONP 实现原理?

九、设计模式

88.说一下你熟悉的设计模式?
89.简单工厂和抽象工厂有什么区别?

十、Spring/Spring MVC

90.为什么要使用 spring?

91.解释一下什么是 aop?

92.解释一下什么是 ioc?

93.spring 有哪些主要模块?

94.spring 常用的注入方式有哪些?

95.spring 中的 bean 是线程安全的吗?

96.spring 支持几种 bean 的作用域?

97.spring 自动装配 bean 有哪些方式?

98.spring 事务实现方式有哪些?

99.说一下 spring 的事务隔离?

100.说一下 spring mvc 运行流程?

101.spring mvc 有哪些组件?

102.@RequestMapping 的作用是什么?

103.@Autowired 的作用是什么?

十一、Spring Boot/Spring Cloud

104.什么是 spring boot?

105.为什么要用 spring boot?

106.spring boot 核心配置文件是什么?

107.spring boot 配置文件有哪几种类型?它们有什么区别?

108.spring boot 有哪些方式可以实现热部署?

109.jpa 和 hibernate 有什么区别?

110.什么是 spring cloud?

111.spring cloud 断路器的作用是什么?

112.spring cloud 的核心组件有哪些?

十二、Hibernate

113.为什么要使用 hibernate?

114.什么是 ORM 框架?

115.hibernate 中如何在控制台查看打印的 sql 语句?

116.hibernate 有几种查询方式?

117.hibernate 实体类可以被定义为 final 吗?

118.在 hibernate 中使用 Integer 和 int 做映射有什么区别?

119.hibernate 是如何工作的?

120.get()和 load()的区别?

121.说一下 hibernate 的缓存机制?

122.hibernate 对象有哪些状态?

123.在 hibernate 中 getCurrentSession 和 openSession 的区别是什么?

124.hibernate 实体类必须要有无参构造函数吗?为什么?

十三、Mybatis

125.mybatis 中 #{}和 ${}的区别是什么?

126.mybatis 有几种分页方式?

127.RowBounds 是一次性查询全部结果吗?为什么?

128.mybatis 逻辑分页和物理分页的区别是什么?

129.mybatis 是否支持延迟加载?延迟加载的原理是什么?

130.说一下 mybatis 的一级缓存和二级缓存?

131.mybatis 和 hibernate 的区别有哪些?

132.mybatis 有哪些执行器(Executor)?

133.mybatis 分页插件的实现原理是什么?

134.mybatis 如何编写一个自定义插件?

十四、RabbitMQ

135.rabbitmq 的使用场景有哪些?

136.rabbitmq 有哪些重要的角色?

137.rabbitmq 有哪些重要的组件?

138.rabbitmq 中 vhost 的作用是什么?

139.rabbitmq 的消息是怎么发送的?

140.rabbitmq 怎么保证消息的稳定性?

141.rabbitmq 怎么避免消息丢失?

142.要保证消息持久化成功的条件有哪些?

143.rabbitmq 持久化有什么缺点?

144.rabbitmq 有几种广播类型?

145.rabbitmq 怎么实现延迟消息队列?

146.rabbitmq 集群有什么用?

147.rabbitmq 节点的类型有哪些?

148.rabbitmq 集群搭建需要注意哪些问题?

149.rabbitmq 每个节点是其他节点的完整拷贝吗?为什么?

150.rabbitmq 集群中唯一一个磁盘节点崩溃了会发生什么情况?

151.rabbitmq 对集群节点停止顺序有要求吗?

十五、Kafka

152.kafka 可以脱离 zookeeper 单独使用吗?为什么?

153.kafka 有几种数据保留的策略?

154.kafka 同时设置了 7 天和 10G 清除数据,到第五天的时候消息达到了 10G,这个时候 kafka 将如何处理?

155.什么情况会导致 kafka 运行变慢?

156.使用 kafka 集群需要注意什么?

十六、Zookeeper

157.zookeeper 是什么?

158.zookeeper 都有哪些功能?

159.zookeeper 有几种部署模式?

160.zookeeper 怎么保证主从节点的状态同步?

161.集群中为什么要有主节点?

162.集群中有 3 台服务器,其中一个节点宕机,这个时候 zookeeper 还可以使用吗?

163.说一下 zookeeper 的通知机制?

十七、MySql

164.数据库的三范式是什么?

165.一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几?

166.如何获取当前数据库版本?

167.说一下 ACID 是什么?

168.char 和 varchar 的区别是什么?

169.float 和 double 的区别是什么?

170.mysql 的内连接、左连接、右连接有什么区别?

171.mysql 索引是怎么实现的?

172.怎么验证 mysql 的索引是否满足需求?

173.说一下数据库的事务隔离?

174.说一下 mysql 常用的引擎?

175.说一下 mysql 的行锁和表锁?

176.说一下乐观锁和悲观锁?

177.mysql 问题排查都有哪些手段?

178.如何做 mysql 的性能优化?

十八、Redis

179.redis 是什么?都有哪些使用场景?

180.redis 有哪些功能?

181.redis 和 memecache 有什么区别?

182.redis 为什么是单线程的?

183.什么是缓存穿透?怎么解决?

184.redis 支持的数据类型有哪些?

185.redis 支持的 java 客户端都有哪些?

186.jedis 和 redisson 有哪些区别?

187.怎么保证缓存和数据库数据的一致性?

188.redis 持久化有几种方式?

189.redis 怎么实现分布式锁?

190.redis 分布式锁有什么缺陷?

191.redis 如何做内存优化?

192.redis 淘汰策略有哪些?

193.redis 常见的性能问题有哪些?该如何解决?

十九、JVM

194.说一下 jvm 的主要组成部分?及其作用?

JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。
在这里插入图片描述

  • Class loader(类装载)
    根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。
  • Execution engine(执行引擎)
    执行classes中的指令。
  • Native Interface(本地接口)
    与native libraries交互,是其它编程语言交互的接口。
  • Runtime data area(运行时数据区域)
    这就是我们常说的JVM的内存。

首先通过编译器把 Java 代码转换成字节码类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

195.说一下 jvm 运行时数据区?

在这里插入图片描述

  • 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;
  • Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
  • 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
  • Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
  • 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
196.说一下堆栈的区别?

堆是全局的,静态和全局变量,new 得到的变量,都放在堆中;
栈是在每个方法进入的时候分配一小块,由系统自动分配,存储局部变量、方法参数等

197.队列和栈是什么?有什么区别?

队列:先进先出
栈:后进先出

198.什么是双亲委派模型?

JDK主要有3个自带ClassLoader:

  • 最基础:Bootstrap ClassLoader(加载JDK的/lib目录下的类)
  • 次基础:Extension ClassLoader(加载JDK的/lib/ext目录下的类)
  • 普通:Application ClassLoader(程序自己classpath下的类)

双亲委派模型要求如果一个类可以被委派最基础的ClassLoader加载,就不能让高层的ClassLoader加载。这样你就知道你用的String类一定是被BootstrapClasserLoader加载的/lib下的那个rt.jar的那个java/lang/String.class.

例外:为什么说 Java SPI 的设计会违反双亲委派原则呢?

首先双亲委派原则本身并非 JVM 强制模型。
SPI 的调用方和接口定义方很可能都在 Java 的核心类库之中,而实现类交由开发者实现,然而实现类并不会被启动类加载器所加载,基于双亲委派的可见性原则,SPI 调用方无法拿到实现类。SPI Serviceloader 通过线程上下文获取能够加载实现类的classloader,一般情况下是 application classloader,绕过了这层限制,逻辑上打破了双亲委派原则。

199.说一下类加载的执行过程?

类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。
类加载过程

  • 加载
    加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。
  • 验证
    主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。
  • 准备
    主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值,比如8种基本类型的初值,默认为0;引用类型的初值则为null;常量的初值即为代码中设置的值
  • 解析
    将常量池内的符号引用替换为直接引用的过程。比如调用hello()方法,将符号hello替换为方法地址0x12345678的过程。
  • 初始化
    对类变量初始化,是执行类构造器的过程。只对static修饰的变量或语句进行初始化。
    如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
    如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
200.怎么判断对象是否可以被回收?
  1. 引用计数器
    为每个对象创建一个引用计数,当有对象引用时,计数器+1,当引用释放时,计数器-1,所以,当计数器为0时,就认为可以被回收。但是此算法存在循环引用的问题,当前JVM已经不使用此算法。
  2. 可达性分析
    从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链时,则认为此对象可以被回收。

哪些对象可以作为GC Roots?

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(native方法,调用C++代码)引用的对象
201.java 中都有哪些引用类型?

Java中有四种引用类型,引用强度由强到弱:强引用、软引用、弱引用、虚引用。针对不同的引用类型,GC的回收策略不同。

  • 强引用
    通过关键字new的对象就是强引用对象,强引用指向的对象任何时候都不会被回收,宁愿OOM也不会回收。
  • 软引用
    如果一个对象持有软引用,那么当JVM堆空间不足时,会被回收。一个类的软引用可以通过java.lang.ref.SoftReference持有。
  • 弱引用
    如果一个对象持有弱引用,那么在GC时,只要发现弱引用对象,就会被回收。一个类的弱引用可以通过java.lang.ref.WeakReference持有。
  • 虚引用
    几乎和没有一样,随时可以被回收。通过PhantomReference持有。
202.说一下 jvm 有哪些垃圾回收算法?
  1. 复制算法
    将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半。
    优点:简单高效,不会出现内存碎片
    缺点:内存利用率低,存活对象较多时需要复制的次数多,效率明显降低

  2. 标记-清除算法
    首先标记所有需要回收的对象,然后统一回收被标记的对象
    优点:利用率100%
    缺点:1、标记和清除效率都不高(对比复制算法)2、会产生大量不连续的内存碎片

  3. 标记-整理算法
    首先标记出所有需要回收的对象,在标记完成后,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端,边界以外的内存。
    优点:内存利用率100%,没有内存碎片
    缺点:标记和清除效率都不高(对比复制算法及标记-清除算法)

203.说一下 jvm 有哪些垃圾回收器?
204.详细介绍一下 CMS 垃圾回收器?
205.新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

新生代回收器:Serial、ParNew、Parallel Scavenge
老年代回收器:Serial Old、Parallel Old、CMS
整堆回收器:G1

  1. Serial
    新生代单线程收集器,优点是简单高效,Client模式下默认的新生代收集器

  2. ParNew
    新生代并行收集器,Serial收集器的多线程版本

  3. Parallel Scavenge
    新生代并行收集器,追求高吞吐量,高效利用 CPU。

  4. Serial Old
    Serial收集器的老年代版本

  5. Parallel Old
    Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法

  6. CMS
    CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
    基于标记-清除算法实现,过程分为4步:
    (1)初始标记
    仅仅是标记 GC Roots 内直接关联的对象,这个阶段速度很快
    (2)并发标记
    进行的是 GC Tracing,从 GC Roots 开始对堆进行可达性分析,找出存活对象。
    (3)重新标记
    为了 修正 并发期间由于 用户进行运作 导致的 标记变动 的那一部分对象的 标记记录。这个阶段的 停顿时间 一般会比 初始标记阶段 稍长一些,但远比 并发标记 的时间短
    (4)并发清除
    并发清除 阶段会清除垃圾对象。
    优点:并发收集、低停顿
    缺点:1)CMS回收器对CPU资源非常依赖 2)CMS回收器无法清除浮动垃圾 3)垃圾收集结束后残余大量空间碎片,需要配合内存整理策略优化

  7. G1
    标记-整理垃圾收集器,JDK1.7后全新的收集器,取代CMS。
    G1收集器的特点:
    (1)并行与并发
    使用多个 CPU 来缩短 Stop-the-World 的 停顿时间,部分其他回收器需要停顿 Java 线程执行的 GC 动作,G1 回收器仍然可以通过 并发的方式 让 Java 程序继续执行。
    (2)分代回收
    与其他回收器一样,分代概念 在 G1 中依然得以保留。虽然 G1 可以不需要 其他回收器配合 就能独立管理 整个GC堆,但它能够采用 不同的策略 去处理 新创建的对象 和 已经存活 一段时间、熬过多次 GC 的旧对象,以获取更好的回收效果。新生代 和 老年代 不再是 物理隔离,是多个 大小相等 的独立 Region。
    (3)空间整合
    与 CMS 的 标记—清理 算法不同,G1 从 整体 来看是基于 标记—整理 算法实现的回收器。从 局部(两个 Region 之间)上来看是基于 复制算法 实现的。
    但无论如何,这 两种算法 都意味着 G1 运作期间 不会产生内存空间碎片,回收后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象 时不会因为无法找到 连续内存空间 而提前触发 下一次 GC。
    (4)可预测的停顿
    这是 G1 相对于 CMS 的另一大优势,降低停顿时间 是 G1 和 CMS 共同的关注点。G1 除了追求 低停顿 外,还能建立 可预测 的 停顿时间模型,能让使用者明确指定在一个 长度 为 M 毫秒的 时间片段 内,消耗在 垃圾回收 上的时间不得超过 N 毫秒。(后台维护的 优先列表,优先回收 价值大 的 Region)。

206.简述分代垃圾回收器是怎么工作的?

分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:
(1)把 Eden + From Survivor 存活的对象放入 To Survivor 区;
(2)清空 Eden 和 From Survivor 分区;
(3)From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。

207.说一下 jvm 调优的工具?
208.常用的 jvm 调优的参数都有哪些?
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值