JAVA面试宝典(纯享版)

一、Java基础

1、Java开发平台都有什么

Ee:web
Se:客户端
Me:嵌入式应用开发

2、Jdk和jre的区别

Jdk是开发人员使用
Jre是java程序运行平台

3、list、set、map的区别

List和set是conllection的子类
Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、 List、Queue三种子接口。
我们比较常用的是Set、List,Map接口不是 collection的子接口。
List 有序,可以存多个null,arraylist和Vector是数组,linkedlist双向循环链表
性能:ArrayList 在性能方面要优于 Vector。
扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。
Set 无序,使用hashcode计算后存储,只有一个null,
Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不 要求有序,允许重复。Hashmap,treemap,linkedhashmap,hashtable,ConcurrentHashMap
JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主 体,链表则是主要
为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转 化为红黑树,以减少搜索时间,扩容因子0.75,每次扩当前1倍
HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
HashMap是使用了哪些方法来有效解决哈希冲突的:

  1. 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
  2. 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
  3. 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;
    ConcurrentHashMap: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组
    +链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑
    ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用Node 数组+链表+红黑树的数据结构来实现,并发控制使用synchronized 和 CAS 来操作。
    WeakHashMap
    强引用:
    如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。 比如String str = "hello"这时候str就是一个强引用。
    软引用:
    内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。
    弱引用:
    如果一个对象具有弱引用,在垃圾回收时候,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。
    虚引用:
    如果一个对象具有虚引用,就相当于没有引用,在任何时候都有可能被回收。 使用虚引用的目的就是为了得知对象被GC的时机,所以可以利用虚引用来进行销毁前的一些操作,比如说资源释放等。

4、Java创建对象有几种方式?

java中提供了以下四种创建对象的方式:

  1. new创建新对象
  2. 通过反射机制
  3. 采用clone机制
  4. 通过序列化机制

5、JDBC操作的步骤

加载数据库驱动类
打开数据库连接
执行sql语句
处理返回结果
关闭资源

6、在使用jdbc的时候,如何防止出现sql注入的问题。

使用PreparedStatement类,而不是使用Statement类

7、怎么在JDBC内调用一个存储过程

使用CallableStatement

8、是否了解连接池,使用连接池有什么好处?

提效,与线程池一样,避免重复造轮子,就是避免每次去创建链接,直接从连接池拿链接即可。

9、你所了解的数据源技术有那些?使用数据源有什么好处?

Dbcp,c3p0等,用的最多还是c3p0,因为c3p0比dbcp更加稳定,安全;通过配置文件的形式来维护数据库信息,而不是通过硬编码。当连接的数据库信息发生改变时,不需要再更改程序代码就实现了数据库信息的更新。
druid的功能比较全面,且扩展性较好,比较方便对jdbc接口进行监控跟踪等。
c3p0历史悠久,代码及其复杂,不利于维护。
我们现在代码里面使用的都是德鲁伊,这个是阿里开源的连接池,相较于前面两种效率更高,然后springboot自己现在集成了一个连接池,据说效率更高,但是这个没有研究。

10、char 型变量中能不能存贮一个中文汉字,为什么?

char 类型可以存储一个中文汉字,因为 Java 中使用的编码是 Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统
一的唯一方法),一个 char 类型占 2 个字节(16 比特),所以放一个中文是没问题的。

11、面向对象五大基本原则是什么(可选)

单一职责原则SRP(Single Responsibility Principle)类的功能要单一,不能包罗万象,跟杂货铺似的。
开放封闭原则OCP(Open-Close Principle) 一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
里式替换原则LSP(the Liskov Substitution Principle LSP)子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
依赖倒置原则DIP(the Dependency Inversion Principle DIP)高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。
接口分离原则ISP(the Interface Segregation Principle ISP)设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。

12、BIO,NIO,AIO 有什么区别?

BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO,异步 IO 的操作基于事件和回调机制。

13、异常分类:

Throwable 是 Java 语言中所有错误或异常的超类。下一层分为 Error 和 Exception
RuntimeException 如 : NullPointerException 、 ClassCastException ;一个是检查异常CheckedException,如 I/O 错误导致的 IOException、SQLException
检查异常 CheckedException:一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch

14、反射

Class.forname

15、JAVA 注解

Annotation(注解)
@Target 修饰的对象范围
@Retention 定义 被保留的时间长短
n SOURCE:在源文件中有效(即源文件保留)
n CLASS:在 class 文件中有效(即 class 保留)
n RUNTIME:在运行时有效(即运行时保留)
@Documented 描述-javadoc
@Inherited 阐述了某个被标注的类型是被继承的

16、泛型

  1. 表示该通配符所代表的类型是 T 类型的子类。
  2. 表示该通配符所代表的类型是 T 类型的父类。

17、设计模式

创建型模式(Creational Patterns)
单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点。
工厂方法模式(Factory Method):定义一个用于创建对象的接口,让子类决定实例化哪一个类。
抽象工厂模式(Abstract Factory):创建相关或依赖对象的家族,而不需明确指定具体类。
建造者模式(Builder):将一个复杂对象的构建与它的表示分离,允许按步骤构建对象。
原型模式(Prototype):通过拷贝现有的实例创建新的实例,而不是通过新建。
结构型模式(Structural Patterns)
适配器模式(Adapter):允许对象间的接口不兼容问题,通过包装一个存在的对象。
桥接模式(Bridge):将抽象部分与实现部分分离,使它们可以独立变化。
组合模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。
装饰器模式(Decorator):动态地给一个对象添加一些额外的职责。
外观模式(Facade):为子系统中的一组接口提供一个统一的高层接口。
享元模式(Flyweight):通过共享来高效地支持大量细粒度的对象。
代理模式(Proxy):为其他对象提供一个代理以控制对这个对象的访问。
行为型模式(Behavioral Patterns)
责任链模式(Chain of Responsibility):使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。
命令模式(Command):将请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化。
解释器模式(Interpreter):定义如何评估语言的文法。
迭代器模式(Iterator):顺序访问一个聚合对象中的各个元素,不暴露其内部的表示。
中介者模式(Mediator):用一个中介对象来封装一系列的对象交互。
备忘录模式(Memento):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
观察者模式(Observer):对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
状态模式(State):允许一个对象在其内部状态改变时改变它的行为。
策略模式(Strategy):定义一系列算法,把它们一个个封装起来,并使它们可互换。
模板方法模式(Template Method):在一个方法中定义一个算法的框架,而将一些步骤延迟到子类中实现。
访问者模式(Visitor):为一个对象结构(如组合结构)增加新能力

二、多线程

1、生命周期

JAVA面试宝典(纯享版)_第1张图片

2、创建

继承Thread类;
实现Runnable接口;
实现Callable接口通过FutureTask包装器来创建Thread线程;
使用ExecutorService、Callable、Future实现有返回结果的多线程(也就是使用ExecutorService来管理前面的三种方式)。

3、线程池

  • corePoolSize:线程池核心线程数量
  • maximumPoolSize:线程池最大线程数量
  • keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
  • unit:存活时间的单位
  • workQueue:存放任务的队列
  • handler:超出线程范围和队列容量的任务的处理程序
  1. SingleThreadExecutor:单线程线程池,里面的核心线程数和线程数都是1,并且工作队列使用的是无界队列。由于是单线程工作,每次只能处理一个任务,所以后面所有的任务都被阻塞在工作队列中,只能一个个任务执行。
  2. FixedThreadExecutor:固定大小线程池,这个与单线程类似,只是创建了固定大小的线程数量。
  3. CachedThreadPool:无界线程池,没有工作队列,任务进来就执行,线程数量不够就创建,与前面两个的区别是:空闲的线程会被回收掉,空闲的时间是60s。这个适用于执行很多短期异步的小程序或者负载较轻的服务器。
  4. ScheduledThreadPool:给定延迟后运行命令或者定期地执行
  5. LinkedBlockingQuene作为阻塞队列,当线程池没有可执行任务时,也不会释放线程。
    由于LinkedBlockingQuene的特性,这个队列是无界的,若消费不过来,会导致内存被任务队列占满,最终oom;

线程池大小分配
线程池究竟设置多大要看你的线程池执行的什么任务了,CPU密集型、IO密集型、混合型,任 务类型不同,设置的方式也不一样。
任务一般分为:CPU密集型、IO密集型、混合型,对于不同类型的任务需要分配不同大小的线 程池。

  1. CPU密集型
    尽量使用较小的线程池,一般Cpu核心数+1
  2. IO密集型
    方法一:可以使用较大的线程池,一般CPU核心数 * 2
    方法二:(线程等待时间与线程CPU时间之比 + 1)* CPU数目
  3. 混合型 可以将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,按情况而定

4、终止线程的方法.

使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
使用interrupt方法中断线程。

5、sleep()和wait() 有什么区别?

Sleep属于Thread,wait是object
Sleep 让出cpu不释放锁
Wait放弃锁,进入等待池,等notify()执行后唤醒

6、synchronized和lock、volatile区别

他俩都是互斥锁
Synchronized是个关键字,可以用在变量、方法、和类级别
volatile仅能使用在变量级别
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的 修改可见性和原子性
指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。

synchronized: 具有原子性,有序性和可见性;
volatile:具有有序性和可见性

7、线程分类

守护线程(后台线程,典型的就是GC)和用户线程(就是我们常说的业务线程)

8、乐观锁、悲观锁、自旋锁

乐观锁:乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。
悲观锁:悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock
自旋锁:如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

9、公平锁和非公平锁

公平锁(Fair)
加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
非公平锁(Nonfair)
加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待

  1. 非公平锁性能比公平锁高 5~10 倍,因为公平锁需要在多核的情况下维护一个队列
  2. Java 中的 synchronized 是非公平锁, ReentrantLock 默认的 lock()方法采用的是非公平锁。

10、为什么要用 join()方法?

很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,也就是需要主线程需要在子线程结束后再结束,这时候就要用到 join() 方法 。

11、什么是阻塞队列

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
JDK7 提供了 7 个阻塞队列。分别是:
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

12、Java 中用到的线程调度算法是什么?

一个cpu的时候,有很多线程,一个线程占用cpu,其余的线程在等待,为了充分发挥cpu的使用率。所以就有两种调度算法。
有两种调度模型:分时调度模型和抢占式调度模型。分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的CPU 的时间片这个也比较好理解。
java 虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。

三、JVM

1、内存模型

JAVA面试宝典(纯享版)_第2张图片

  • 程序计数器:是当前线程所执行的字节码的行号指示器
  • 虚拟机栈:每个线程独享,存储局部变量表、操作数栈、动态链接、方法出口等信息
  • 本地方法栈:虚拟机栈的作用是一样的,只不过虚 拟机栈是服务 Java方法的,而本地方法栈是为虚拟机调用 Native 方法服务的
  • 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据
  • Java堆:存储对象实例,空间比较大,我们调优基本上都是围绕堆去讨论的。

2、GC算法

  • 标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清 除垃圾碎片。
  • 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的 对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。
  • 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清 除掉端边界以外的内存。
  • 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。

3、引用计数法和可达性分析

  • 引用计数法:即一个对象如果没有任何与之关联的引用, 即他们的引用计数都不为 0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。
  • 可达性分析:为了解决引用计数法的循环引用问题,于是有了可达性分析法,通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象, 不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收

4、分代算法

新生代与复制算法:Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中。
老年代与标记复制算法:当新生代的 Eden Space 和 s0 空间不足时就会发生一次 GC,进行 GC 后,EdenSpace 和 s0 区的存活对象会被挪到 s1,然后将 Eden Space 和 s0 进行清理。如果 s1 无法足够存储某个对象,则将这个对象存储到老生代。 在进行 GC 后,使用的便是 Eden Space 和 s1 了,如此反复循环。当对象在 s0区或者s1区躲过一次 GC 后,其年龄就会+1。 默认情况下年龄到达 15 的对象会被移到老生代中。

5、JVM 类加载机制

JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化

6、双亲委派

当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class), 子类加载器才会尝试自己去加载。
打破:

  1. 特殊场景:例如一个tomcat部署多个web应用,相同的类名和全限定名,就需要打破
  2. 热部署:服务热部署,需要适应不同版本的对象

继承 ClassLoader 类:新建一个类加载器,继承java.lang.ClassLoader。
重写 loadClass()方法:重写该方法时,我们先尝试自己加载类,如果加载不到,再去按照正常的双亲委派模型去加载。

7、你知道哪些JVM性能调优

设定堆内存大小<

你可能感兴趣的:(java,面试,开发语言,设计模式)