java面试题
2021-07-05 11:40:46 0 举报
AI智能生成
面试题
作者其他创作
大纲/内容
java基础
分支主题
JAVA基础
1、面向对象和面向对象的区别
面向对象:注重事情有哪些参与者、及各自需要做什么
封装
明确标识出外部使用的所有成员函数和数据项
继承
继承基类的方法,并作出自己的改变/拓展
多态
基于对象所属类的不同,外部对同一个方法的调用,实际执行逻辑不同
面向过程
注重事情的每一个步骤和顺序
2、JDK、JRE、JVM三者区别和联系
JDK
java开发工具
JRE
java运行时环境
JVM
java虚拟机
3、==和equals
==
基本类型对比的是变量值
引用类型对比的是堆中内存对象的地址
equals
Object默认采用==比较,通常要重写
String
1、先用==
2、判断是否为String类型
3、判断字符串长度是否相等
4、判断字符串值是否相等
4、final
修饰方法:表示方法不可被子类覆盖,但可以重载
修饰变量:表示变量一旦被赋值就不可以更改值
修饰成员变量
类变量:只能在静态初始化块中指定初始值或生命该类变量时指定初始值
成员变量:可以在非静态初始化块、生命该变量或者构造其中执行初始值
修饰局部变量
局部变量必须由程序员显示初始化。在使用final修饰局部变量时,在定义时就要指定默认值或在后面代码块中对final变量赋初值,但不允许第二次赋值
修饰基本类型数据
数值一旦在初始化之后便不能更改
修饰引用类型变量
在对其初始化之后便不能再让其指向另一个对象。但是引用的值是可变的
5、为什么局部内部类和匿名内部类只能访问局部final变量
内部类+外部类,编译后会生成两个class文件
内部类和外部类是同一个级别,内部类不会因为定义在方法中就会随着方法执行完毕就被销毁
外部类的局部变量会复制一份,作为内部类的成员变量,这样外部类方法结束后,对应的局部变量被销毁,但此时内部类仍可以访问此变量
要保持变量一致,故将此变量设置为final
6、String、StringBuffer、StringBuilder的区别及使用场景
区别
1、String由final修饰,不可变,每次操作都会产生新的String对象
2、StringBuffer和StringBuilder在源对象上操作
3、StringBuffer线程安全,StringBuilder线程不安全
4、StringBuffer方法都是synchronized修饰的
场景
性能:StringBuilder>StringBuffer>String
经常需要改变字符串时,不要用String
优先使用StringBuilder,多线程使用共享变量时使用StringBuffer
7、重载和重写的区别
重载
发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时
重写
发生在父子类中,方法名、参数列表必须相同,返回值范围<=父类,抛出的异常<=父类,访问修饰符范围>=父类
若父类方法使用private访问修饰符,则此方法不能被重写
8、接口或抽象类的区别
接口
对类的行为加以约束,可强制要求不同的类具有相同的行为,对如何实现没有进行限制
抽象类
代码复用,将某些具有相同行为的类抽象为一个抽象类并实现了B,避免让所有子类都来实现B,其他方法可由子类自己实现。抽象类不允许实例化出来
1、抽象类可以存在普通成员函数,而接口中只能存在public abstract方法
2、抽象类中的成员变量可以使各种类型的,而接口中的成员变量只能是public static final类型的
3、抽象类只能继承一个,接口可以实现多个
9、List和Set的区别
List
有序,按照对象进入的顺序保存对象,可重复,允许多个Null元素对象
可使用Iterator取出所有元素,再逐一遍历,也可使用get(index)获取指定下标的元素
Set
无序,不可重复,最多允许一个Null元素对象
取元素时只能用Iterator接口取得所有元素,再逐一遍历各个元素
10、hashCode与equals
hashCode
作用
获取哈希吗,也称为散列码;实际返回一个int值,确定在哈希表中的索引位置
hashCode()定义在Object中,java任何类中都包含有hashCode()函数
hashCode默认行为是对堆上的对象产生独特值,若不重写,该class的两个对象无论如何都不会相等(即使两个对象都指向相同的数据)
是否相等的情况
两个对象相等,hashCode一定相同
两个对象相等,对两个对象分别调用equals方法都返回true
两个对象有相同hashCode值,他们不一定相等
equals方法被覆盖时,hashCode方法也必须被覆盖(重写)
HashSet如何检查重复
先使用hashCode,若看位置是否有值
有值:使用equals判断是否相等
相等:元素冲突
不相等:重新散列到其他位置,大大减少了equals次数,提高了执行速度
无值:直接插入,对象没有重复出现
11、ArrayList和LinkedList的区别
ArrayList
基于动态数组,连续内存存储,是和下标访问
扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,将老数组的数据拷贝进新数组
若不是尾部插入数据,则会涉及到元素的移动
LinkedList
基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询
遍历时必须使用Iterator,不能使用for,因为for循环体内get(i)取得某一元素时,都需要对list重新遍历
不要试图使用indexOf等返回元素索引,并利用其进行遍历,若结果为空,则需要遍历整个列表
12、HashMap和HashTable的区别和底层原理
1、Hashmap方法没有synchronized修饰,线程非安全,HashTable线程安全
2、HashMap允许key和value为null,而HashTable不允许
底层实现
数组+链表
1、计算Key的hash值,二次hash然后对数组长度取模,对应到数组下标
2、如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组
3、如果产生hash冲突,先进行equals比较,相同则取代该元素,不同则判断链表高度插入链表
链表高度达到8,并且数组长度到64则转变为红黑树,红黑树长度低于6则转回链表
4、若key为null,存在下标0的位置
13、ConcurrentHashMap原理,jdk7和jdk8版本的区别
jdk7
数据结构
ReentranLock+Segment+HashEntry
一个Segment包含一个HashEntry数组,每个HashEntry又是一个链表结构
元素查询
二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在链表的头部
锁
Segment分段锁,Segment集成了ReentranLock,锁定操作的Segment,其他Segment不受影响,并发度为Segment个数,可在构造函数中指定,数组扩容不会影响到其他Segment
get方法无需加锁,volatile保证
jdk8
synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性
查找,替换,复制擦操作都是用CAS
锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高。扩容时,阻塞所有的读写操作、并发扩容
读操作无锁
Node的val和next使用volatile修饰,读写线程对改变量互相可见
数组用volatile修饰,保证扩容时被读线程感知
14、如何实现一个IOC容器
1、配置文件配置包扫描路径
2、递归包扫描获取.class文件
3、反射、确定需要交给IOC管理的类
4、对需要注入的类进行依赖注入
详细
配置文件中指定需要扫描包的路径
定义一些注解,分别表示访问控制吃呢哥、业务服务层、数据持久层、依赖注入注解、获取配置文件注解
从配置文件中获取需要扫描包的路径,获取当前路径下文件信息和文件夹信息,将当前路径下的.class文件添加到Set集合中
遍历Set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map来存储这些对象
遍历这个IOC容器,获取每一个类的实例,判断里面是否有依赖其他类的实例,然后进行递归注入
15、什么是字节码?采用字节码的好处是什么?
java中的编译器和解释器
机器和编译程序之间加入一层抽象的虚拟的机器,这台虚拟的机器在任何平台上都提供给编译程序一个共同的接口
编译程序只需要生成虚拟机能够理解的代码,然后由jvm的解释器转换为特定系统的机器码进行执行
好处
java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时保留了解释型语言可移植的特点
字节码并不专对一种特定机器,因此,java程序无需重新编译便可在不同的计算机上运行
16、java的异常体系
Throwable
Error
程序无法处理的错误,一旦出现,程序将被迫停止运行
Exception
RunTimeException(运行时异常)
导致当前线程执行失败
CheckedException(检查异常)
发生在程序编译过程中,会导致程序编译不通过
17、Java类加载器
BootStrapClassLoader
是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件
ExtClassLoader
是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件夹下的jar包和class类
AppClassLoader
是自定义类加载器的父类,负责加载classpath下的类文件。
系统类加载器,线程上下文加载器
继承ClassLoader实现自定义类加载器
18、双亲委派机制
向上委派
询问上一层是否加载该类,没有继续向上直至顶层
1、安全性,避免用户自己编写的类动态替换了Java的一些核心类,比如String
2、避免了类的重复加载,因为JVM中区分不同类,不仅仅根据类名,相同的class文件被不通的ClassLoader加载就是不同的两个类
19、GC如何判断对象可以被回收
方法
引用计数法
每个对象有一个引用计数属性,新增一个引用计数+1,引用释放时计数减1,计数为0时可以回收
可达性分析法
从GC Roots开始向下搜索,搜索走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明对象不可用
GC Roots对象
1、虚拟机栈(栈帧中的本地变量表)中引用的对象
2、方法区中类静态属性引用的对象
3、方法区中常量引用的对象
4、本地方法栈中JNI(即一般说的Native方法)引用的对象
死亡标记
可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。
对象被系统宣告死亡至少要经历两次标记过程
1、经过可达性分析发现没有与GC Roots相连接的引用链
2、在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法
当对象变成不可达时,GC会判断对象是否覆盖了finalize方法
未覆盖,则直接回收
覆盖,将对象放入F-Queue队列,由一个低优先级线程执行该队列中对象的finalize方法
可达,对象“复活”
不可达,则回收
java集合
HashMap
put
大体流程
1、根据key通过哈希算法与与运算得到数组下标
2、如果数组下标位置元素为空,则将key和value封装为Entry对象(jdk7是Entry对象,jdk8是Node对象)并放入该位置
3、如果数组下标不为空
1、先判断是否需要扩容
2、如果不用扩容就生成Entry对象,并使用头插法添加到当前位置的链表中
当前位置上的Node类型为红黑树
1、将key和value封装为一个红黑树节点并添加进红黑树
2、在过程中会判断红黑树中是否存在此key,存在的话更新value
当前位置上的Node类型为链表
1、将key和value封装为一个链表Node并通过尾插法插入到链表的最后位置去
2、尾插法要遍历链表,在遍历过程中会判断是否存在当前key,存在则更新value
3、遍历完成后,将链表新Node插入到链表中去,判断是否链表节点超过8
4、若超过8调用treeifyBin()方法,会判断数组长度是否大于64,若小于则执行resize操作,若大与则转换为红黑树
64的容量之前,因为容量比较小,在数据量比较多的时候,很容易出现hash冲突,直接扩容的效率比树化并不差,并且还可以减少后续元素落在同一个index的概率
插入链表或红黑树后,才会判断是否需要扩容
jdk7到jdk8的变化
1、jdk7底层是数组+链表,jdk8底层是数组+链表+红黑树,目的为了提高插入和查询的整体效率
2、jdk7链表使用头插法,jdk8链表使用尾插法,因为jdk8插入key和value需要判断链表元素个数,要遍历链表统计,整好直接使用尾插法
3、jdk7哈希算法比较复杂,存在各种右移和异或运算,jdk8做了简化,因为负载的哈希算法目的就是提高散列性,来提供HashMap的整体效率,而jdk8中新增了红黑树,可以适当简化哈希算法,节省CPU资源
线程、并发相关
1、线程的生命周期,有几种状态
线程的状态
1、创建
(NEW)新创建一个线程对象
2、就绪
(Runnable)线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权
3、运行
(Running)就绪状态的线程获取了CPU,执行程序代码
4、阻塞
(Blocked)阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
5、死亡
(Dead)线程执行完了或因一场退出了run方法,该线程结束生命周期
阻塞的情况
等待阻塞
运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法
同步阻塞
运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中
其他阻塞
运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法
2、sleep()、wait()、join()、yield()的区别
锁池
所有需要竞争同步锁的线程都会放在锁池当中
比如当前对象的锁被一个线程得到,则其他线程需要在锁池等待
当前线程释放同步锁后,锁池中的线程去竞争同步锁
当某个线程得到后同步锁后,就会进入就绪队列等待cpu资源分配
等待池
只有调用了notify()或notifyAll()后等待池才会开始去竞争锁
notify()是随机从等待池选出一个线程放到锁池
notifyAll()是将等待池的所有县城都放到锁池
1、sleep是Thread的静态本地方法,wait是Object的本地方法
2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中
3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字
4、sleep不需要被唤醒,但是wait需要
5、sleep一般用于当前线程休眠、或者轮循暂停操作,wait则多用于多线程之间的通信
6、sleep会让出CPU执行时间且强制上下文切换,而wait则不一定,wait后可能还有机会重新竞争到锁,然后继续执行
7、yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权,继续执行
3、对线程安全的理解
4、Thread、Runable的区别
5、对守护线程的理解
6、ThreadLocal原理和使用场景
说一下ThreadLocal
1、ThreadLocal是Java中所提供的的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻,任何方法中获取缓存的数据
2、ThreadLocal底层是通过ThreadLocalMap来实现,每个Thread对象都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
3、如果在线程中使用ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使用完成后,应该要把设置的key,value,Entry对象回收,但线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也不会被回收,从而出现内存泄漏
使用了ThreadLocal对象后,手动调用remove()方法清除Entry对象
4、ThreadLocal经典的应用场景就是连接管理
一个线程持有一个连接,该连接对象可以在不同的方法之间进行传递,线程之间不共享同一个连接
5、ThreadLocalMap的key是弱引用,gc后key为null,但value是强引用,所以导致内存泄漏
ThreadLocal和synchronized的区别
1、synchronized提供了同步机制,而ThreadLocal将共享变量变成线程私有
2、存储在jvm哪个区域
ThreadLocal存放在堆中,他属于对象
3、ThreadLocal真的只是当前线程可见吗?
ThreadLocal并不只是当前线程可见的,可以通过InheritableThreadLocal类可以实现多个线程访问ThreadLocal的值
4、ThreadLocal会导致内存泄漏吗
ThreadLocalMap.Entry.key弱引用,ThreadLocalMap.Entry.value强引用,弱引用被回收后为null,但是强引用不会被回收,导致Entry对象内存泄漏
5、为什么用Entry数组而不是Eentry对象
业务代码会new很多ThreadLocal对象,但一个线程中只有一个ThreadLocalMap
6、你学习的开源框架中哪些用到了ThreadLocal
Spring框架
DateTimeContextHolder
RequestContextHolder
7、ThreadLocal里的对象一定是线程安全的吗
未必,如果在每个线程中ThreadLocal.set()进去的东西本来就是多线程共享的同一个对象,比如static对象,那么多个线程的ThreadLocal.get()获取的还是这个共享对象本身,还是有并发访问线程不安全问题
7、ThreadLocal内存泄漏原因、如何避免
8、并发、并行、串行的区别
9、并发的三大特性
10、volatile
11、为什么用线程池?解释下线程池参数
12、简述线程池处理流程
13、线程池中阻塞队列的作用?为什么先添加队列而不是先创建最大线程?
14、如何查看线程死锁
1、可通过jstack进行查看,jstack命令中会显示发生了死锁的线程
2、有可能两个线程去操作数据库,数据库发生了死锁
1、查看是否锁表:show open tables where In_use>0;
2、查看进程 show processlist
3、查看正在锁的事务select * from INFORMATION_SCHEMA.INNOOB_LOCKS;
4、查看等待锁的事务select * from INFORMATION_SCHEMA.INNOOB_LOCK_WAITS;
15、线程之间如何进行通讯
1、线程之间可以通过共享内存或基于网络来进行通信
2、如果是通过共享内存来进行通信,则需要考虑并发问题,什么时候阻塞,什么时候唤醒
3、java中的wait()、notify()就是阻塞和唤醒
4、通过网络就比较简单,通过网络连接将通信数据发送给对方,当然也要考虑到并发问题,处理方式就是加锁等方式
1、JVM中哪些是共享区,哪些可以作为gc root
共享区
1、堆
TLAB(线程本地分配缓存区)
jvm一般会为每个线程在Eden区创建一块区域,由线程独享
2、方法区
线程隔离的数据区
1、虚拟机栈
2、本地方法栈
3、程序计数器
2、如何排查jvm问题
正常运行的系统
1、jmap来查看jvm各个区域的使用情况
2、jstack来查看线程的运行情况,比如那些线程阻塞,是否出现死锁
3、通过jstat查看垃圾回收情况
4、找到占用CPU最多的线程,定位到具体方法,优化方法执行
已经发生OOM的系统
1、通过生成的dump文件,使用工具进行分析
2、根据dump文件找到异常的实例对象和异常的线程,定位到具体代码
3、jdk7到jdk8 JVM发生了哪些变化
1、7存在永久代,8使用元数据空间替换了永久代
元数据空间占用本地内存空间,也就是服务器内存空间,不会影响到JVM内存
spring
1、如何实现一个IOC容器
2、spring是什么
3、谈谈你对AOP的理解
1、如何实现AOP
利用动态代理技术来实现AOP,当调用代理对象的某个方法时,可以任意控制该方法的执行,比如先打印执行时间,再执行该方法,然后再次打印执行时间
JDK动态代理
InvocationHandler
Cglib动态代理
2、项目中哪些地方用到了AOP
1、事务
2、权限控制
3、方法的执行日志
4、凡是需要对某些方法做统一处理的,都可以用到AOP实现,利用AOP可以做到业务无侵入
4、谈谈你对IOC的理解
5、BeanFactory和ApplicationContext有什么区别?
6、描述一下Spring Bean的生命周期
7、解释一下Spring支持的几种Bean作用域
8、Spring框架中的单例Bean是线程安全的吗
9、Spring框架中都用到了哪些设计模式
10、Spring事务的实现方式和原理以及隔离级别
11、spring事务传播机制
1、Spring事务底层是基于数据库事务和AOP机制的
2、首先对于使用@Transactional注解的Bean,Spring会创建一个代理对象作为Bean
3、当调用代理对象的方法时,会先判断该方法上是否加了@Transactional
4、如果加了,那么则利用事务管理器创建一个数据库连接
5、并且修改数据库连接的autocommit属性为false,禁止此连接的自动提交,这是实现Spring事务非常重要的一步
6、执行当前方法,方法中会执行sql
7、执行完当前方法后,如果没有出现异常,就直接提交事务
8、如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务
9、Spring事务的隔离级别对应的就是数据库的隔离级别
10、Spring事务的传播机制是Spring事务自己来实现的,也是Spring事务中最复杂的
11、Spring事务的传播机制是基于数据库连接来做的,一个数据库连接一个事务,如果传播机制配置需要新开一个事务,那么实际上就是先建立一个数据库连接,在此新数据库连接上执行sql
12、spring事务什么时候会失效
1、Spring事务是基于代理来实现的,所以对某个加了@Transactional的方法只有是被代理对象调用时,name这个注解才会生效,所以如果是被代理对象来调用这个方法,name@Transactional是不会生效
2、如果某个方法是private,那么@Transactional也会失效,因为底层cglib是基于父子类来实现,子类不能重载父类private方法,所以无法很好的利用代理,也会导致@Transactional失效
13、什么是bean的自动装配,有哪些方式
14、介绍一下Spring,读过源码介绍一下大致流程
1、Spring是一个快速开发框架,帮助程序员来管理对象
2、Spring源码实现很优秀,设计模式的应用、并发安全的实现、面向接口的设计等
3、创建Spring容器,也就是启动Spring时
1、首先会进行扫描,将所有BeanDefinition对象,并存在一个Map
2、然后筛选出费懒加载的单例BeanDefinition进行创建Bean,对于多例Bean不需要在启动过程中进行创建
3、利用BeanDefinition创建Bean就是Bean的创建生命周期,包括了合并DeanDefinition、推断构造方法、实例化、属性填充、初始化前、初始化、初始化后等步骤,其中AOP是发生在初始化后这一步
4、单例Bean创建完了之后,Spring会发布一个容器启动事件
5、Spring启动结束
6、在源码中会更复杂,比如源码会提供一些模板方法,让子类来实现。比如源码涉及到一些BeanFactoryPostProcessor和BeanPostProcessor的注册,Spring的扫描就是通过BeanFactoryPostProcessor来实现,依赖注入通过BeanPostProcessor实现
7、在Spring启动过程中还回处理@Import等注解
0 条评论
回复 删除
下一页