java技术面试基础知识点总结

建议点开目录先读题。

目录

1.面向对象和面向过程的区别

2. Java的四个基本特性(抽象、封装、继承,多态)

3. 重载和重写的区别

4. 构造器Constructor是否可被override

5. 访问控制符public,protected,private,以及默认的区别

6. 是否可以继承String类

7. String和StringBuffer、StringBuilder的区别

8. hashCode和equals方法的关系

9. 抽象类和接口的区别

10. 自动装箱与拆箱

11. 什么是泛型、为什么要使用以及泛型擦除

12. Java中的集合类及关系图

13. ArrayList和vector区别

14 ArrayList和LinkedList区别及使用场景

15. Collection和Collections的区别

16. Concurrenthashmap实现原理

17. Error、Exception区别

18. Unchecked Exception和Checked Exception,各列举几个

19. 多线程的实现方式

20. 线程的状态转换

21. 什么是线程安全

22.  如何保证线程安全

23. Synchronized如何使用

24. synchronized和Lock的区别

25. 多线程如何进行信息交互

26. sleep和wait的区别(考察的方向是是否会释放锁)

27. 如何才能产生死锁

28. 死锁的预防

29. 什么叫守护线程,用什么方法实现守护线程

30. Java线程池技术及原理 

31. volatile关键字

32. Java中的NIO,BIO,AIO分别是什么

33. IO和NIO区别

34. 内存溢出和内存泄漏的区别

35. Java内存模型及各个区域的OOM,如何重现OOM

36. 出现OOM如何解决

37. Java内存管理及回收算法

38. Java类加载器及如何加载类(双亲委派)

39. xml解析方式


1.面向对象和面向过程的区别


面向过程 
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。 
缺点:没有面向对象易维护、易复用、易扩展 
面向对象 
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 
缺点:性能比面向过程低

2. Java的四个基本特性(抽象、封装、继承,多态)

抽象:就是把现实生活中的某一类东西提取出来,用程序代码表示,我们通常叫做类或者接口。抽象包括两个方面:一个是数据抽象,一个是过程抽象。数据抽象也就是对象的属性。过程抽象是对象的行为特征。 
封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行封装隐藏。封装分为属性的封装和方法的封装。 
继承:是对有着共同特性的多类事物,进行再抽象成一个类。这个类就是多类事物的父类。父类的意义在于抽取多类事物的共性。 
多态:允许不同类的对象对同一消息做出响应。方法的重载、类的覆盖正体现了多态。

3. 重载和重写的区别

重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。 
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类;如果父类方法访问修饰符为private则子类中就不是重写。

4. 构造器Constructor是否可被override

构造器不能被重写,不能用static修饰构造器,只能用 public private protected这三个权限修饰符,且不能有返回语句。

5. 访问控制符public,protected,private,以及默认的区别

private只有在本类中才能访问; 访问等级4
public在任何地方都能访问; 访问等级1
protected在同包内的类及包外的子类能访问; 访问等级2
默认不写在只在同包内能访问。访问等级3

6. 是否可以继承String类

String类是final类故不可以继承,一切由final修饰过的都不能继承

7. String和StringBuffer、StringBuilder的区别

可变性: 
String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。 
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。
线程安全性: 

String中的对象是不可变的,也就可以理解为常量,线程安全。 

AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。

StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

性能: 

每次对String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。

StringBuffer每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

8. hashCode和equals方法的关系

equals相等,hashcode必相等;hashcode相等,equals可能不相等。

9. 抽象类和接口的区别

语法层次: 
抽象类和接口分别给出了不同的语法定义,一个类智能继承自一个抽象类,但是可以实现多个接口。

设计层次: 
抽象层次不同,抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。 
跨域不同,抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在”is-a” 关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的,仅仅是实现了接口定义的契约而已,”like-a”的关系。。 
设计层次不同,抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。

10. 自动装箱与拆箱

装箱:将基本类型用它们对应的引用类型包装起来; 
拆箱:将包装类型转换为基本数据类型; 
Java使用自动装箱和拆箱机制,节省了常用数值的内存开销和创建对象的开销,提高了效率,由编译器来完成,编译器会在编译期根据语法决定是否进行装箱和拆箱动作。

11. 什么是泛型、为什么要使用以及泛型擦除

泛型,即“参数化类型”。 
创建集合时就指定集合元素的类型,该集合只能保存其指定类型的元素,避免使用强制类型转换。 
Java编译器生成的字节码是不包涵泛型信息的,泛型类型信息将在编译处理是被擦除,这个过程即类型擦除。 泛型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。 
类型擦除的主要过程如下:
将所有的泛型参数用其最左边界(最*的父类型)类型替换。 
移除所有的类型参数。

12. Java中的集合类及关系图

List和Set继承自Collection接口。 
Set无序不允许元素重复。HashSet和TreeSet是两个主要的实现类。 
List有序且允许元素重复。ArrayList、LinkedList和Vector是三个主要的实现类。 
Map也属于集合系统,但和Collection接口没关系。Map是key对value的映射集合,其中key列就是一个集合。key不能重复,但是value可以重复。 HashMap、TreeMap和Hashtable是三个主要的实现类。 

SortedSet和SortedMap接口对元素按指定规则排序,SortedMap是对key列进行排序。

13. ArrayList和vector区别

ArrayList和 Vector都实现了List接口, 都是通过数组实现的。 
Vector是线程安全的,而ArrayList是非线程安全的。 
List第一次创建的时候,会有一个初始大小,随着不断向List中增加元素,当 List 认为容量不够的时候就会进行扩容。Vector缺省情况下自动增长原来一倍的数组长度,ArrayList增长原来的50%。

14 ArrayList和LinkedList区别及使用场景

ArrayList底层是用数组实现的,可以认为ArrayList是一个可改变大小的数组。随着越来越多的元素被添加到ArrayList中,其规模是动态增加的。 
LinkedList底层是通过双向链表实现的, LinkedList和ArrayList相比,增删的速度较快。但是查询和修改值的速度较慢。同时,LinkedList还实现了Queue接口,所以他还提供了offer(), peek(), poll()等方法。 
LinkedList更适合从中间插入或者删除(链表的特性)。 ArrayList更适合检索和在末尾插入或删除(数组的特性)。

15. Collection和Collections的区别

java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。 
java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

16. Concurrenthashmap实现原理

java技术面试基础知识点总结

具体原理一句两句也说不清楚,网络文章: 
http://www.cnblogs.com/ITtangtang/p/3948786.html 
http://ifeve.com/concurrenthashmap/

17. Error、Exception区别

Error类和Exception类的父类都是throwable类,他们的区别是:
Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。 
Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

18. Unchecked Exception和Checked Exception,各列举几个

Unchecked Exception:

指的是程序的瑕疵或逻辑错误,并且在运行时无法恢复。 

包括Error与RuntimeException及其子类,如:OutOfMemoryError, UndeclaredThrowableException, IllegalArgumentException, IllegalMonitorStateException, NullPointerException, IllegalStateException, IndexOutOfBoundsException等。 语法上不需要声明抛出异常。

Checked Exception:

代表程序不能直接控制的无效外界情况(如用户输入,数据库问题,网络异常,文件丢失等) 除了Error和RuntimeException及其子类之外,如:ClassNotFoundException, NamingException, ServletException, SQLException, IOException等。 
需要try catch处理或throws声明抛出异常。

19. 多线程的实现方式

继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。

java技术面试基础知识点总结

20. 线程的状态转换

《一张图让你看懂JAVA线程间的状态转换》文章 

java技术面试基础知识点总结
http://my.oschina.net/mingdongcheng/blog/139263

21. 什么是线程安全

线程安全就是多线程访问同一代码,不会产生不确定的结果。

22.  如何保证线程安全

对非安全的代码进行加锁控制; 
使用线程安全的类; 
多线程并发情况下,线程共享的变量改为方法级的局部变量。

23. Synchronized如何使用

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 
修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; 
修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

24. synchronized和Lock的区别

主要相同点:Lock能完成synchronized所实现的所有功能 
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。Lock的锁定是通过代码实现的,而synchronized是在JVM层面上实现的,synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。Lock锁的范围有局限性,块范围,而synchronized可以锁住块、对象、类。

25. 多线程如何进行信息交互

void notify() 唤醒在此对象监视器上等待的单个线程。 
void notifyAll() 唤醒在此对象监视器上等待的所有线程。 
void wait() 导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法。 
void wait(long timeout) 导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法,或者超过指定的时间量。 
void wait(long timeout, int nanos) 导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。

26. sleep和wait的区别(考察的方向是是否会释放锁)

sleep()方法是Thread类中方法,而wait()方法是Object类中的方法。 
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态,在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

27. 如何才能产生死锁

产生死锁的四个必要条件:
互斥条件:所谓互斥就是进程在某一时间内独占资源。 
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 
不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。 
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

28. 死锁的预防

打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。
打破互斥条件。即允许进程同时访问某些资源。但是,有的资源是不允许被同时访问的,像打印机等等,这是由资源本身的属性所决定的。所以,这种办法并无实用价值。 
打破占有且申请条件。可以实行资源预先分配策略。即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又申请资源的现象,因此不会发生死锁。 
打破循环等待条件,实行资源有序分配策略。采用这种策略,即把资源事先分类编号,按号分配,使进程在申请,占用资源时不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁。

29. 什么叫守护线程,用什么方法实现守护线程

所谓守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。 

将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。

(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

setDaemon(boolean on)方法可以方便的设置线程的Daemon模式,true为守护模式,false为用户模式。

30. Java线程池技术及原理 

http://www.importnew.com/19011.html 
http://www.cnblogs.com/dolphin0520/p/3932921.html

  • 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
  • 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
  • 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
  • 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

31. volatile关键字

用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。 

Java语言中的volatile变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是synchronized的一部分。

锁提供了两种主要特性:互斥(mutual exclusion)和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的,如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。

Volatile变量具有synchronized的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。要使volatile变量提供理想的线程安全,必须同时满足下面两个条件:对变量的写操作不依赖于当前值;该变量没有包含在具有其他变量的不变式中。 

第一个条件的限制使volatile变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而volatile不能提供必须的原子特性。实现正确的操作需要使 x 的值在操作期间保持不变,而 volatile 变量无法实现这点。 每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。 
read and load 从主存复制变量到当前工作内存 
use and assign 执行代码,改变共享变量值 
store and write 用工作内存数据刷新主存相关内容 
其中use and assign 可以多次出现,但是这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样。

32. Java中的NIO,BIO,AIO分别是什么

BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。 

NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。 

AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

33. IO和NIO区别

关于NIO可以参考这篇文章: https://tech.meituan.com/nio.html
IO是面向流的,NIO是面向缓冲区的。 
IO的各种流是阻塞的,NIO是非阻塞模式。 
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

34. 内存溢出和内存泄漏的区别

内存溢出是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。 
内存泄漏是指分配出去的内存不再使用,但是无法回收。

35. Java内存模型及各个区域的OOM,如何重现OOM

这部分内容很重要,详细阅读《深入理解Java虚拟机》,也可以详细阅读这篇网络文章

http://hllvm.group.iteye.com/group/wiki/2857-JVM

36. 出现OOM如何解决

可通过命令定期抓取heap dump或者启动参数OOM时自动抓取heap dump文件。 
通过对比多个heap dump,以及heap dump的内容,分析代码找出内存占用最多的地方。 
分析占用的内存对象,是否是因为错误导致的内存未及时释放,或者数据过多导致的内存溢出。

37. Java内存管理及回收算法

阅读这篇文章:http://www.cnblogs.com/hnrainll/archive/2013/11/06/3410042.html

38. Java类加载器及如何加载类(双亲委派)

阅读文章:https://www.ibm.com/developerworks/cn/java/j-lo-classloader/(推荐) 
或http://blog.csdn.net/zhoudaxia/article/details/35824249

39. xml解析方式

DOM(JAXP Crimson解析器) 
SAX 
JDOM 
DOM4J 
区别:
DOM4J性能最好,连Sun的JAXM也在用DOM4J。目前许多开源项目中大量采用DOM4J,例如大名鼎鼎的hibernate也用DOM4J来读取XML配置文件。如果不考虑可移植性,那就采用DOM4J. 

JDOM和DOM在性能测试时表现不佳,在测试10M文档时内存溢出。在小文档情况下还值得考虑使用DOM和JDOM。虽然JDOM的开发者已经说明他们期望在正式发行版前专注性能问题,但是从性能观点来看,它确实没有值得推荐之处。另外,DOM仍是一个非常好的选择。DOM实现广泛应用于多种编程语言。它还是许多其它与XML相关的标准的基础,因为它正式获得W3C推荐(与基于非标准的Java模型相对),所以在某些类型的项目中可能也需要它(如在JavaScript中使用DOM)。 

SAX表现较好,这要依赖于它特定的解析方式-事件驱动。一个SAX检测即将到来的XML流,但并没有载入到内存(当然当XML流被读入时,会有部分文档暂时隐藏在内存中)。