[Java]Java面试常用知识点总结 — JavaSE 部分(来自牛客资料)

Java面试常用知识点总结 — JavaSE 部分

(PDF资源:https://download.csdn.net/download/qq_36254699/11577742 )

一、JavaSE 部分

1、Java 基础

①Java 基础部分(基本语法,Java 特性等)

1、为什么重写 equals 还要重写 hashcode?

链接:https://www.jianshu.com/p/75d9c2c3d0c1
考点:java 基础
参考回答:
因为在HashMap中,如果要比较key是否相等, 要同时使用这两个函数。
重载 hashCode()是为了对同一个key,能得到相同的 HashCode,这样 HashMap 就可以定位到我们指定的 key 上。
重载 equals()是为了向 HashMap 表明当前对象和 key 上所保存的对象是相等的,这样我们才真正地获得了这个 key 所对应的这个键值对。

因为自定义的类的hashcode() 方法继承于 Object 类,其 hashcode 码为默认的内存地址,这样即便有相同含义的两个对象,比较也是不相等的。
HashMap中的比较key是这样的,先求出 key的hashcode(),比较其值是否相 等,若相等再比较 equals(),若相等则认为他们是相等的;若 equals()不相等则认为他们不相等。
如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。
HashMap 用来判断 key 是否相等的方法, 其实是调用了 HashSet 判断加入元素 是否相等。

2、说一下 map 的分类和常见的情况

考点:java 基础
参考回答:
java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMap Hashtable LinkedHashMap 和 TreeMap.
Map 主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重
复。

  • HashMap 是一个最常用的 Map,它根据键的 HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。 HashMap 最多只允许一条 记录的键为 Null;允许多条记录的值为Null; HashMap 不支持线程的同步,即任一时刻可以有多 个线程同时写 HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有同步的能力,或者使用ConcurrentHashMap
  • Hashtable 与 HashMap 类似,它继承自 Dictionary类,不同的是:它不允许记录的键或者值 为空;它支持线程的同步,即任一时刻只有一个线程能写 Hashtable,因此也导致了Hashtable 在写入时会比较
  • LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历 LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排 序。在遍历的时候会比 HashMap 慢,不过有种情况例外,当HashMap 容量很大,实际数据较少时, 遍历起来可能会比 LinkedHashMap 慢,因为 LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而 HashMap 的遍历速度和他的容量有关。
  • TreeMap 实现 SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序, 也可以指定排序的比较器,当用 Iterator 遍历 TreeMap时,得到的记录是排过序的。
    一般情况下,我们用的最多的是 HashMap,在 Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么 TreeMap 会更好。如果需要输出的 顺序和输入的相同,那么用 LinkedHashMap 可以实现,它还可以按读取顺序来排列.

3、Object 若不重写 hashCode()的话,hashCode()如何计算出来的?

考点:基础
参考回答:
Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回 对象的内存地址。

4、==比较的是什么?

考点:基础
参考回答:
==对比两个对象基于内存引用,如果两个对象的引用完全相同(指向同一个对象)时,==操作将返回 true,否则返回 false。==如果两边是基本类型,就是比较数值是否相等。

5、若对一个类不重写,它的 equals()方法是如何比较的?

考点:基础
参考回答:
比较是对象的地址。

6、java8 新特性

考察点:java8
参考回答:
Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中。
方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有 Java 类或对象(实例) 的方法或构造器。与 lambda 联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
新工具 − 新的编译工具,如:Nashorn 引擎 jjs、 类依赖分析器 jdeps。
Stream API −新添加的 Stream API(java.util.stream) 把真正的函数式编程风格引入到 Java 中。
Date Time API − 加强对日期与时间的处理。
Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
Nashorn, JavaScript 引擎 − Java 8 提供了一个新的 Nashorn javascript 引擎,它允许 我们在 JVM 上运行特定的 javascript 应用。

7、说说 Lamda 表达式的优缺点。

考察点:Java 基础
参考回答:
优点:1. 简洁。2. 非常容易并行计算。3. 可能代表未来的编程趋势。
缺点:1. 若不用并行计算,很多时候计算速度没有比传统的 for 循环快。(并行计算有时 需要预热才显示出效率优势)2. 不容易调试。3. 若其他程序员没有学过 lambda 表达式,代码 不容易让其他语言的程序员看懂。

8、一个十进制的数在内存中是怎么存的?

考察点:计算机基础
参考回答:
补码的形式。

9、为啥有时会出现 4.0-3.6=0.40000001 这种现象?

考察点:计算机基础
参考回答:
原因简单来说是这样:2 进制的小数无法精确的表达 10 进制小数,计算机在计算 10 进制小 数的过程中要先转换为 2 进制进行计算,这个过程中出现了误差。

10、Java 支持的数据类型有哪些?什么是自动拆装箱?

考察点:JAVA 数据类型
参考回答:
Java 语言支持的 8 种基本数据类型是:
byte short int long float double boolean char 自动装箱是 Java 编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如: 把 int 转化成 Integer,double 转化成 Double,等等。反之就是自动拆箱。

11、什么是值传递和引用传递?

考察点:JAVA 引用传递
参考回答:
**值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量. 引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。**所以对引用对象进行操作会同时改变原对象. 一般认为,java 内的传递都是值传递.

12、数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用 Array 而不 是 ArrayList?

考察点:Array
参考回答:

  • Array 和 ArrayList 的不同点: Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
  • Array 大小是固定的,ArrayList 的大小是动态变化的。
  • ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
  • 对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类 型的时候,这种方式相对比较慢。

13、你了解大 O 符号(big-O notation)么?你能给出不同数据结构的例子么?

考察点:JAVA notation
参考回答:
大 O 符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景下有多么好。 大 O 符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般 使用大 O 符号基于时间,内存和性能来选择最好的实现。大 O 符号可以对大量数据的性能给出一 个很好的说明。
同时,大 O 符号表示一个程序运行时所需要的渐进时间复杂度上界。
其函数表示是:
对于函数 f(n),g(n),如果存在一个常数 c,使得 f(n)<=c*g(n),则 f(n)=O(g(n));
大 O 描述当数据结构中的元素增加时,算法的规模和性能在最坏情景下有多好。
大 O 还可以描述其它行为,比如内存消耗。因为集合类实际上是数据结构,因此我们一般使 用大 O 符号基于时间,内存,性能选择最好的实现。大 O 符号可以对大量数据性能给予一个很好 的说明。

14、String 是最基本的数据类型吗?

考察点:数据类型
参考回答:
不是,基本数据类型包括 byte、int、char、long、float、double、boolean 和 short

15、int 和 Integer 有什么区别

考察点:数据类型
参考回答:
Java 提供两种不同的类型:引用类型和原始类型(或内置类型)
int 是 java 的原始数据类型,integer 是 java 为 int 提供的封装类
Java 为每个原始类型提供了封装类。 原始类型封装类 boolean->Boolean,char->Character, byte->Byte short->Short,int->Integer,long->Long,float->Float,double->Double。
引用类型和原始类型的行为完全不同,并且它们具有不同的语义。引用类型和原始类型具有不同的特征和用法,它们包括:大小和速度问题,这种类型以哪种类型的数据结构存储,当引用类型和原始类型用作某个类的实例数据时所指定的缺省值。对象引用实例变量的缺省值为null,而原始类型实例变量的缺省值与它们的类型有关。

16、String 和 StringBuffer 的区别

考察点:数据类型
参考回答:
JAVA 平台提供了两个类:String 和 StringBuffer,它们可以储存和操作字符串,即包含多 个字符的字符数据。String 类提供了数值不可改变的字符串。而StringBuffer 类提供的字符串进行修改。 当你知道字符数据要 改变的时候你就可以使用 StringBuffer 。典型地,可以使用 StringBuffers 来动态构造字符数据

17、我们在 web 应用开发过程中经常遇到输出某种编码的字符,如 iso8859-1 等,如何输出一个某种编码的字符串?

考察点:数据类型
参考回答:

public String translate (String str) { 
	String tempStr = “”; 
	try { 
	/* 以下一行实现需求 */
	tempStr = new String(str.getBytes(“ISO-8859-1″), “GBK”); 
	tempStr = tempStr.trim();
	} catch (Exception e) {
	System.err.println(e.getMessage()); 
	} 
	return tempStr;
}

18、int 和 Integer 有什么区别?

考察点:数据类型
参考回答:
Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型, 但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的 包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱 机制,使得二者可以相互转换。 Java 为每个原始类型提供了包装类型:

  • 原始类型: boolean,char,byte,short,int,long,float,double
  • 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
    如:
class AutoUnboxingTest {
	public static void main(String[] args) {
			//a b的值要满足: -128 <= a,b < 128
			Integer a = new Integer(3);
			Integer b = 3; // 将 3 自动装箱成 Integer 类型
			int c = 3;
			System.out.println(a == b); // false 两个引用没有引用同一对象
			System.out.println(a == c); // true a 自动拆箱成 int 类型再和 c 比较
		}
}

19、&和&&的区别?

考察点:运算符
参考回答:
&运算符有两种用法:
(1)按位与;
(2)逻辑与。
&&运算符是短路运算。

逻辑与跟短路与的 差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是 true 整个表达式的值才是true。&&之所以称为短路运算是因为,如果&&左边的表达式的值是 false,右边的表达式会被直 接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定 用户名不是 null 而且不是空字符串,应当写为:username != null &&!username.equals(""), 二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的 equals 比较,否则会产生 NullPointerException 异常。

20、在 Java 中,如何跳出当前的多重嵌套循环?

考察点:循环
参考回答:
在最外层循环前加一个标记如 A,然后用 break A;可以跳出多重循环。(Java 中支持带标 签的 break 和 continue 语句, 作用有点类似于 C和 C++中的 goto 语句,但是就像要避免使用 goto 一样,应该避免使用带标签的 break 和 continue,因为它不会让你的程序变得更优雅,很多时 候甚至有相反的作用,所以这种语法其实不知道更好)

21、你能比较一下 Java 和 JavaSciprt 吗?

考察:Java&JavaScript
参考回答:
JavaScript 与 Java 是两个公司开发的不同的两个产品。
Java 是原 Sun Microsystems 公司 推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;而 JavaScript 是 Netscape 公司的产品,为了扩展 Netscape 浏览器的功能而开发的一种可以嵌入 Web 页面中运行的基于对象和事件驱动的解释性语言。
JavaScript 的前身是 LiveScript;而 Java 的前身是 Oak 语言。
下面对两种语言间的异同作如下比较:

  • 基于对象和事件驱动与面向对象的区分:Java 是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript 是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对象供设计人员使用。
  • 解释和编译:Java 的源代码在执行之前,必须经过编译。JavaScript 是一种解释性编程语言, 其源代码不需经过编译,由浏览器解释执行。(目前的浏览器几乎都使用了 JIT(即时编译)技术来提升 JavaScript 的运行效率)
  • 强类型变量和类型弱变量:Java 采用强类型变量检查,即所有变量在编译之前必须作声明; JavaScript 中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript 的解释器在运行时检查推断其数据类型。
  • 代码格式不一样

22、简述正则表达式及其用途。

考察点:正则表达式
参考回答:
**在编写处理字符串的程序时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。**换句话说,正则表达式就是记录文本规则的代码。计算机处理的信息更多的时候不是数值而是字符串,正则表达式就是在进行字符串匹配和处理的时候最为强大的工具,绝大多数语言都提供了对正则表达式的支持。

23、Java 中是如何支持正则表达式操作的?

考察点:正则表达式
参考回答:
Java 中的 String 类提供了支持正则表达式操作的方法,包括:matches()、replaceAll()、 replaceFirst()、split()。此外,Java 中可以用 Pattern 类表示正则表达式对象,它提供了丰 富的 API 进行各种正则表达式操作,如:

import java.util.regex.Matcher; 
import java.util.regex.Pattern; 
class RegExpTest { 
	public static void main(String[] args) { 
		String str = "成都市(成华区)(武侯区)(高新区)"; 
		Pattern p = Pattern.compile(".*?(?=\\()"); 
		Matcher m = p.matcher(str); 
		if(m.find()) { 
			System.out.println(m.group());
		} 
	} 
}

24、请你说说 Java 和 PHP 的区别?

考察点:Java 特性
参考回答:
PHP 暂时还不支持像 Java 那样 JIT 运行时编译热点代码,但是 PHP 具有 opcache 机制,能够 把脚本对应的 opcode 缓存在内存,PHP7 中还支持配置 opcache.file_cache 导出 opcode 到文件. 第三方的 Facebook HHVM 也支持 JIT.另外 PHP 官方基于 LLVM 围绕 opcache 机制构建的 Zend JIT 分支也正在开发测试中.在 php-src/Zend/bench.php 测试显示,PHP JIT 分支速度是 PHP 5.4 的 10 倍. PHP 的库函数用 C 实现,而 Java 核心运行时类库(jdk/jre/lib/rt.jar,大于 60MB)用 Java 编写 (jdk/src.zip), 所以 Java 应用运行的时候,用户编写的代码以及引用的类库和框架都要在 JVM 上解释执行. Java 的 HotSpot 机制,直到有方法被执行 10000 次才会触发 JIT 编译, 在此之前运行在解释模式下,以避免出现 JIT 编译花费的时间比方法解释执行消耗的时间还要多的情况.
PHP 内置模板引擎,自身就是模板语言.而 Java Web 需要使用 JSP 容器如 Tomcat 或第三方模板引擎.
PHP 也可以运行在多线程模式下,比如 Apache 的 event MPM 和 Facebook 的 HHVM 都是多线程架构.不管是多进程还是多线程的 PHP Web 运行模式,都不需要 PHP 开发者关心和控制,也就是说 PHP 开发者不需要写代码参与进程和线程的管理,这些都由 PHP-FPM/HHVM/Apache 实现.PHP-FPM 进程管理和并发实现并不需要 PHP 开发者关心,而 Java 多线程编程需要 Java 开发者编码参 与.PHP 一个 worker 进程崩溃,master 进程会自动新建一个新的 worker 进程,并不会导致 PHP 服 务崩溃.而 Java 多线程编程稍有不慎(比如没有捕获异常)就会导致 JVM 崩溃退出.对于 PHP-FPM 和 Apache MOD_PHP 来说,服务进程常驻内存,但一次请求释放一次资源,这种内存释放非常彻底. PHP 基于引用计数的 GC 甚至都还没发挥作用程序就已经结束了。

②关键字

1、介绍一下 Syncronized 锁,如果用这个关键字修饰一个静态方法,锁住了什 么?如果修饰成员方法,锁住了什么?

考点:java 关键字
参考回答:
synchronized 修饰静态方法以及同步代码块的 synchronized (类.class)用法锁的是,线程想要执行对应同步代码,需要获得类锁。
synchronized 修饰成员方法,线程获取的是当前调用该方法的对象实例的对象锁

2、介绍一下 volatile?

考察点:java 关键字
参考回答:
volatile 关键字是用来保证有序性和可见性的。
有序性:这跟 Java 内存模型有关。比如我们所写的代码,不一定是按照我们自己书写的顺序来执行的,编译器会做重排序,CPU 也会做重排序的, 这样的重排序是为了减少流水线的阻塞的,引起流水阻塞,比如数据相关性,提高 CPU 的执行效率。需要有一定的顺序和规则来保证,不然程序员自己写的代码都不知道对不对了,所以有 happens-before 规则,其中有条就是 volatile 变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;有序性实现的是通过插入内存屏障来保证的。
可见性:首先 Java 内存模型分为,主内存,工作内存。比如线程 A 从主内存把变量从主内存读到了自己的工作内存中,做了加 1 的操作,但是此时没有将 i 的最新值刷新会主内存中,线程 B 此时读到的还是 i 的旧值。 加了volatile关键字的代码生成的汇编代码发现,会多出一个lock前缀指令。 Lock指令对Intel 平台的CPU,早期是锁总线,这样代价太高了,后面提出了缓存一致性协议,MESI,来保证了多核之间数据不一致性问题。

3、锁有了解嘛,说一下 Synchronized 和 lock

考察点:java 关键字
参考回答:
synchronized 是 Java 的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。JDK1.5 以后引入了自旋锁、锁粗化、轻量级锁, 偏向锁来有优化关键字的性能。

Synchronized 和 lock不同点:
  • Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
  • synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
  • Lock 可以让等待锁的线程响应中断,而 synchronized 却不行, 使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断;
  • 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

4、讲一讲 Java 里面的 final 关键字怎么用的?

考察点:关键字
参考回答:
当用 final 修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用 final 进行修饰。final 类中的成员变量可以根据需要设为 final,但是要注意 final 类中的所有成员方法都会被隐式地指定为 final 方法。
使用 final 方法的原因有两个:

  • 第一个原因是把方法锁定,以防任何继承类修改它的含义;
  • 第二个原因是效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的 Java 版本中,不需要使用 final 方法进行这些优化了。

对于一个 final 变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改; 如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

③面向对象

1、wait 方法底层原理

考察点:基础
参考回答:
wait方法实现:
lock.wait()方法最终通过ObjectMonitor的void wait(jlong millis, bool interruptable,TRAPS);实现
1、将当前线程封装成ObjectWaiter对象node
2、通过ObjectMonitor::AddWaiter方法将node添加到_WaitSet列表中
3、通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象
4、最终底层的park方法会挂起线程

ObjectSynchorizer::wait方法通过Object对象找到ObjectMonitor对象调用方法void ObjectMonitor::wait(),通过ObjectMonitor::AddWaiter调用把新建里的ObjectWaiter对象,放入到_WaitSet队列的末尾然后,在ObjectMonitor::exit释放锁,接着thread_ParkEvent->park,也就是进行wait。

2、Java 有哪些特性,举个多态的例子。

考察点:语言特性
参考回答:
封装、继承、多态。多态:指允许不同类的对象对同一消息做出响应。即同一消息可以根据 发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

3、String 为啥不可变?

考察点:面向对象
参考回答:
不可变对象是指一个对象的状态在对象被创建之后就不再变化。不可改变的意思就是说:不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对 象,引用类型指向的对象的状态也不能改变。
String 不可变是因为在 JDK 中 String 类被声明为一个 final 类,且类内部的 value 字节数组也是 final 的,只有当字符串是不可变时字符串池才有可能实现,字符串池的实现可以 在运行时节约很多 heap 空间,因为不同的字符串变量都指向池中的同一个字符串;如果字符串 是可变的则会引起很严重的安全问题,譬如数据库的用户名密码都是以字符串的形式传入来获得 数据库的连接,或者在 socket 编程中主机名和端口都是以字符串的形式传入,因为字符串是不 可变的,所以它的值是不可改变的,否则黑客们可以钻到空子改变字符串指向的对象的值造成安 全漏洞;
因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享, 这样便不用因为线程安全问题而使用同步,字符串自己便是线程安全的;
因为字符串是不可变的 所以在它创建的时候 hashcode 就被缓存了,不变性也保证了 hash 码的唯一性,不需要重新计 算,这就使得字符串很适合作为 Map 的键,字符串的处理速度要快过其它的键对象,这就是 HashMap 中的键往往都使用字符串的原因。

4、类和对象的区别

考察点:面向对象
参考回答:
1**.类是对某一类事物的描述,是抽象的;而对象是一个实实在在的个体,是类的一个实例**。
比如:“人”是一个类,而“教师”则是“人”的一个实例。
2.类是一组具有相同属性的对象集合体

5、请列举你所知道的 Object 类的方法。

考察点:面向对象
参考回答:
Object()默认构造方法。

  • clone() 创建并返回此对象的一个副本
  • equals(Object obj) 指 示某个其他对象是否与此对象“相等”
  • finalize()当垃圾回收器确定不存在对该对象的更多引 用时,由对象的垃圾回收器调用此方法
  • getClass()返回一个对象的运行时类
  • hashCode()返回 该对象的哈希码值。
  • notify()唤醒在此对象监视器上等待的单个线程
  • notifyAll()唤醒在此 对象监视器上等待的所有线程
  • toString()返回该对象的字符串表示
  • wait()导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法
  • wait(long timeout)导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量
  • wait(long timeout, int nanos) 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某 个实际时间量。

6、重载和重写的区别?相同参数不同返回值能重载吗?

考察点:重载
参考回答:
重载(Overloading)
(1) 方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在, 具有不同的参数个数/类型;重载 Overloading 是一个类中多态性的一种表现。
(2) Java 的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法, 这就是多态性。
(3) 重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准。

重写(Overriding)
(1) 父类与子类之间的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。在 Java 中,子类可继承父类中的方法,而不需要重新编写相同的方法。
但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。
(2)若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法。如需父类中原有的方法,可使用 super 关键字,该关键字引用了当前类的父类。
(3)子类函数的访问修饰权限不能少于父类的。

7、”static”关键字是什么意思?Java 中是否可以覆盖(override)一个 private 或者是 static 的方法?

考察点:static 变量
参考回答:
“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。 Java 中 static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。

8、String 能继承吗?

考察点:String
参考回答:
不能,char 数组用 final 修饰的。

9、StringBuffer 和 StringBuilder 有什么区别,底层实现上呢?

考察点:类
参考回答:
StringBuffer 线程安全,StringBuilder 线程不安全,底层实现上的话,StringBuffer 其 实就是比 StringBuilder 多了 Synchronized 修饰符

10、类加载机制,双亲委派模型,好处是什么?

考察点:类
参考回答:
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归, 如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才 自己去加载
使用双亲委派模型的好处在于 Java 类随着它的类加载器一起具备了一种带有优先级的层次 关系。例如类 java.lang.Object,它存在在 rt.jar 中,无论哪一个类加载器要加载这个类,最 终都是委派给处于模型最顶端的 Bootstrap ClassLoader 进行加载,因此 Object 类在程序的各 种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的 话,如果用户编写了一个 java.lang.Object 的同名类并放在 ClassPath 中,那系统中将会出现
多个不同的 Object 类,程序将混乱。因此,如果开发者尝试编写一个与 rt.jar 类库中重名的 Java 类,可以正常编译,但是永远无法被加载运行。

11、静态变量存在哪?

考察点:类
参考回答:
方法区

12、讲讲什么是泛型?

考察点:JAVA 泛型
参考回答:
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法 时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化, 类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用时传入具体的类型(类型实参)。

public class GenericTest {
public static void main(String[] args) {
/*
List list = new ArrayList();
list.add("qqyumidi");
list.add("corn");
list.add(100);
*/
List<String> list = new ArrayList<String>();
list.add("qqyumidi");
list.add("corn");
//list.add(100); // 1 提示编译错误
for (int i = 0; i < list.size(); i++) {
String name = list.get(i); // 2
System.out.println("name:" + name);
}
}
}

采用泛型写法后,在//1 处想加入一个 Integer 类型的对象时会出现编译错误,通过 List,直接限定了 list 集合中只能含有 String 类型的元素,从而在//2 处无须进行强 制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是 String 类型 了。

13、解释 extends 和 super 泛型限定符-上界不存下界不取

考察点:JAVA 泛型
参考回答:
(1)泛型中上界和下界的定义
上界 <? extend Fruit>
下界 <? super Apple>
(2)上界和下界的特点
上界的 list 只能 get,不能 add(确切地说不能 add 出除 null 之外的对象,包括 Object)
下界的 list 只能 add,不能 get
(3)示例代码
import java.util.ArrayList;
import java.util.List;
class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
public class CovariantArrays {
public static void main(String[] args) {
//上界
List<? extends Fruit> flistTop = new ArrayList();
flistTop.add(null);
//add Fruit 对象会报错
//flist.add(new Fruit());
Fruit fruit1 = flistTop.get(0);
//下界
List<? super Apple> flistBottem = new ArrayList();
flistBottem.add(new Apple());
flistBottem.add(new Jonathan());
//get Apple 对象会报错
//Apple apple = flistBottem.get(0);
}
}
(4)上界 <? extend Fruit> ,表示所有继承 Fruit 的子类,但是具体是哪个子类,无法确 定,所以调用 add 的时候,要 add 什么类型,谁也不知道。但是 get 的时候,不管是什么子类, 不管追溯多少辈,肯定有个父类是 Fruit,所以,我都可以用最大的父类 Fruit 接着,也就是把 所有的子类向上转型为 Fruit。
下界 <? super Apple>, 表示Apple的所有父类,包括Fruit, 一直可以追溯到老祖宗Object 。 那么当我 add 的时候,我不能 add Apple 的父类,因为不能确定 List 里面存放的到底是哪个父 类。但是我可以 add Apple 及其子类。因为不管我的子类是什么类型,它都可以向上转型为 Apple 及其所有的父类甚至转型为 Object 。但是当我 get 的时候,Apple 的父类这么多,我用什么接 着呢,除了 Object,其他的都接不住。
所以,归根结底可以用一句话表示,那就是编译器可以支持向上转型,但不支持向下转型。 具体来讲,我可以把 Apple 对象赋值给 Fruit 的引用,但是如果把 Fruit 对象赋值给 Apple 的引用就必须得用 cast。

14、是否可以在 static 环境中访问非 static 变量?

考察点:static 变量
参考回答:
不能,static 变量在 Java 中是属于类的,它在所有的实例中的值是一样的。当类被 Java 虚拟机 载入的时候,会对 static 变量进行初始化。如果你的代码尝试不用实例来访问非 static 的变量, 编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。

15、谈谈如何通过反射创建对象?

考察点:类
参考回答:

  • 方法 1:通过类对象调用 newInstance()方法,例如:String.class.newInstance()
  • 方法 2:通过类对象的 getConstructor()或 getDeclaredConstructor()方法获得构造器 (Constructor)对象并调用其 newInstance()方法创建对象,例如: String.class.getConstructor(String.class).newInstance(“Hello”);

16、Java 支持多继承么?

考察点:JAVA 多继承
参考回答:
Java 中类不支持多继承,只支持单继承(即一个类只有一个父类)。 但是 java 中的接口 支持多继承,,即一个子接口可以有多个父接口。(接口的作用是用来扩展对象的功能,一个子 接口继承多个父接口,说明子接口扩展了多个功能,当类实现接口时,类就扩展了相应的功能)。

17、接口和抽象类的区别是什么?

考察点:抽象类
参考回答:
Java 提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:

  • 类可以实现很多个接口,但是只能继承一个抽象类
  • 接口中所有的方法隐含的都是抽象的,而抽象类则可以同时包含抽象和非抽象的方法
  • 接口中声明的变量默认都是 final 的,抽象类可以包含非 final 的变量
  • 接口中的成员函数默认是 public 的,抽象类的成员函数可以是 protected 或者 是 public
  • 接口是绝对抽象的,不可以被实例化,抽象类也不可以被实例化,但是,如果它包含 main 方法的话是可以被调用的

18、Comparable 和 Comparator 接口是干什么的?列出它们的区别。

考察点:comparable 接口
参考回答:
Java 提供了只包含一个 compareTo()方法的 Comparable 接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。
Java 提供了包含 compare()和 equals()两个方法的 Comparator 接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals() 方法需要一个对象作为参数,它用来决定输入参数是否和 comparator 相等。只有当输入参数也是一个 comparator 并且输入参数和当前 comparator 的排序结果是相同的时候,这个方法才返回true。

19、面向对象的特征有哪些方面

考察点:JAVA 特征
参考回答:

  • 继承: 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。 对象的一个新类可以从现有的类中派 生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原 始类称为新类的基类(父类)。派生 类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的 需要。
  • 封装: 封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基 本概念,即现实世界可以被描绘成一 系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。
  • 多态性: 多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享 、代码共享的优势,很好的解决了应用程序函数同名问题。

20、final, finally, finalize 的区别。

考察点:声明
参考回答:
final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承
finally 是异常处理语句结构的一部分,表示总是执行。
finalize 是 Object 类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以 覆盖此方法提供垃圾收集时的其他资源 回收,例如关闭文件等。

21、Overload 和 Override 的区别。Overloaded 的方法是否可以改变返回值的类型?

考察点:JAVA 多态
参考回答:
方法的重写 Overriding 和重载 Overloading 是 Java 多态性的不同表现。重写 Overriding 是父类与子类之间多态性的一种表现,重载 Overloading 是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被”屏蔽”了。如果在一个类中定义 了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载 (Overloading)。
如果两个方法的参数列表完全一样,不可以让它们的返回值不同来实现重载Overload。

22、abstract class 和 interface 有什么区别?

考察点:抽象类
参考回答:
抽象类与接口的区别前面有描述

23、Static Nested Class 和 Inner Class 的不同

考察点:声明
参考回答:
静态嵌套类: Static Nested Class 是被声明为静态的内部类,它可以不依赖外部类实例而被实例化
内部类需要在外部实例化后而被实例化

24、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性, 并可返回变化后的结果,那么这里到底是值传递还是引用传递?

考察点:对象
参考回答:
是值传递。Java 编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变,但对象的引用是永远不会改变的。

25、Java 的接口和 C++的虚类的相同和不同处。

考察点:接口
参考回答:
由于 Java 不支持多继承,而有可能某个类或对象要使用分别在几个类或对象里面的方法或属性,现有的单继承机制就不能满足要求。 与继承相比,接口有更高的灵活性,因为接口中没有任何实现代码。当一个类实现了接口以后, 该类要实现接口里面所有的方法和属性,并且接口里面的属性在默认状态下面都是 public static,所有方法默认情况下是 public. 一个类可以实现多个接口。

26、JAVA 语言如何进行异常处理,关键字:throws,throw,try,catch,finally 分别代表什么意义?在 try 块中可以抛出异常吗?

考察点:异常
参考回答:
Java 通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接 口。在 Java 中,每个异常都是一个对象,它是 Throwable 类或其它子类的实例。当一个方法出 现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个 异常并进行处理。
Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。一般情况下是用 try 来执行一段程序,如果出现异常,系统会抛出(throws)一个 异常,这时候你可以通过它的类型来捕捉(catch)它,或最后(finally)由缺省处理器来处理。

  • 用 try 来指定一块预防所有”异常”的程序
  • 紧跟在 try 程序后面,应包含一个 catch 子句来指 定你想要捕捉的”异常”的类型
  • throw 语句用来明确地抛出一个”异常”
  • throws 用来标明一 个成员函数可能抛出的各种”异常”
  • Finally 为确保一段代码不管发生什么”异常”都被执行 一段代码

try中是可以抛出异常的

27、内部类可以引用他包含类的成员吗?有没有什么限制?

考察点:类
参考回答:
一个内部类对象可以访问创建它的外部类对象的内容

  • 内部类如果不是 static 的,那么它可以访问创建它的外部类对象的所有属性
  • 内部类如果是 static的,即为 nested class,那么它只可以访问创建它的外部类对象的所有static属性

当从外部类继承的时候,内部类是不会被覆盖的,它们是完全独立的实体,每个都在自己的命名空间内,如果从内部类中明确地继承,就可以覆盖原来内部类的方法。

28、两个对象值相同(x.equals(y) == true),但却可有不同的 hashcode 说法 是否正确?

考察点:对象
参考回答:
不对,如果两个对象 x 和 y 满足 x.equals(y) == true,它们的哈希码(hash code)应当 相同。
Java 对于 eqauls 方法和 hashCode 方法是这样规定的:
(1)如果两个对象相同(equals 方法返回 true),那么它们的 hashCode 值一定要相同
(2)如果两个对象的 hashCode 相同,它们并不一定相同。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在 Set 集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。

29、重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类 型进行区分?

考察点:java 重载
参考回答:
8题

30、如何通过反射获取和设置对象私有字段的值?

考察点:类
参考回答:
可以通过类对象的 getDeclaredField()方法字段(Field)对象,然后再通过字段对象的 setAccessible(true)将其设置为可以访问,接下来就可以通过 get/set 方法来获取/设置字段的 值了。下面的代码实现了一个反射的工具类,其中的两个静态方法分别用于获取和设置私有字段 的值,字段可以是基本类型也可以是对象类型且支持多级对象操作,例如 ReflectionUtil.get(dog, “owner.car.engine.id”);可以获得 dog 对象的主人的汽车的引擎的 ID 号。
import java.lang.reflect.Method; class MethodInvokeTest {
public static void main(String[] args) throws Exception { String str = “hello”; Method m = str.getClass().getMethod(“toUpperCase”); System.out.println(m.invoke(str)); // HELLO }
}

31、谈一下面向对象的"六原则一法则"。

考察点:Java 对象
参考回答:

  • 单一职责原则一个类只做它该做的事情。(单一职责原则想表达的就是"高内聚",写 代码最终极的原则只有六个字"高内聚、低耦合",所谓的高内聚就是一个代码模块只完成一项功 能,在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内 聚的原则,这个类就只有单一职责。另一个是模块化,好的自行车是组装车,从减震叉、刹车到 变速器,所有的部件都是可以拆卸和重新组装的,好的乒乓球拍也不是成品拍,一定是底板和胶 皮可以拆分和自行组装的,一个好的软件系统,它里面的每个功能模块也应该是可以轻易的拿到 其他系统中使用的,这样才能实现软件复用的目标。)
  • 开闭原则软件实体应当对扩展开放, 对修改关闭。(在理想的状态下,当我们需要为一个软件系统增加新功能时,只需要从原来的系 统派生出一些新类就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:①抽象是 关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可 变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱,如果不 清楚如何封装可变性,可以参考《设计模式精解》一书中对桥梁模式的讲解的章节。)
  • 依赖倒转原则面向接口编程。(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类 型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一 个子类型所替代,请参考下面的里氏替换原则。)
  • 里氏替换原则任何时候都可以用子类型替换 掉父类型。(关于里氏替换原则的描述,Barbara Liskov 女士的描述比这个要复杂得多,但简 单的说就是能用父类型的地方就一定能使用子类型。里氏替换原则可以检查继承关系是否合理, 如果一个继承关系违背了里氏替换原则,那么这个继承关系一定是错误的,需要对代码进行重构。 例如让猫继承狗,或者狗继承猫,又或者让正方形继承长方形都是错误的继承关系,因为你很容 易找到违反里氏替换原则的场景。需要注意的是:子类一定是增加父类的能力而不是减少父类的 能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。)
  • 接口隔离原则接口要小而专,绝不能大而全。(臃肿的接口是对接口的污染,既然接口表示 能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。例如,琴棋书画就应该分 别设计为四个接口,而不应设计成一个接口中的四个方法,因为如果设计成一个接口中的四个方 法,那么这个接口很难用,毕竟琴棋书画四样都精通的人还是少数,而如果设计成四个接口,会 几项就实现几个接口,这样的话每个接口被复用的可能性是很高的。Java 中的接口代表能力、 代表约定、代表角色,能否正确的使用接口一定是编程水平高低的重要标识。)
  • 合成聚合复用原则优先使用聚合或合成关系复用代码。(通过继承来复用代码是面向对象程序设计中被滥用 得最多的东西,因为所有的教科书都无一例外的对继承进行了鼓吹从而误导了初学者,类与类之 间简单的说有三种关系,Is-A 关系、Has-A 关系、Use-A 关系,分别代表继承、关联和依赖。其 中,关联关系根据其关联的强度又可以进一步划分为关联、聚合和合成,但说白了都是 Has-A 关系,合成聚合复用原则想表达的是优先考虑 Has-A 关系而不是 Is-A 关系复用代码,原因嘛可 以自己从百度上找到一万个理由,需要说明的是,即使在 Java 的 API 中也有不少滥用继承的例 子,例如 Properties 类继承了 Hashtable 类,Stack 类继承了 Vector 类,这些继承明显就是错 误的,更好的做法是在 Properties 类中放置一个 Hashtable 类型的成员并且将其键和值都设置 为字符串来存储数据,而 Stack 类的设计也应该是在 Stack 类中放一个 Vector 对象来存储数据。 记住:任何时候都不要继承工具类,工具是可以拥有并可以使用的,而不是拿来继承的。)
  • 迪米特法则迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。再复杂 的系统都可以为用户提供一个简单的门面, Java Web开发中作为前端控制器的Servlet或 Filter 不就是一个门面吗,浏览器对服务器的运作方式一无所知,但是通过前端控制器就能够根据你的 请求得到相应的服务。调停者模式也可以举一个简单的例子来说明,例如一台计算机,CPU、内 存、硬盘、显卡、声卡各种设备需要相互配合才能很好的工作,但是如果这些东西都直接连接到 一起,计算机的布线将异常复杂,在这种情况下,主板作为一个调停者的身份出现,它将各个设 备连接在一起而不需要每个设备之间直接交换数据,这样就减小了系统的耦合度和复杂度。

32、请问 Query 接口的 list 方法和 iterate 方法有什么区别?

考察点:接口
参考回答:
list()方法无法利用一级缓存和二级缓存(对缓存只写不读),它只能在开启查询缓存的前提下使用查询缓存;iterate()方法可以充分利用缓存,如果目标数据只读或者读取频繁,使用 iterate()方法可以减少性能开销
list()方法不会引起 N+1 查询问题,而 iterate()方法可能引起 N+1 查询问题

33、Java 中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?

考察点:方法
参考回答:
8题

34、Java 中,什么是构造函数?什么是构造函数重载?什么是复制构造函数?

考察点:JAVA 构造函数
参考回答:
当新对象被创建的时候,构造函数会被调用。每一个类都有构造函数。在程序员没有给类提 供构造函数的情况下,Java 编译器会为这个类创建一个默认的构造函数
Java 中构造函数重载和方法重载很相似。可以为一个类创建多个构造函数。每一个构造函数必须有它自己唯一的参数列表。
Java 不支持像 C++中那样的复制构造函数,这个不同点是因为如果你不自己写构造函数的情况下, Java 不会创建默认的复制构造函数

35、hashCode()和 equals()方法有什么联系?

考点:基础
参考回答:
Java 对象的 eqauls 方法和 hashCode 方法是这样规定的:
➀相等(相同)的对象必须具有相等的哈希码(或者散列码)
➁如果两个对象的 hashCode 相同,它们并不一定相同。

④集合部分

1、Map 和 ConcurrentHashMap 的区别?

考点:集合
参考回答:
hashmap 是线程不安全的,put 时在多线程情况下,会形成环从而导致死循环。 CoucurrentHashMap 是线程安全的,采用分段锁机制,减少锁的粒度。

2、hashMap 内部具体如何实现的?

考点:集合
参考回答:
HashMap 基于数组实现的,通过对 key 的 hashcode & 数组的长度得到在数组中位置,如当前数组有元素,则数组当前元素 next 指向要插入的元素,这样来解决 hash 冲突的,形成了拉链式的结构。put 时在多线程情况下,会形成环从而导致死循环。数组长度一般是 2n,从 0 开始编 号,所以 hashcode & (2n-1),(2n-1)每一位都是 1,这样会让散列均匀。
需要注意的是, HashMap 在 JDK1.8 的版本中引入了红黑树结构做优化,当链表元素个数大于等于 8 时,链表转换成树结构;若桶中链表元素个数小于等于 6 时,树结构还原成链表。因为红黑树的平均查找长度是 log(n),长度为 8 的时候,平均查找长度为 3,如果继续使用链表,平均查找长度为 8/2=4, 这才有转换为树的必要。链表长度如果是小于等于 6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。还有选择 6 和 8,中间有个差值 7 可以有效防止链表和树频 繁转换。假设一下,如果设计成链表个数超过 8 则链表转换成树结构,链表个数小于 8 则树结构 转换成链表,如果一个 HashMap 不停的插入、删除元素,链表个数在 8 左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。

3、如果 hashMap 的 key 是一个自定义的类,怎么办?

考点:集合
参考回答:
使用 HashMap,如果 key 是自定义的类,就必须重写 hashcode()和 equals()

4、ArrayList 和 LinkedList 的区别,如果一直在 list 的尾部添加元素,用哪 个效率高?

考点:集合
参考回答:
ArrayList 采用数组数组实现的,查找效率比 LinkedList 高。LinkedList 采用双向链表实 现的,插入和删除的效率比 ArrayList 要高。一直在 list 的尾部添加元素,LinkedList 效率要高

5、HashMap 底层,负载因子,为啥是 2^n?

考点:集合
参考回答:
负载因子默认是 0.75, 2^n 是为了让散列更加均匀,例如出现极端情况都散列在数组中的一个下标,那么 hashmap 会由 O(1)复杂退化为 O(n)的。

6、ConcurrentHashMap 锁加在了哪些地方?

考点:集合
参考回答:
加在每个 Segment 上面。

7、TreeMap 底层,红黑树原理?

考点:集合
参考回答:
TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点。
红黑树的插入、删除、遍历时间复杂度都为 O(lgN),所以性能上低于哈希表。但是哈希表无法提供键值对的有序输出,红黑树因为是排序插入的,可以按照键的值的大小有序输出。红黑树性质:
性质 1:每个节点要么是红色,要么是黑色
性质 2:根节点永远是黑色的
性质 3:所有的叶节点都是空节点(即 null),并且是黑色的
性质 4:每个红色节点的两个子节点都是黑色。(从每个叶子到根的路径上不会有两个连续 的红色节点)
性质 5:从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点

8、concurrentHashMap 有啥优势,1.7,1.8 区别?

考点:集合
参考回答:
Concurrenthashmap 线程安全的
1.7 是在 jdk1.7 中采用 Segment + HashEntry 的方式进 行实现的, lock 加在 Segment 上面
1.7size 计算是先采用不加锁的方式,连续计算元素的个数, 最多计算 3 次:
1、如果前后两次计算结果相同,则说明计算出来的元素个数是准确的;
2、如果 前后两次计算结果都不同,则给每个 Segment 进行加锁,再计算一次元素的个数

1.8 中放弃了 Segment 臃肿的设计,取而代之的是采用 Node + CAS + Synchronized 来 保证并发安全进行实现,
1.8 中使用一个 volatile 类型的变量 baseCount 记录元素的个数,当 插入新数据或删除数据时,会通过 addCount()方法更新 baseCount,通过累加 baseCount 和 CounterCell 数组中的数量,即可得到元素的总个数;

9、ArrayList 是否会越界?

考点:集合
参考回答:
ArrayList 是实现了基于动态数组的数据结构, ArrayList 并发 add()可能出现数组下标越界异常

10、什么是 TreeMap?

考察点:key-value 集合
参考回答:
TreeMap 是一个有序key-value 集合,基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序, 具体取决于使用的构造方法。
TreeMap 的特性:

  • 根节点是黑色
  • 每个节点都只能是红色或者黑色
  • 每个叶节点(NIL 节点,空节点)是黑色的
  • 如果一个节点是红色的,则它两个子节点都是黑色的,也就是说在一条路径上不能出现两个 红色的节点
  • 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点

11、ConcurrentHashMap 的原理是什么?

考察点:JAVA 内存模型
参考回答:
jdk1.7:
ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment。
HashEntry 用来封装映射表的键值对;
Segment 用来充当锁的角色每个 Segment 对象守护整个散列映射表的若干个桶。每个桶是由若干个 HashEntry 对象链接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组。HashEntry 用来封装散列映射表中的键值对。
在 HashEntry 类中, key, hash 和 next 域都被声明为 final 型, value 域被声明为 volatile 型。

static final class HashEntry<K,V> {
	final K key; // 声明key 为 final 型
	final int hash; // 声明 hash值为 final 型
	volatile V value; // 声明 value 为volatile 型
	final HashEntry<K,V> next; // 声明 next 为 final 型
	HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
		this.key = key;
		this.hash = hash;
		this.next = next;
		this.value = value;
	}
}

在 ConcurrentHashMap 中,在散列时如果产生“碰撞”,将采用“分离链接法”来处理“碰撞”:把“碰撞”的 HashEntry 对象链接成一个链表。由于 HashEntry 的 next 域为 final 型, 所以新节点只能在链表的表头处插入。 下图是在一个空桶中依次插入 A,B,C 三个 HashEntry 对象后的结构图:
图 1. 插入三个节点后桶的结构示意图:
在这里插入图片描述
注意:由于只能在表头插入,所以链表中节点的顺序和插入的顺序相反
Segment 类继承于 ReentrantLock 类,从而使得 Segment 对象能充当锁的角色。每个 Segment 对象用来守护其(成员对象 table 中)包含的若干个桶。

12、Java 集合类框架的基本接口有哪些?

考察点:JAVA 集合
参考回答:
集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它 自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。 Java 集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java 集合类里面最基 本的接口有: Collection:代表一组对象,每一个对象都是它的子元素。 Set:不包含重复元素的 Collection。 List:可以包含重复元素。 Map:可以把键(key)映射到值(value)的对象,键不能重复

13、为什么集合类没有实现 Cloneable 和 Serializable 接口?

考察点:JAVA 集合
参考回答:
克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此, 应该由集合类的具体实现来决定如何被克隆或者是序列化
实现 Serializable 序列化的作用:将对象的状态保存在存储媒体中以便可以在以后重写创建出完全相同的副本;按值将对象从一个从一个应用程序域发向另一个应用程序域
实现 Serializable 接口的作用就是可以把对象存到字节流,然后可以恢复。所以如果对象没有序列化,就不能进行网络传输,要网络传输就得转为字节流,所以在分布式应用中,就得实现序列化。如果你不需要分布式应用,那就没必要实现实现序列化

14、什么是迭代器?

考察点:JAVA 迭代器
参考回答:
Iterator 提供了统一遍历操作集合元素的统一接口, Collection 接口实现 Iterable 接
口,每个集合都通过实现 Iterable 接口中 iterator()方法返回 Iterator 接口的实例, 然后对集合的元素进行迭代操作. 有一点需要注意的是:在迭代元素的时候不能通过集合的方法删除元素, 否则会抛出 ConcurrentModificationException 异常. 但是可以通过 Iterator 接口中的 remove()方法进行删除.

15、Iterator 和 ListIterator 的区别是什么?

考察点:迭代器
参考回答:
Iterator 和 ListIterator 的区别是:

  • Iterator 可用来遍历 Set 和 List 集合,但是 ListIterator 只能用来遍历 List
  • Iterator 对集合只能是前向遍历,ListIterator 既可以前向也可以后向
  • ListIterator 实现了 Iterator 接口,并包含其他的功能,比如:增加元素,替换元素,获取前 一个和后一个元素的索引,等等

16、快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?

考察点:集合
参考回答:
快速失败(fail—fast):
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)

安全失败(fail—safe):
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景**:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改**。

17、HashMap 和 Hashtable 有什么区别?

考察点:集合
参考回答:
HashMap 和 Hashtable 都实现了 Map 接口,因此很多特性非常相似。但是,他们有以下不同
点:

  • HashMap 允许键和值是 null,而 Hashtable 不允许键或者值是 null

  • Hashtable 是同步的,而 HashMap 不是。因此,HashMap 更适合于单线程环境,而 Hashtable 适合于多线程环境

  • HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的

  • HashTable 提供了对键的列举(Enumeration)。 一般认为 HashTable 是一个遗留的类

  • HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75

  • 两者计算hash的方法不同 ,Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取而HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取模

    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    

18、ArrayList 和 LinkedList 有什么区别?

考察点:ArrayList
参考回答:
ArrayList 和 LinkedList 都实现了 List 接口,他们有以下的不同点:

  • ArrayList 是基于索引的数据接口,它的底层是数组。它可以以 O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList 是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是 O(n)
  • 相对于 ArrayList,LinkedList 的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引
  • LinkedList 比 ArrayList 更占内存,因为 LinkedList 为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素

19、ArrayList,Vector,LinkedList 的存储性能和特性是什么?

考察点:ArrayList
参考回答:
ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便 增加和插入元素,它们都允许直接按序号索 引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢Vector 由于使用了 synchronized 方法(线程 安全),通常性能上较 ArrayList 差而 LinkedList 使用双向链表实现存储,按序号索引数据 需要进行前向或后向遍历,但是插入数 据时只需要记录本项的前后项即可,所以插入速度较快

20、Collection 和 Collections 的区别。

考察点:集合
参考回答:
Collection 是集合类的上级接口,继承他的接口主要有 Set 和 List.
Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、 线程安全化等操作

21、你所知道的集合类都有哪些?主要方法?

考察点:集合
参考回答:
最常用的集合类是 List 和 Map。 List 的具体实现包括 ArrayList 和 Vector,它们是可 变大小的列表,比较适合构建、存储和操 作任何类型对象的元素列表。 List 适用于按数值索引访问元素的情形。 Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作"键"和"值"),其 中每个键映射到一个值。

22、List、Set、Map 是否继承自 Collection 接口?

考察点:collection 接口
参考回答:
List、Set 是Map 不是。Map 是键值对映射容器,与 List 和 Set 有明显的区别,而 Set 存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List 是线性结构的容器, 适用于按数值索引访问元素的情形。

23、阐述 ArrayList、Vector、LinkedList 的存储性能和特性

考察点:ArrayList
参考回答:
ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便 增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,
Vector 中的方法由于添加了 synchronized 修饰,因此 Vector 是线程安全的容器,但性能上较 ArrayList 差,因此已经是 Java 中的遗留容器。
LinkedList 使用双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号 索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高),按序号 索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速 度较快。 Vector 属于遗留容器(Java 早期的版本中提供的容器,除此之外, Hashtable、 Dictionary、 BitSet、Stack、Properties 都是遗留容器),已经不推荐使用,但是由于 ArrayList 和 LinkedListed 都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具 类 Collections 中的 synchronizedList 方法将其转换成线程安全的容器后再使用(这是对装潢 模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)。

24、List、Map、Set 三个接口存取元素时,各有什么特点?

考察点:List
参考回答:
List 以特定索引来存取元素,可以有重复元素。
Set 不能存放重复元素(用对象的 equals() 方法来区分元素是否重复)。
Map 保存键值对(key-value pair)映射,映射关系可以是一对一 或多对一。
Set 和 Map 容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理 论存取时间复杂度为 O(1),而基于排序树版本的实现在插入或删除元素时会按照元素或元素的 键(key)构成排序树从而达到排序和去重的效果。

2、Java 高级知识

①线程

1、多线程中的 i++线程安全吗?为什么?

考察点:多线程
参考回答:
不安全。i++不是原子性操作。i++分为读取 i 值,对 i 值加一,再赋值给 i++,执行期中任 何一步都是有可能被其他线程抢占的。

2、如何线程安全的实现一个计数器?

考察点:多线程
参考回答:
可以使用加锁,比如 synchronized 或者 lock。也可以使用 Concurrent 包下的原子类

3、多线程同步的方法

考察点:多线程
参考回答:
可以使用 synchronized、lock、volatile 和 ThreadLocal 来实现同步。

4、介绍一下生产者消费者模式?

考察点:线程
参考回答:
在这里插入图片描述
生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储 空间,生产者向空间里生产数据,而消费者取走数据。
优点:支持并发、解耦。

5、线程,进程,然后线程创建有很大开销,怎么优化?

考察点:多线程
参考回答:
可以使用线程池

6、线程池运行流程,参数,策略

考察点:线程池
参考回答:
线程池主要就是指定线程池核心线程数大小,最大线程数,存储的队列,拒绝策略,空闲线 程存活时长。**当需要任务大于核心线程数时候,就开始把任务往存储任务的队列里,当存储队列满了的话,就开始增加线程池创建的线程数量,如果当线程数量也达到了最大,就开始执行拒绝策略,**比如说记录日志,直接丢弃,或者丢弃最老的任务。

7、讲一下 AQS 吧。

考察点:多线程
参考回答:
AQS 其实就是一个可以给我们实现锁的框架
内部实现的关键是:先进先出的队列、state 状态
定义了内部类 ConditionObject
拥有两种线程模式独占模式和共享模式。
在 LOCK 包中的相关锁(常用的有 ReentrantLock、 ReadWriteLock)都是基于 AQS 来构建, 一般我们叫 AQS 为同步器。

8、创建线程的方法,哪个更好,为什么?

考察点:线程
参考回答:
13题
实现 Runnalbe 接口更好,使用实现 Runnable 接口的方式创建的线程可以处理同一资源,从 而实现资源的共享.

9、Java 中有几种方式启动一个线程?

考察点:线程
参考回答:
13题

10、Java 中有几种线程池?

考察点:线程池
参考回答:
1、newFixedThreadPool 创建一个指定工作线程数量的线程池。每当提交一个任务就创建一 个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
2、newCachedThreadPool 创建一个可缓存的线程池。这种类型的线程池特点是:
1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为 Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为 1 分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个 工作线程
3、newSingleThreadExecutor 创建一个单线程化的 Executor,即只创建唯一的工作者线程 来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个 线程是活动的
4、newScheduleThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执 行,类似于 Timer

11、线程池有什么好处?

考察点:线程池
参考回答:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能执行
  • 提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源, 还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

12、cyclicbarrier 和 countdownlatch 的区别

考察点:线程
参考回答:
CountDownLatch 和 CyclicBarrier 都能够实现线程之间的等待,只不过它们侧重点不同:

  • CountDownLatch 一般用于某个线程 A 等待若干个其他线程执行完任务之后,它才执行
  • CyclicBarrier 一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行
  • CountDownLatch 是不能够重用的,而 CyclicBarrier 是可以重用的

13、启动线程有哪几种方式,线程池有哪几种?

考察点:线程池
参考回答:
启动线程有如下三种方式:
一、继承 Thread 类创建线程类
(1)定义 Thread 类的子类,并重写该类的 run 方法,该 run 方法的方法体就代表了线程要 完成的任务。因此把 run()方法称为执行体。
(2)创建 Thread 子类的实例,即创建了线程对象。
(3)调用线程对象的 start()方法来启动该线程。
代码:

public class FirstThreadTest extends Thread{
	int i = 0;
	//重写 run 方法,run 方法的方法体就是现场执行体
	public void run(){
		for(;i<100;i++){
		System.out.println(getName()+" "+i);
		}
	}
	
	public static void main(String[] args){
		for(int i = 0;i< 100;i++){
			System.out.println(Thread.currentThread().getName()+" : "+i);
			if(i==20)
			{
				new FirstThreadTest().start();
				new FirstThreadTest().start();
			}
		}
	}
}

上述代码中 Thread.currentThread()方法返回当前正在执行的线程对象。GetName()方法返 回调用该方法的线程的名字。
二、通过 Runnable 接口创建线程类
(1)定义 runnable 接口的实现类,并重写该接口的 run()方法,该 run()方法的方法体同样 是该线程的线程执行体。
(2)创建 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创建 Thread 对象, 该 Thread 对象才是真正的线程对象。
(3)调用线程对象的 start()方法来启动该线程。
代码:

public class RunnableThreadTest implements Runnable
{
	private int i;
	public void run(){
		for(i = 0;i <100;i++){
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
	}
	public static void main(String[] args){
		for(int i = 0;i < 100;i++){
			System.out.println(Thread.currentThread().getName()+" "+i);
			if(i==20){
				RunnableThreadTest rtt = new RunnableThreadTest();
				new Thread(rtt,"新线程 1").start();
				new Thread(rtt,"新线程 2").start();
			}
		}
	}
}

三、通过 Callable 和 Future 创建线程
(1)创建 Callable 接口的实现类,并实现 call()方法,该 call()方法将作为线程执行体, 并且有返回值。
(2)创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call()方法的返回值。
(3)使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
(4)调用 FutureTask 对象的 get()方法来获得子线程执行结束后的返回值
代码:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableThreadTest implements Callable<Integer>
{
	public static void main(String[] args){
		CallableThreadTest ctt = new CallableThreadTest();
		FutureTask<Integer> ft = new FutureTask<>(ctt);
		for(int i = 0;i < 100;i++){
			System.out.println(Thread.currentThread().getName()+" 的循环变量 i 的值"+i);
			if(i==20){
			new Thread(ft,"有返回值的线程").start();
			}
		}
		try{
			System.out.println("子线程的返回值:"+ft.get());
		} catch (InterruptedException e){
			e.printStackTrace();
		} catch (ExecutionException e){
			e.printStackTrace();
		}
	}
	
	@Override
	public Integer call() throws Exception{
		int i = 0;
		for(;i<100;i++)
		{
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
		return i;
	}
}

线程池的种类有:
Java 通过 Executors 提供四种线程池,分别为:

  1. newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲 线程,若无可回收,则新建线程
  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行
  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务, 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

14、如何理解 Java 多线程回调方法?

考察点:JAVA 多线程
参考回答:
所谓回调,就是客户程序 C 调用服务程序 S 中的某个方法 A,然后 S 又在某个时候反过来调 用 C 中的某个方法 B,对于 C 来说,这个 B 便叫做回调方法

15、创建线程有几种不同的方式?你喜欢哪一种?为什么?

考察点:JAVA 线程
参考回答:
13题

16、概括的解释下线程的几种可用状态。

考察点:JAVA 线程状态
参考回答:

  1. 新建( new ):新创建了一个线程对象
  2. 就绪( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。
  3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) , 执行程序代码。
  4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有机会再次获得 cpu timeslice 转到运行( running )状态。

阻塞的情况分三种:

  • 等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待 队列( waitting queue )中。
  • 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁 被别的线程占用, 则 JVM 会把该线程放入锁池( lock pool )中
  • 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join () 方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。 当 sleep ()状态超时、join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态
  1. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则 该线程结束生命周期。死亡的线程不可再次复生

17、同步方法和同步代码块的区别是什么?

考察点:JAVA 代码块同步
参考回答:
区别:

  • 同步方法默认用 this 或者当前类 class 对象作为锁
  • 同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法

18、在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?

考察点:JAVA 线程同步
参考回答:
监视器和锁在 Java 虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一 个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码

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

考察点:线程
参考回答:

  • sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程, 但是监控状态依然保持,到时后会自动 恢复。调用 sleep 不会释放对象锁
  • wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的 等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态

20、同步和异步有何异同,在什么情况下分别使用他们?举例说明。

考察点:线程同步
参考回答:
如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数 据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。 当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的 返回时,就应该使用异步编程,在很 多情况下采用异步途径往往更有效率。

21、设计 4 个线程,其中两个线程每次对 j 增加 1,另外两个线程对 j 每次减少 1。使用内部类实现线程,对 j 增减的时候没有考虑顺序问题。

考察点:JAVA 线程
参考回答:

public class ThreadTest1{ 
	private int j; 
	public static void main(String args[]){ 
		ThreadTest1 tt=new ThreadTest1(); 
		Inc inc=tt.new Inc();
		Dec dec=tt.new Dec(); 
		for(int i=0;i<2;i++){ 
			Thread t=new Thread(inc); 
			t.start(); 
			t=new Thread(dec); 
			t.start(); 
		} 
	} 

	private synchronized void inc(){ 
		j++; 
		System.out.println(Thread.currentThread().getName()+"-inc:"+j); 
	} 

	private synchronized void dec(){ 
		j--; 
		System.out.println(Thread.currentThread().getName()+"-dec:"+j); 
	} 
	class Inc implements Runnable{
		public void run(){
			for(int i=0;i<100;i++){ 
				inc(); 
			} 
		} 
	} 
	class Dec implements Runnable{ 
		public void run(){ 
			for(int i=0;i<100;i++){ 
				dec(); 
			} 
		} 
	} 
  }

22、启动一个线程是用 run()还是 start()?

考察点:JAVA 线程
参考回答:
启动一个线程是调用 start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着 它可以由 JVM 调度并执行。这并不意味着线 程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。

23、请说出你所知道的线程同步的方法

考察点:线程同步
参考回答:

  • wait():使一个线程处于等待状态,并且释放所持有的对象的 lock
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉 InterruptedException 异常
  • notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定 唤醒哪个线程,而且不是按优先级
  • notifyAll():唤醒所有处入等待状态的线程,并不是给所有唤醒线程一个对象的锁,而是让它们竞争

24、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?

考察点:线程
参考回答:
13题
同步的实现方法有:
1.使用synchronized关键字修饰类或者代码块;
2.使用Volatile关键字修饰变量;
3.在类中加入重入锁。

ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁

25、 java 中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop() 和 suspend()方法为何不推荐使用?

考察点:线程
参考回答:
13题

  • 反对使用 stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对 象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的 问题所在
  • **suspend()方法容易发生死锁。调用 suspend()的时候,目标线程会停下来,但却仍 然持有在这之前获得的锁定。**此时,其他任何线程都不能访问锁定的资源,除非被”挂起”的线 程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源, 就会造成死锁。所以不应该使用 suspend(),而应在自己的 Thread 类中置入一个标志,指出线 程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状态。若标志指出 线程应当恢复,则用一个 notify()重新启动线程。

26、线程的 sleep()方法和 yield()方法有什么区别?

考察点:线程
参考回答:

  • sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会,yield()方法只会给相同优先级或更高优先级的线程以运行的机会
  • 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready) 状态
  • sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常
  • sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性

27、当一个线程进入一个对象的 synchronized 方法 A 之后,其它线程是否可进 入此对象的 synchronized 方法 B?

考察点:线程
参考回答:
不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的 synchronized 修饰符要求执行方法时要获得对象的锁,如果已经进入 A 方法说明对象锁已经被 取走,那么试图进入 B 方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。

28、请说出与线程同步以及线程调度相关的方法。

考察点:线程同步
参考回答:

  • wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常
  • notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一 个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关
  • notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让 它们竞争,只有获得锁的线程才能进入就绪状态;
  • 通过 Lock 接口提供了显式的锁机制(explicit lock),增强了灵活性以及对线程的协调。 Lock 接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了 newCondition() 方法来产生用于线程之间通信的 Condition 对象;此外, Java 5 还提供了信号量机制(semaphore), 信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须 得到信号量的许可(调用 Semaphore 对象的 acquire()方法);在完成对资源的访问后,线程必 须向信号量归还许可(调用 Semaphore 对象的 release()方法)

29、举例说明同步和异步

考察点:线程
参考回答:
如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据 以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就 必须进行同步存取(数据库操作中的排他锁就是最好的例子)。当应用程序在对象上调用了一个 需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程, 在很多情况下采用异步途径往往更有效率。事实上,所谓的同步就是指阻塞式操作,而异步就是 非阻塞式操作。

30、什么是线程池(thread pool)?

考察点:线程池
参考回答:
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者 其它更多资源。在 Java 中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进 行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是 一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建, 使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。 Java 5+中的 Executor 接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情 况下,因此在工具类 Executors 面提供了一些静态工厂方法,生成一些常用的线程池:

  • **newSingleThreadExecutor:创建一个单线程的线程池。**这个线程池只有一个线程在工作,也 就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线 程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行
  • **newFixedThreadPool:创建固定大小的线程池。**每次提交一个任务就创建一个线程,直到线程 达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异 常而结束,那么线程池会补充一个新线程
  • **newCachedThreadPool:创建一个可缓存的线程池。**如果线程池的大小超过了处理任务所需要 的线程,那么就会回收部分空闲(60 秒不执行任务)的线程,当任务数增加时,此线程池又可 以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操 作系统(或者说 JVM)能够创建的最大线程大小
  • **newScheduledThreadPool:创建一个大小无限的线程池。**此线程池支持定时以及周期性执行任 务的需求

31、说说线程的基本状态以及状态之间的关系?

考察点:线程
参考回答:
16题

32、如何保证线程安全?

考察点:线程
参考回答:
通过合理的时间调度,避开共享资源的存取冲突。另外,在并行任务设计上可以通过适当的 策略,保证任务与任务之间不存在共享资源,设计一个规则来保证一个客户的计算工作和数据访 问只会被一个线程或一台工作机完成,而不是把一个客户的计算工作分配给多个线程去完成。

②锁

1、讲一下非公平锁和公平锁在 reetrantlock 里的实现。

考察点:锁
参考回答:
如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,FIFO。
对于非公 平锁,只要 CAS 设置同步状态成功,则表示当前线程获取了锁,而公平锁还需要判断当前节点是 否有前驱节点,如果有,则表示有线程比当前线程更早请求获取锁,因此需要等待前驱线程获取 并释放锁之后才能继续获取锁。

2、讲一下 synchronized,可重入怎么实现。

考察点:锁
参考回答:
每个锁关联一个线程持有者和一个计数器。当计数器为 0 时表示该锁没有被任何线程持有, 那么任何线程都都可能获得该锁而调用相应方法。当一个线程请求成功后,JVM 会记下持有锁的 线程,并将计数器计为 1。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请 求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个 synchronized 方法/ 块时,计数器会递减,如果计数器为 0 则释放该锁。

3、锁和同步的区别

考察点:锁
参考回答:

类别 synchronized Lock
存在层次 Java的关键字,在jvm层面上 是一个接口
锁的释放 1.以获取锁的线程执行完同步代码,释放锁
2.线程执行发生异常,jvm会让线程释放锁
必须在finally中释放锁,不然容易造成线程死锁
锁的获取 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 Lock有多种获取锁的方式,如lock、tryLock
状态 无法判断,只能阻塞 可以判断
tryLock(); tryLock(long time, TimeUnit unit) 可避免死锁
类型 可重入,非公平,不可中断 可重入,可公平 可中断:lockInterruptibly();
功能 功能单一 API丰富 tryLock();tryLock(long time, TimeUnit unit)可避免死锁

4、什么是死锁(deadlock)?

考察点:线程死锁
参考回答:
两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是这些线程都陷入了无限的等待中。
例如,如果线程 1 锁住了 A,然后尝试对 B 进行加锁,同时线程 2 已经锁住了 B,接着尝试 对 A 进行加锁,这时死锁就发生了。线程 1 永远得不到 B,线程 2 也永远得不到 A,并且它们永远也不会知道发生了这样的事情。为了得到彼此的对象(A 和 B),它们将永远阻塞下去。这种 情况就是一个死锁。

5、如何确保 N 个线程可以访问 N 个资源同时又不导致死锁?

考察点:死锁
参考回答:
使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死 锁了。
预防死锁,预先破坏产生死锁的四个条件互斥不可能破坏,所以有如下三种方法:
1.破坏请求和保持条件,进程必须等所有要请求的资源都空闲时才能申请资源,这种方法会使 资源浪费严重(有些资源可能仅在运行初期或结束时才使用,甚至根本不使用). 允许进程获取初期所需资源后,便开始运行,运行过程中再逐步释放自己占有的资源,比如有一个进程的任务是把数据复制到磁盘中再打印,前期只需获得磁盘资源而不需要获得打印机资源,待复制完毕后再释放掉磁盘资源。这种方法比第一种方法好,会使资源利用率上升。
2.破坏不可抢占条件,这种方法代价大,实现复杂。
3.破坏循坏等待条件,对各进程请求资源的顺序做一个规定,避免相互等待。这种方法对资 源的利用率比前两种都高,但是前期要为设备指定序号,新设备加入会有一个问题,其次对用户 编程也有限制。

6、请你简述 synchronized 和 java.util.concurrent.locks.Lock 的异同?

考察点:锁机制
参考回答:
3题

③JDK

1、Java 中的 LongAdder 和 AtomicLong 的区别

考点:JDK
参考回答:
JDK1.8 引入了 LongAdder 类。CAS 机制就是,在一个死循环内,不断尝试修改目标值,直到 修改成功。如果竞争不激烈,那么修改成功的概率就很高,否则,修改失败的的概率就很高,在 大量修改失败时,这些原子操作就会进行多次循环尝试,因此性能就会受到影响。 结合 ConcurrentHashMap的实现思想, 应该可以想到对一种传统AtomicInteger等原子类的改进思路。 虽然 CAS 操作没有锁,但是像减少粒度这种分离热点的思想依然可以使用。将 AtomicInteger的内部核心数据 value 分离成一个数组,每个线程访问时,通过哈希等算法映射到其中一个数字 进行计数,而最终的计数结果,则为这个数组的求和累加。热点数据 value 被分离成多个单元 cell,每个 cell 独自维护内部的值,当前对象的实际值由所有的 cell 累计合成,这样热点就进 行了有效的分离,提高了并行度。

2、JDK 和 JRE 的区别是什么?

考察点:JDK
参考回答:
Java 运行时环境(JRE)是将要执行 Java 程序的 Java 虚拟机。它同时也包含了执行 applet 需要的浏览器插件。Java 开发工具包(JDK)是完整的 Java 软件开发包,包含了 JRE,编译器和其 他的工具(比如:JavaDoc,Java 调试器),可以让开发者开发、编译、执行 Java 应用程序。

④反射

1、反射的实现与作用

考察点:反射
参考回答;
JAVA 语言编译之后会生成一个.class 文件,反射就是通过字节码文件找到某一个类、类中 的方法以及属性等。反射的实现主要借助以下四个类:Class:类的对象,Constructor:类的构 造方法,Field:类中的属性对象,Method:类中的方法对象。
作用:反射机制指的是程序在运行时能够获取自身的信息。在 JAVA 中,只要给定类的名字, 那么就可以通过反射机制来获取类的所有信息

⑤JVM

1、JVM 回收算法和回收器,CMS 采用哪种回收算法,怎么解决内存碎片问题?

考察点:JVM
参考回答:
https://blog.csdn.net/qq_36254699/article/details/102794635

2、类加载过程

考察点:JVM
参考回答:
如下图所示,JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们 就分别来看一下这五个过程。
在这里插入图片描述
加载
加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行 时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)。
验证
这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的 要求,并且不会危害虚拟机自身的安全。
准备
准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量 所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:
public static int v = 8080;
实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080,将 v 赋值为 8080 的 putstatic 指 令是程序被编译后,存放于类构造器<client>方法之中,这里我们后面会解释。 但是注意如果声明为:
public static final int v = 8080;
在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性 将 v 赋值为 8080。
解析
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的:
CONSTANT_Class_info
CONSTANT_Field_info
CONSTANT_Method_info
等类型的常量。
下面我们解释一下符号引用和直接引用的概念:
符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机 实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面 量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。
直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了 直接引用,那引用的目标必定已经在内存中存在。
初始化
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类 加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代 码。
初始化阶段是执行类构造器<client>方法的过程。 <client>方法是由编译器自动收集类中的 类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证<client>方法执行之前,父 类的<client>方法已经执行完毕。p.s: 如果一个类中没有对静态变量赋值也没有静态语句块, 那么编译器可以不为这个类生成<client>()方法。
注意以下几种情况不会执行类初始化:
通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
定义对象数组,不会触发该类的初始化。
常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发 定义常量所在的类。
通过类名获取 Class 对象,不会触发类的初始化。
通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类 初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。
类加载器
虚拟机设计团队把加载动作放到 JVM 外部实现,以便让应用程序决定如何获取所需的类, JVM 提供了 3 种类加载器:
启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中的,或通过 -Xbootclasspath 参数指定路径中的,且被虚拟机认可(按文件名识别,如 rt.jar)的类。
扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库。
应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。
JVM 通过双亲委派模型进行类的加载,当然我们也可以通过继承 java.lang.ClassLoader 实 现自定义的类加载器。
在这里插入图片描述
当一个类加载器收到类加载任务,会先交给其父类加载器去完成,因此最终加载任务都会传 递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试执行加载任务。采 用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器 加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器 最终得到的都是同样一个 Object 对象。

3、JVM 分区

考察点:JVM
参考回答:
在这里插入图片描述
java 内存通常被划分为 5 个区域:程序计数器(Program Count Register)、本地方法栈 (Native Stack)、方法区(Methon Area)、(Stack)、(Heap)。

4、eden 区,survial 区?

考察点:JVM
参考回答:
目前主流的虚拟机实现都采用了分代收集的思想,把整个堆区划分为新生代和老年代;新生 代又被划分成 Eden 空间、 From Survivor 和 To Survivor 三块区域。
我们把 Eden : From Survivor : To Survivor 空间大小设成 8 : 1 : 1 ,对象总是在 Eden 区出生, From Survivor 保存当前的幸存对象, To Survivor 为空。
一次 gc 发生后:

  1. Eden 区活着的对象 + From Survivor 存储的对象被复制到 To Survivor ;
  2. 清空 Eden 和 From Survivor ;
  3. 颠倒 From Survivor 和 To Survivor 的逻辑关系: From 变 To , To 变 From 。

可以看出,只有在 Eden 空间快满的时候才会触发 Minor GC 。 而 Eden 空间占新生代的绝大部分,所以 Minor GC 的频率得以降低。当然,使用两个 Survivor 这种方式我们也付出了一定的代价,如 10% 的空间浪费、复制对象的开销等。

5、JAVA 虚拟机的作用?

考察点:java 虚拟机
参考回答:
解释运行字节码程序 消除平台相关性。
jvm 将 java 字节码解释为具体平台的具体指令。一般的高级语言如要在不同的平台上运行, 至少需要编译成不同的目标代码。而引入 JVM 后,Java 语言在不同平台上运行时不需要重新编 译。Java 语言使用模式 Java 虚拟机屏蔽了与具体平台相关的信息,使得 Java 语言编译程序只 需生成在 Java 虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java 虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。
假设一个场景,要求 stop the world 时间非常短,你会怎么设计垃圾回收机制?
绝大多数新创建的对象分配在 Eden 区。
在 Eden 区发生一次 GC 后,存活的对象移到其中一个 Survivor 区。
在 Eden 区发生一次 GC 后,对象是存放到 Survivor 区,这个 Survivor 区已经存在其他存活 的对象。
一旦一个 Survivor 区已满,存活的对象移动到另外一个 Survivor 区。然后之前那个空间已 满 Survivor 区将置为空,没有任何数据。
经过重复多次这样的步骤后依旧存活的对象将被移到老年代。

6、GC 中如何判断对象需要被回收?

考察点:JAVA 虚拟机
参考回答:
即使在可达性分析算法中不可达的对象,也并非是“非回收不可”的,这时候它们暂时处于 “等待”阶段,要真正宣告一个对象回收,至少要经历两次标记过程:如果对象在进行可达性分析 后发现没有与 GC Roots 相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件 是此对象是否有必要执行 finalize()方法。当对象没有覆盖 finalize()方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。(即意味着直接回收)
**如果这个对象被判定为有必要执行 finalize()方法,那么这个对象将会放置在一个叫做 F-Queue 的队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的 Finalizer 线程去执行 它。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束,**这样做的原 因是,如果一个对象在 finalize()方法中执行缓慢,或者发生了死循环(更极端的情况),将很可 能会导致 F-Queue 队列中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。
finalize()方法是对象逃脱回收的最后一次机会,稍后GC将对F-Queue中的对象进行第二次 小规模的标记,如果对象要在 finalize()中跳出回收——只要重新与引用链上的任何一个对象 建立关联即可,譬如把自己(this 关键字)赋值给某个类变量或者对象的成员变量,那在第二次标 记时它将被移除出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的被回收 了。

7、JAVA 虚拟机中,哪些可作为 ROOT 对象?

考察点:JAVA 虚拟机
参考回答:

  • 虚拟机栈中的引用对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用对象
  • 本地方法栈中 JNI 引用对象

8、JVM 内存模型是什么?

考察点:JVM 内存模型
参考回答:
Java 内存模型(简称 JMM),JMM 决定一个线程对共享变量的写入何时对另一个线程可见。从 抽象的角度来看,JMM 定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存 (main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该 线程以读/写共享变量的副本。
本地内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其 他的硬件和编译器优化。其关系模型图如下图所示:
在这里插入图片描述

9、jvm 是如何实现线程?

考察点:JVM
参考回答:
线程是比进程更轻量级的调度执行单位。线程可以把一个进程的资源分配和执行调度分开。 一个进程里可以启动多条线程,各个线程可共享该进程的资源(内存地址,文件 IO 等),又可以 独立调度。线程是 CPU 调度的基本单位。
主流 OS 都提供线程实现。Java 语言提供对线程操作的同一 API,每个已经执行 start(), 且还未结束的 java.lang.Thread 类的实例,代表了一个线程。
Thread 类的关键方法,都声明为 Native。这意味着这个方法无法或没有使用平台无关的手 段来实现,也可能是为了执行效率。
实现线程的方式
A.使用内核线程实现内核线程(Kernel-Level Thread, KLT)就是直接由操作系统内核支持 的线程。
内核来完成线程切换
内核通过调度器 Scheduler 调度线程,并将线程的任务映射到各个 CPU 上
程序使用内核线程的高级接口,轻量级进程(Light Weight Process,LWP)
用户态和内核态切换消耗内核资源
使用用户线程实现
系统内核不能感知线程存在的实现
用户线程的建立、同步、销毁和调度完全在用户态中完成
所有线程操作需要用户程序自己处理,复杂度高
用户线程加轻量级进程混合实现
轻量级进程作为用户线程和内核线程之间的桥梁

10、jvm 最大内存限制多少

考察点:JVM
参考回答:
(1)堆内存分配
JVM 初始分配的内存由-Xms 指定,默认是物理内存的 1/64;JVM 最大分配的内存由-Xmx 指 定,默认是物理内存的 1/4。默认空余堆内存小 于 40%时, JVM 就会增大堆直到-Xmx 的最大限制; 空余堆内存大于 70%时,JVM 会减少堆直到-Xms 的最小限制。因此服务器一般设置-Xms、 -Xmx 相等以避免在每次 GC 后调整堆的大小。
(2)非堆内存分配
JVM 使用-XX:PermSize 设置非堆内存初始值,默认是物理内存的 1/64;由 XX:MaxPermSize 设置最大非堆内存的大小,默认是物理内存的 1/4。
(3)VM 最大内存
首先 JVM 内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM 内存的最大值跟 操作系统有很大的关系。简单的说就 32 位处理器虽 然可控内存空间有 4GB,但是具体的操作系 统会给一个限制,这个限制一般是 2GB-3GB(一般来说 Windows 系统下为 1.5G-2G,Linux 系 统 下为 2G-3G),而 64bit 以上的处理器就不会有限制了。
(3)下面是当前比较流行的几个不同公司不同版本 JVM 最大堆内存:
在这里插入图片描述

11、什么是 Java 虚拟机?为什么 Java 被称作是“平台无关的编程语言”?

考察点:JVM
参考回答:
Java 虚拟机是一个可以执行 Java 字节码的虚拟机进程。Java 源文件被编译成能被 Java 虚 拟机执行的字节码文件。 Java 被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或 者是重新编译。Java 虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。

12、描述一下 JVM 加载 class 文件的原理机制?

考察点:JVM
参考回答:
JVM 中类的装载是由 ClassLoader 和它的子类来实现的,Java ClassLoader 是一个重要的 Java 运行时系统组件。它负责在运行时查找和装入类文件的类。
Java 中的所有类,都需要由类加载器装载到 JVM 中才能运行。类加载器本身也是一个类, 而它的工作就是把 class 文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的 加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要 的类。
类装载方式,有两种 (1)隐式装载, 程序在运行过程中当碰到通过 new 等方式生成对象时,隐式调用类装载器加载 对应的类到 jvm 中, (2)显式装载, 通过 class.forname()等方法,显式加载需要的类 , 隐式加载与显式加载的 区别:两者本质是一样的。
Java 类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行 的基础类(像是基类)完全加载到 jvm 中,至于其他类,则在需要的时候才加载。这当然就是为了 节省内存开销。

⑥GC

1、java 中内存泄露是啥,什么时候出现内存泄露?

考察点:内存泄漏
参考回答:
Java 中的内存泄露,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就 是内存泄露。如果长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露。

2、minor gc 如果运行的很频繁,可能是什么原因引起的,minor gc 如果运行的 很慢,可能是什么原因引起的?

考察点:GC
参考回答:
可能是堆内存太小

3、阐述 GC 算法

考察点:JVM
参考回答:
①GC(GarbageCollection 垃圾收集),GC 的对象是堆空间和永久区
②GC 算法包含:引用计数法,标记清除,标记压缩,复制算法
③引用计数器的实现很简单,对于一个对象 A,只要有任何一个对象引用了 A,则 A 的引用 计数器就加 1,当引用失效时,引用计数器就减 1。只要对象 A 的引用计数器的值为 0,则对象 A 就不可能再被使用。
④标记-清除算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段: 标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开 始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有 未被标记的对象。与标记-清除算法相比,复制算法是一种相对高效的回收方法不适用于存活对 象较多的场合如老年代将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对 象,交换两个内存的角色,完成垃圾回收。

4、GC 是什么? 为什么要有 GC?

考察点:回收
参考回答:
GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方, 忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监 测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显 示操作方法。

5、垃圾回收的优点和原理。并考虑 2 种回收机制

考察点:垃圾回收
参考回答:
Java 语言中一个显著的特点就是引入了垃圾回收机制,使 c++程序员最头疼的内存管理的 问题迎刃而解,它使得 Java 程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回 收机制,Java 中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"。垃圾回收可以 有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的 线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收, 程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。回收机制有分代复制垃 圾回收和标记垃圾回收,增量垃圾回收。

6、java 中会存在内存泄漏吗,请简单描述。

考察点:内存
参考回答:
Java 中的确存在 Java 的内存泄漏, 并且事态可以变得相当严重
Java garbage collector 自动释放哪些内存里面程序不在需要的对象, 以此避免大多数的 其他程序上下文的内存泄漏. 但是 Java 应用程序依旧会有相当的内存泄漏. 查找原因会十分困 难. 有两类主要的 Java 内存泄漏: * 不再需要的对象引用 * 未释放的系统资源 2.2 非必要的对象引用 Java 代码常常保留对于不再需要的对象引用, 并且这组织了内存的垃圾收集器的工作. Java 对 象通常被其他对象包含引用, 为此一个单一对象可以保持整个对象树在内存中, 于是导致了如 下问题: * 在向数组添加对象以后遗漏了对于他们的处理 * 直到你再次使用对象的时候都不释放引用. 比如一个菜单指令可以插件一个对象实例引用并 且不释放便于以后再次调用的时候使用, 但是也许永远不会发生. * 在其他引用依然需要旧有状态的时候贸然修改对象状态. 比如当你为了在一个文本文件里面 保存一些属性而使用一个数组, 诸如”字符个数”等字段在不再需要的时候依然保留在内存当 中。允许一个长久执行的线程所引用的对象. 设置引用为 NULL 也无济于事, 在线程退出和空闲之 前, 对象不会被收集释放 2.3 未释放的系统资源 Java 方法可以定位 Java 实例意外的堆内存, 诸如针对视窗和位图的内存资源. Java 常常通过 JNI(Java Native Interface)调用 C/C++子程序定位这些资源.

7、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办 法主动通知虚拟机进行垃圾回收?(垃圾回收)

考察点:垃圾回收
参考回答:
对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用情况。 通常,GC 采用有向图的方式记录和管理堆 (heap)中的所有对象。通过这种方式确定哪些对象是”可达的”,哪些对象是”不可达的”。当 GC 确定一些对象为”不可达”时,GC 就有责 任回收这些内存空间。可以。程序员可以手动执行 System.gc(),通知 GC 运行,但是 Java 语言 规范并不保证 GC 一定会执行。

⑦ IO 和 NIO,AIO

1、怎么打印日志?

考察点:异常
参考回答:
cat /var/log/*.log
如果日志在更新,如何实时查看 tail -f /var/log/messages
还可以使用 watch -d -n 1 cat /var/log/messages
-d 表示高亮不同的地方,-n 表示多少秒刷新一次。

2、运行时异常与一般异常有何异同?

考察点:异常
参考回答:
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能 遇到的异常,是一种常见运行错误。java 编译器要求方法必须声明抛出可能发生的非运行时异 常,但是并不要求必须声明抛出未被捕获的运行时异常。

3、error 和 exception 有什么区别?

考察点:异常
参考回答:
error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指 望程序能处理这样的情况。 exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。

4、给我一个你最常见到的 runtime exception

考察点:异常
参考回答:
ArithmeticException,
ArrayStoreException,
BufferOverflowException,
BufferUnderflowException,
CannotRedoException, CannotUndoException,
ClassCastException,
CMMException,
ConcurrentModificationException,
DOMException, EmptyStackException,
IllegalArgumentException,
IllegalMonitorStateException,
IllegalPathStateException, IllegalStateException,
ImagingOpException,
IndexOutOfBoundsException,
MissingResourceException, NegativeArraySizeException,
NoSuchElementException,
NullPointerException,
ProfileDataException,
ProviderException, RasterFormatException, SecurityException, SystemException, UndeclaredThrowableException, UnmodifiableSetException, UnsupportedOperationException

5、Java 中的异常处理机制的简单原理和应用。

考察点:异常
参考回答:
当 JAVA 程序违反了 JAVA 的语义规则时,JAVA 虚拟机就会将发生的错误表示为一个异常。 违反语义规则包括 2 种情况。一种是 JAVA 类库内置的语义检查。例如数组下标越界,会引发 IndexOutOfBoundsException;访问 null 的对象时会引发 NullPointerException。另一种情况就 是 JAVA 允许程序员扩展这种语义检查,程序员可以创建自己的异常,并自由选择在何时用 throw 关键字引发异常。所有的异常都是 java.lang.Thowable 的子类。

6、java 中有几种类型的流?JDK 为每种类型的流提供了一些抽象类以供继承, 请说出他们分别是哪些类?

考察点:stream
参考回答:
字节流,字符流。字节流继承于 InputStream OutputStream,字符流继承于 InputStreamReader OutputStreamWriter。在 java.io 包中还有许多其他的流,主要是为了提高 性能和使用方便。

7、什么是 java 序列化,如何实现 java 序列化?

考察点:序列化
参考回答:
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对 流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象 流进行读写操作时所引发的问题。 序列化的实现:将需要被序列化的类实现 Serializable 接口,该接口没有需要实现的方法, implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如: FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即 保存其状态),要恢复的话则用输入流。

8、运行时异常与受检异常有什么区别?

考察点:异常
参考回答:
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能 遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序 运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java 编译器要求 方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异 常和继承一样,是面向对象程序设计中经常被滥用的东西,在 Effective Java 中对异常的使用 给出了以下指导原则: - 不要将异常处理用于正常的控制流(设计良好的 API 不应该强迫它的调用者为了正常的控制流 而使用异常) - 对可以恢复的情况使用受检异常,对编程错误使用运行时异常 - 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生) - 优先使用标准的异常 - 每个方法抛出的异常都要有文档 - 保持异常的原子性 - 不要在 catch 中忽略掉捕获到的异常

发布了84 篇原创文章 · 获赞 23 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_36254699/article/details/100418374