Java面试题

一.Java基础

目录

一.Java基础

1.请你谈谈Java中是如何支持正则表达式操作的?

2.请你简单描述下正则表达式及其用途。

3.请你比较下Java和JavaScript。

4.请你说明一下,在Java中如何调出当前的多重嵌套循环?

5.请你讲讲&和&&的区别?

6. int和Integer有什么区别?

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

8.请你说明String和StringBuffer的区别

9.请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?

10.请你解释什么是值传递和引用传递?

11. 请你讲讲Java支持的数据类型有哪些?什么是自动拆装箱?

12.请你解释为什么会出现4.0-3.6=0.40000001这种现象?

13.请你讲讲一个十进制的数在内存中是怎么存的?

14.请你说说Lamda表达式的优缺点。

15. 请你说明符号“==”比较的是什么?

 16.请你解释Object若不重写hashCode()的话,hashCode()如何计算出来的?

17.请你解释为什么重写equals还要重写hashcode?

18.请你介绍下map的分类和常见的情况

19.请你讲讲Java里面的final关键字是怎么用的?

20.请你谈谈关于Synchronized和lock 

 21. 请你介绍一下volatile

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

23.请解释hashCode()和equals()方法有什么联系?

 24.请解释Java中的概念,什么是构造函数?什么是构造函数重载?什么是复制构造函数?

25. 请说明Java中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?

26.请你说说Static Nested Class 和 Inner Class的不同

 27.请说明Query接口的list方法和iterate方法有什么区别?

28. 请说明如何通过反射获取和设置对象私有字段的值?

29.请说明内部类可以引用他包含类的成员吗,如果可以,有没有什么限制吗?

30.请你讲讲abstract class和interface有什么区别?

31.请说明面向对象的特征有哪些方面

32. 请说明Comparable和Comparator接口的作用以及它们的区别。

33.请说明Java是否支持多继承?

34.请你谈谈如何通过反射创建对象?

35. 请你说明是否可以在static环境中访问非static变量?

36.请解释一下extends 和super 泛型限定符

 37.请你讲讲什么是泛型?

38.请说明静态变量存在什么位置?

39.请你解释一下类加载机制,双亲委派模型,好处是什么?

40. 请你谈谈StringBuffer和StringBuilder有什么区别,底层实现上呢?

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

42. 请列举你所知道的Object类的方法并简要说明。

43. 请解释一下String为什么不可变?

44.请你讲讲wait方法的底层原理

45.请说明List、Map、Set三个接口存取元素时,各有什么特点?

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

47.请说明Collection 和 Collections的区别。

48. 请简要说明ArrayList,Vector,LinkedList的存储性能和特性是什么?

49.请你说明HashMap和Hashtable的区别?  

50. 请说说快速失败(fail-fast)和安全失败(fail-safe)的区别?

51.  请你说说Iterator和ListIterator的区别?

52. 请简单说明一下什么是迭代器?

53.请解释为什么集合类没有实现Cloneable和Serializable接口? 

 54. 请你说明一下ConcurrentHashMap的原理?

55. 请你说明concurrenthashmap有什么优势以及1.7和1.8区别?

56.请解释一下TreeMap?

 57.请说明ArrayList是否会越界?

58.请你简单介绍一下ArrayList和LinkedList的区别,并说明如果一直在list的尾部添加元素,用哪种方式的效率高? 

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

60. 请你解释一下hashMap具体如何实现的?

61. 如何保证线程安全?

62.请你简要说明一下线程的基本状态以及状态之间的关系?

63. 请你解释一下什么是线程池(thread pool)?

64.举例说明同步和异步

 65.请介绍一下线程同步和线程调度的相关方法。

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

67.请简述一下线程的sleep()方法和yield()方法有什么区别?

68. 请回答以下几个问题: 第一个 问题:Java中有几种方法可以实现一个线程? 第二个问题:用什么关键字修饰同步方法?  第三个问题:stop()和suspend()方法为何不推荐使用,请说明原因?

69.  请说出你所知道的线程同步的方法

70.启动一个线程是用run()还是start()?

71.请使用内部类实现线程设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。

72. 请说明一下sleep() 和 wait() 有什么区别?

73. 请你说明一下在监视器(Monitor)内部,是如何做到线程同步的?在程序又应该做哪种级别的同步呢? 

74. 请分析一下同步方法和同步代码块的区别是什么?

75.请解释一下Java多线程回调是什么意思?

76.  请列举一下启动线程有哪几种方式,之后再说明一下线程池的种类都有哪些?

77.请简要说明一下JAVA中cyclicbarrier和countdownlatch的区别分别是什么?

78. 请说明一下线程池有什么优势?

79. 请简短说明一下你对AQS的理解。

80. 请简述一下线程池的运行流程,使用参数以及方法策略等

81.  如何在线程安全的情况下实现一个计数器?

 82. 多线程中的i++线程安全吗?请简述一下原因?

83.JAVA中如何确保N个线程可以访问N个资源,但同时又不导致死锁? 

84. 请讲一下非公平锁和公平锁在reetrantlock里的实现过程是怎样的。

85. Java中的LongAdder和AtomicLong有什么区别?

86.  请说明一下JAVA中反射的实现过程和作用分别是什么?

87.请简单描述一下JVM加载class文件的原理是什么?

88.jvm最大内存限制多少?

89.为什么Java被称作是“平台无关的编程语言”?

90.  jvm是如何实现线程的?

91.请问什么是JVM内存模型?

92. 请列举一下,在JAVA虚拟机中,哪些对象可作为ROOT对象?

93.GC中如何判断对象是否需要被回收?

94.请说明一下JAVA虚拟机的作用是什么?

95.请说明一下eden区和survial区的含义以及工作原理?

96. 请简单描述一下类的加载过程

 97.请简单说明一下JVM的回收算法以及它的回收器是什么?还有CMS采用哪种回收算法?使用CMS怎样解决内存碎片的问题呢?

98.请简单描述一下垃圾回收器的基本原理是什么?还有垃圾回收器可以马上回收内存吗?并且有什么办法可以主动通知虚拟机进行垃圾回收呢?

99. 请问,在java中会存在内存泄漏吗?请简单描述一下。

100. 什么原因会导致minor gc运行频繁?同样的,什么原因又会导致minor gc运行很慢?请简要说明一下

101. 请问运行时异常与受检异常有什么区别?

102.请问什么是java序列化?以及如何实现java序列化?

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

104.请问error和exception有什么区别?


1.请你谈谈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());
        }
    }
}

2.请你简单描述下正则表达式及其用途。

在编写处理字符串的程序时,经常会有查找复合某些负责规则的字符串需求,正则表达式就是用于描述这些规则的工具。

3.请你比较下Java和JavaScript。

java是一种可以撰写跨平台应用软件的面向对象的程序设计语言,即使是开发简单的程序,也必须设计对象;

而JavaScript是一种直译式脚本语言,它本身提供了非常丰富的内部对象供设计人员使用。

4.请你说明一下,在Java中如何调出当前的多重嵌套循环?

Lable标签.在最外层循环前加一个标记如A,然后用break A;可以跳出多重循环。

5.请你讲讲&和&&的区别?

①.非短路与

        语法: 布尔表达式a&布尔表达式b

        作用: 当a与b的值都为true时 整个表达式的值为true

         特点: 无论a是否为false  都会对b进行判断

②.短路与

         语法:布尔表达式a&&布尔表达式b

         作用: 当a与b的值都为true时 整个表达式的值为true 

         特点: 如果a为false 则不会判断b
 

6. int和Integer有什么区别?

1.为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每 一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。

基本数据类型: boolean,char,byte,short,int,long,float,double

封装类类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

 2 int与Integer的基本使用对比
Integer是int的包装类;int是基本数据类型;
Integer变量必须实例化后才能使用;int变量不需要;
Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
Integer的默认值是null;int的默认值是0。
更多:Java基础之int和Integer有什么区别_chenliguan的博客-CSDN博客_int integer

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

 考察对String类的熟悉程度,要输出特定编码的字符串,需要用到String类的两个方法:

 1、public String(byte[] bytes, Charset charset) :使用指定的字符集解码指定的字节数组来构造新的字符串。

 2、public byte[] getBytes(Charset charset):使用给定的字符集将该字符串编码为一个字节序列,并将结果存储到一个新的字节数组中。

 这两个方法中的charset必须一致,否则会出现乱码。

8.请你说明String和StringBuffer的区别

String 类不可变,内部维护的 char[] 数组长度不可变,为 final 修饰,String 类也是 final 修饰,不存在扩容。字符串拼接,截取,都会生成一个新的对象。频繁操作字符串效率低下,因为每次都会生成新的对象。

StringBuilder 类内部维护可变长度 char[] , 初始化数组容量为 16,存在扩容, 其 append 拼接字符串方法内部调用 System 的 native 方法,进行数组的拷贝,不会重新生成新的 StringBuilder 对象。非线程安全的字符串操作类, 其每次调用 toString 方法而重新生成的 String 对象,不会共享 StringBuilder 对象内部的 char[],会进行一次 char[] 的 copy 操作。

StringBuffer 类内部维护可变长度 char[], 基本上与 StringBuilder 一致,但其为线程安全的字符串操作类,大部分方法都采用了 Synchronized 关键字修改,以此来实现在多线程下的操作字符串的安全性。其 toString 方法而重新生成的 String 对象,会共享 StringBuffer 对象中的 toStringCache 属性(char[]),但是每次的 StringBuffer 对象修改,都会置 null 该属性值。

(1):String 是对象不是原始类型,是不可变对象,一旦创建,就不能修改它的值,对于已经存在的String对象的修改都是重新创建一个String对象,把新的值保存进去;String是final类,不能被继承。

(2):StringBuffer 是可以修改的对象,修改它的时候不会像创建String对象一样重新创建赋值,它只能通过构造函数创建,在创建后会在内存中分配空间,初始化会保存一个null,当向StringBuffer赋值的时候可以通过它的append方法。它的本质是一个线程安全的可修改的字符序列,把所有修改数据的方法都加上synchronized。但是保证了线程安全是需要性能的代价。

(3):在操作字符串连接中StringBufer效率要比String要高:

String str = new String("hello,word");

str + = "xiaoming";

上面的处理其实是先通过建立一个StringBuffer 通过append方法,最后 .toString(); 通过对比知道String的连接操作比StringBuffer多了一些操作,效率会大打折扣。

(4):String对象的创建修改过程每次都会创建新的对象,这样之前的对象就会被垃圾回收,影响性能。

更多:(1)请你说明String 和StringBuffer,StringBuilder的区别_weixin_30604651的博客-CSDN博客

9.请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?

(1)Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
存储内容比较:
Array数组可以包含基本类型和对象类型,ArrayList却只能包含对象类型。
但是需要注意的是:Array数组在存放的时候一定是同种类型的元素。ArrayList就不一定了,因为ArrayList可以存储Object。
(2)Array大小是固定的,ArrayList的大小是动态变化的。
空间大小比较:
它的空间大小是固定的,空间不够时也不能再次申请,所以需要事前确定合适的空间大小。
ArrayList的空间是动态增长的,如果空间不够,它会创建一个空间比原空间大约0.5倍的新数组,然后将所有元素复制到新数组中, 接着抛弃旧数组。而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。(比较麻烦的地方)。
(3)ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
方法上的比较:
ArrayList作为Array的增强版,当然是在方法上比Array更多样化,比如添加全部addAll()、删除全部removeAll()、返回迭代器iterator()等。
(4)对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

适用场景:
1、如果想要保存一些在整个程序运行期间都会存在而且不变的数据,可以使用一个全局数组;
2、如果只是单纯想要以数组的形式保存数据,而不对数据进行增加、删除等操作,只是为了方便进行查找的话,可以使用ArrayList。
3、如果需要对元素进行频繁的移动或删除,或者是处理超大量的数据,使用ArrayList就不合适了,因为它的效率很低,可以选择使用LinkedList。
 

10.请你解释什么是值传递和引用传递?

值传递:在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。

引用传递:”引用”也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,形参和实参指向同一块内存地址,对形参的操作会影响的真实内容。

11. 请你讲讲Java支持的数据类型有哪些?什么是自动拆装箱?

java中的8种基本数据类型:boolean byte char short int float double long

自动装箱: 就是将基本数据类型自动转换成对应的包装类。

自动拆箱:就是将包装类自动转换成对应的基本数据类型。

int的自动装箱都是通过Integer.valueOf()方法来实现的,Integer的自动拆箱都是通过integer.intValue来实现的。

12.请你解释为什么会出现4.0-3.6=0.40000001这种现象?

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

13.请你讲讲一个十进制的数在内存中是怎么存的?

补码的形式。

14.请你说说Lamda表达式的优缺点。

优点:1. 简洁。2. 非常容易并行计算。3. 可能代表未来的编程趋势。

缺点:1. 若不用并行计算,很多时候计算速度没有比传统的 for 循环快。(并行计算有时需要预热才显示出效率优势)2. 不容易调试。3. 若其他程序员没有学过 lambda 表达式,代码不容易让其他语言的程序员看懂。

15. 请你说明符号“==”比较的是什么?

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

 16.请你解释Object若不重写hashCode()的话,hashCode()如何计算出来的?

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

17.请你解释为什么重写equals还要重写hashcode?

HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等的。若equals()不相等则认为他们不相等。
如果只重写equals没有重写hashCode(),就会导致相同的key值也被hashcode认为是不同的key值(因为没有重写hashCode(),则任何对象的hashCode()值都不相等),就会在hashmap中存储相同的key值(map中key值不能相同),这就不符合条件了。
 

18.请你介绍下map的分类和常见的情况

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时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。底层都以散列结构  与链表 实现 该链表也是双向链表  记录了键值对的存储顺序

TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。

19.请你讲讲Java里面的final关键字是怎么用的?

1.修饰类

 当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。要注意final类中的所有成员方法都会被隐式地指定为final方法。

2.修饰方法

如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。即父类的final方法是不能被子类所覆盖的,也就是说子类是不能够存在和父类一模一样的方法的。

3.修饰变量

  final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。

更多:浅谈Java中的final关键字 - 平凡希 - 博客园

20.请你谈谈关于Synchronized和lock 

更多:详解synchronized与Lock的区别与使用_brickworkers的博客-CSDN博客_synchronized与lock的区别

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

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

 21. 请你介绍一下volatile

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

更多:Java并发编程:volatile关键字解析 - Matrix海子 - 博客园

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

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

  1. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分, 作用的对象是这个类的所有对象。
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法, 作用的对象是调用这个方法的对象;
  3. 修改一个静态的方法,其作用的范围是整个静态方法, 作用的对象是这个类的所有对象;
  4. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码, 作用的对象是调用这个代码块的对象;

23.请解释hashCode()和equals()方法有什么联系?

Java对象的eqauls方法和hashCode方法是这样规定的:

➀相等(相同)的对象必须具有相等的哈希码(或者散列码)。

➁如果两个对象的hashCode相同,它们并不一定相同。

 24.请解释Java中的概念,什么是构造函数?什么是构造函数重载?什么是复制构造函数?

5.类加载     面试常见  类加载的触发是重点

(1)什么是类加载?

当第一次使用某个类时,JVM会按照Classpath所配置的目录,寻找  对应的.class 文件

并将该.class文件中的内容(父类/属性/方法/类变量...)读取到JVM的内存中,并且保存起来以便使用

(2)类加载的过程中,两个很重要的作用:

a.给静态属性赋默认值

b.执行静态初始化代码块,给其他属性进行初始化,

(3)什么时候后会触发类加载?

a.第一次创建某个类的对象时,该类会进行类加载

b.第一次通过 类名.静态变量的形式  调用静态成员时,会触发该类的类加载

c.子类进行类加载时,如果父类没有类加载,则先进行父类的类加载,在进行子类的类加载
d.使用Class.forName()方法来获取类对象时,也会触发该类对象所对应类的类加载

6.第一次创建某个类的对象 笔试题重点

(1)先进行类加载

a.父类  类变量  子类 类变量分配空间  赋默认值

b.父类  类变量初始化  

c.子类  类变量初始化

(2)在进行对象创建

a.给子类  父类属性分配空间 赋默认值

b.递归构建父类对象(父类属性初始化,执行父类构造方法中的内容)

c.子类属性初始化

d.执行子类构造中的内容
 

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

25. 请说明Java中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?

Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。 

26.请你说说Static Nested Class 和 Inner Class的不同

Static Nested Class静态内部类可以在不创建实例的条件下直接创建,因为它只访问静态方法和成员,它和类直接绑定,并且不能访问任何非静态类型的方法和成员变量。

但是Inner class内部类是和实例绑定的,他可以访问实例的成员变量和方法,所以在创建他之前必须先创建一个实例,然后通过实例创建它才行。

 27.请说明Query接口的list方法和iterate方法有什么区别?

list()和iterate()区别

1、返回的类型不同,list()返回List,iterate()返回Iterator

2、获取方式不同,list会一次性将数据库中的信息全部查询出,iterate会先把所有数据的id查询出来,然后真正要遍历某个对象的时候先到缓存中查找,如果找不到,以id为条件再发送一条sql到数据库,这样如果缓存中没有数据,则查询数据库的次数为n+1

3、iterate会查询2级缓存,list只会查询一级缓存

4、list中返回的List中每个对象都是原本的对象,iterate中返回的对象是***对象。(debug可以发现)

28. 请说明如何通过反射获取和设置对象私有字段的值?

可以通过类对象的getDeclaredField()方法字段(Field)对象,然后再通过字段对象的setAccessible(true)将其设置为可以访问,接下来就可以通过get/set方法来获取/设置字段的值了。

29.请说明内部类可以引用他包含类的成员吗,如果可以,有没有什么限制吗?

一个内部类对象可以访问创建它的外部类对象的内容,内部类如果不是static的,那么它可以访问创建它的外部类对象的所有属性内部类如果是sattic的,即为nested class,那么它只可以访问创建它的外部类对象的所有static属性一般普通类只有public或package的访问修饰,而内部类可以实现static,protected,private等访问修饰。当从外部类继承的时候,内部类是不会被覆盖的,它们是完全独立的实体,每个都在自己的命名空间内,如果从内部类中明确地继承,就可以覆盖原来内部类的方法。

30.请你讲讲abstract class和interface有什么区别?

下面比较一下两者的语法区别:

1.抽象类可以有构造方法,接口中不能有构造方法。

2.抽象类中可以有普通成员变量,接口中没有普通成员变量

3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

4. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然

eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

5. 抽象类中可以包含静态方法,接口中不能包含静态方法

6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

7. 一个类可以实现多个接口,但只能继承一个抽象类。


31.请说明面向对象的特征有哪些方面

(1)继承:就是保留父类的属性,开扩新的东西。通过子类可以实现继承,子类继承父类的所有状态和行为,同时添加自身的状态和行为。

(2)封装:就是类的私有化。将代码及处理数据绑定在一起的一种编程机制,该机制保证程序和数据不受外部干扰。

(3)多态:是允许将父对象设置成为和一个和多个它的子对象相等的技术。包括重载和重写。重载为编译时多态,重写是运行时多态。
 

32. 请说明Comparable和Comparator接口的作用以及它们的区别。

更多:https://www.jb51.net/article/123097.htm

作用

用来实现集合中元素的比较和排序。

Comparator 和 Comparable 比较

Comparable是排序接口;若一个类实现了Comparable接口,就意味着“该类支持排序”。

而Comparator是比较器;我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。

我们不难发现:Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。

33.请说明Java是否支持多继承?

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

34.请你谈谈如何通过反射创建对象?

方法 1:通过类对象调用 newInstance()方法,例如:

String.class.newInstance()

方法 2:通过类对象的 getConstructor()或 getDeclaredConstructor()

方法获得构造器(Constructor)对象并调用其 newInstance()方法创建对象,

例如:String.class.getConstructor(String.class).newInstance(“Hello”);

35. 请你说明是否可以在static环境中访问非static变量?

不可以,因为static变量是属于类的,在类加载的时候就被初始化了,这时候非静态变量并没有加载,故非静态变量不能访问。 

36.请解释一下extends 和super 泛型限定符

  • extends上限通配符,用来限制类型的上限,只能传入本类和子类,add方法受阻,可以从一个数据类型里获取数据;
  • super下限通配符,用来限制类型的下限,只能传入本类和父类,get方法受阻,可以把对象写入一个数据结构里;

 限定通配符总是包括自己
上界类型通配符:add方法受限
下界类型通配符:get方法受限
如果你想从一个数据类型里获取数据,使用 ? extends 通配符
如果你想把对象写入一个数据结构里,使用 ? super 通配符
如果你既想存,又想取,那就别用通配符
不能同时声明泛型通配符上界和下界

 更多:Java面试 - extends 和super 泛型限定符_I18N_R的博客-CSDN博客_extends 和super 泛型限定符

 37.请你讲讲什么是泛型?

创建集合时先说清楚,只能存放String类型的数据,这时候若是有人捣乱存储其他类型的数据编译就会报错。

等于是将运行时期会发生的异常提前到编译时期了

所以泛型的作用是一种安全机制,是一种书写规范,它和接口的作用有着一定的类似,都是在制定规则。

38.请说明静态变量存在什么位置?

首先,java里面是没有静态变量这个概念的,不信你自己在方法里面定义一个static int i =0;java里只有静态成员变量。它属于类的属性。至于他放在那里?楼上说的是静态区。我不知道到底有没有这个翻译。但是 深入jvm里是是翻译为方法区的。虚拟机的体系结构:堆,方法区,本地方法栈,pc寄存器。而方法区保存的就是一个类的模板,堆是放类的实例的。栈是一般来用来函数计算的。随便找本计算机底层的书都知道了。栈里的数据,函数执行完就不会存储了。这就是为什么局部变量每一次都是一样的。就算给他加一后,下次执行函数的时候还是原来的样子。

39.请你解释一下类加载机制,双亲委派模型,好处是什么?

虚拟机类加载机制:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。
 

40. 请你谈谈StringBuffer和StringBuilder有什么区别,底层实现上呢?

  • StringBuffer和StringBuilder都继承自抽象类AbstractStringBuilder。

  • 存储数据的字符数组也没有被final修饰,说明值可以改变,且构造出来的字符串还有空余位置拼接字符串,但是拼接下去肯定也有不够用的时候,这时候它们内部都提供了一个自动扩容机制,当发现长度不够的时候(默认长度是16),会自动进行扩容工作,扩展为原数组长度的2倍加2,创建一个新的数组,并将数组的数据复制到新数组,所以对于拼接字符串效率要比String要高。自动扩容机制是在抽象类中实现的。

  • 线程安全性:StringBuffer效率低,线程安全,因为StringBuffer中很多方法都被 synchronized 修饰了,多线程访问时,线程安全,但是效率低下,因为它有加锁和释放锁的过程。StringBuilder效率高,但是线程是不安全的。

 更多:JAVA面试题 StringBuffer和StringBuilder的区别,从源码角度分析? - Java蚂蚁 - 博客园

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

 在进行方法调用时,系统唯一的任务是确定被调用方法的版本。对于private、static、final方法或者构造器,这部分方法在程序真正运行之前就有一个可以确定的调用版本,并且该版本在运行期间是不可变的,编译器一开始就能确定要调用的版本,这叫做静态绑定,这些方法在类加载的时候就会把符号引用转化为该方法的直接引用。与之对应,在程序运行期间确定方法调用版本的调用方式叫做动态绑定,此时,虚拟机会为每个类创建一个方法表,列出所有方法签名和实际调用的方法,这样一来虚拟机在调用方法时,只用查找该表就行了,只有在调用时采用动态绑定的方法才能体现出多态特性。

“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。

如果在父类中修饰了一个private的方法,子类继承之后,对子类也是不可见的。那么如果子类声明了一个跟父类中定义为private一样的方法,那么编译器只当作是你子类自己新增的方法,并不能算是继承过来的。

42. 请列举你所知道的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() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。

43. 请解释一下String为什么不可变?

不可变对象是指一个对象的状态在对象被创建之后就不再变化。不可改变的意思就是说:不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。

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

44.请你讲讲wait方法的底层原理

wait即object的wait()和notify()或者notifyall()一起搭配使用
wait方法会将当前线程放入wait set等待被唤醒

1.将当前线程封装成objectwaiter对象node
2.通过objectmonitor::addwaiter方法将node添加到_WaitSet列表中
3.通过ObjectMonitor:exit方法释放当前的ObjectMonitor对象,这样其他竞争线程就可以获取该ObjectMonitor对象
4.最终底层的park方法会挂起线程
notiry方法就是随机唤醒等待池中的一个线程

45.请说明List、Map、Set三个接口存取元素时,各有什么特点?

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

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

ArrayList 采用的是数组形式来保存对象的,这种方式将对象放在连续的位置中,所以最大的缺点就是插入删除时非常麻烦

LinkedList 采用的将对象存放在独立的空间中,而且在每个空间中还保存下一个链接的索引 但是缺点就是查找非常麻烦 要丛第一个索引开始

ArrayList和Vector都是用数组方式存储数据,此数组元素数要大于实际的存储空间以便进行元素增加和插入操作,他们都允许直接用序号索引元素,但是插入数据元素涉及到元素移动等内存操作,所以索引数据快而插入数据慢.

Vector使用了sychronized方法(线程安全),所以在性能上比ArrayList要差些.

LinkedList使用双向链表方式存储数据,按序号索引数据需要前向或后向遍历数据,所以索引数据慢,是插入数据时只需要记录前后项即可,所以插入的速度快.
 

47.请说明Collection 和 Collections的区别。

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

48. 请简要说明ArrayList,Vector,LinkedList的存储性能和特性是什么?

ArrayList 和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差。

LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

49.请你说明HashMap和Hashtable的区别?  

1. HashMap继承自抽象类AbstractMap,而HashTable继承自抽象类Dictionary。其中Dictionary类是一个已经被废弃的类

2.HashTable比HashMap多了两个公开方法。一个是elements,这来自于抽象类Dictionary,鉴于该类已经废弃,所以这个方法也就没什么用处了。另一个多出来的方法是contains,这个多出来的方法也没什么用,因为它跟containsValue方法功能是一样的。

3.HashMap是支持null键和null值的,而HashTable在遇到null时,会抛出NullPointerException异常。这并不是因为HashTable有什么特殊的实现层面的原因导致不能支持null键和null值,这仅仅是因为HashMap在实现时对null做了特殊处理,将null的hashCode值定为了0,从而将其存放在哈希表的第0个bucket中。

4.可以看到HashTable默认的初始大小为11,之后每次扩充为原来的2n+1。HashMap默认的初始化大小为16,之后每次扩充为原来的2倍。还有我没列出代码的一点,就是如果在创建时给定了初始化大小,那么HashTable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。也就是说HashTable会尽量使用素数、奇数。而HashMap则总是使用2的幂作为哈希表的大小。我们知道当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀(具体证明,见这篇文章),所以单从这一点上看,HashTable的哈希表大小选择,似乎更高明些。但另一方面我们又知道,在取模计算时,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法。所以从hash计算的效率上,又是HashMap更胜一筹。所以,事实就是HashMap为了加快hash的速度,将哈希表的大小固定为了2的幂。当然这引入了哈希分布不均匀的问题,所以HashMap为解决这问题,又对hash算法做了一些改动。具体我们来看看,在获取了key对象的hashCode之后,HashTable和HashMap分别是怎样将他们hash到确定的哈希桶(Entry数组位置)中的。

正如我们所言,HashMap由于使用了2的幂次方,所以在取模运算时不需要做除法,只需要位的与运算就可以了。但是由于引入的hash冲突加剧问题,HashMap在调用了对象的hashCode方法之后,又做了一些位运算在打散数据。

5.我们说HashTable是同步的,HashMap不是,也就是说HashTable在多线程使用的情况下,不需要做额外的同步,而HashMap则不行。

6.Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。

50. 请说说快速失败(fail-fast)和安全失败(fail-safe)的区别?

快速失败和安全失败是对迭代器而言的。
快速失败:当在迭代一个集合的时候,如果有另外一个线程在修改这个集合,就会抛出ConcurrentModificationException异常,java.util下都是快速失败。
安全失败:在迭代时候会在集合二层做一个拷贝,所以在修改集合上层元素不会影响下层。在java.util.concurrent下都是安全失败

一:快速失败(fail—fast)

在用迭代器遍历一个集合对象时,如果遍历过程导致集合中元素个数变化(例如: add、remove、clear),则会抛出ConcurrentModificationException。

原理 : 迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果元素数量发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount和expectedmodCount值是否相等,是的话就返回遍历;否则抛出异常ConcurrentModificationException,终止遍历。

注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。

场景: java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。

二:安全失败(fail—safe)

采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理: 由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。

缺点: 基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

场景: java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

51.  请你说说Iterator和ListIterator的区别?

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

52. 请简单说明一下什么是迭代器?

Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包括了可以返回迭代器实例的迭代方法。迭代器可以在迭代过程中删除底层集合的元素,但是不可以直接调用集合的remove(Object obj)删除,可以通过迭代器的remove()方法删除

53.请解释为什么集合类没有实现Cloneable和Serializable接口? 

  • 集合类接口Collection,List,Set,Map定义了自己集合类的抽象即可,如果接口的设计也要考虑是否可以克隆,串行化等一堆额外特性,那是不是还要额外考虑是否可以Closeable, 接口就不是基于抽象,不是纯粹的接口了。 

  • 对于具体的实现类, java.util.ArrayList,LinkedList,HashMap,HashSet, 有什么特性就实现什么接口,可以实现多个接口即可。实际这些类都实现了Cloneable和Serializable接口,因为实际应用中集合类很常用,串行化和克隆也常用。

1.Cloneable接口作用是将一个对象的属性值复制给另一个对象,而不是对象的一个引用。 

2.Serializable接口作用(这个罗嗦一下)

2.1序列化的用途

 1.有时候,如果想让一个对象持久的存储下来(存到磁盘),或者是进行远程的对象调用,那就要使用序列化实现这些作用。我们必须对所有支持持久化存储的类实现Serializable接口,读取的时候也要进行反序列化。

 2.对于jvm来说,进行持久化的类必须有个标记,就是实现Serializable接口,关联serialVersionUID,这个变量就是在反序列话中确定用那个类加载这个对象。

 3.值得主意的是,持久化的数据都是存在在java堆中,static类型的数据存在在方法区中,不能被持久化。如果不想让某个成员变量持久化,变量前面用transient关键字

 4.当然序列化的那个serialVersionUID这个还可以进行自定义

3.其实不难看出,Cloneable是复制对象的,序列化也是针对对象的操作,集合类只是管理对象的一个工具,就好比说list能够线性的管理对象,set集合能够对对象去重等,这些集合类都是针对与为管理对象而产生的。

其实,这两个接口都是针对真实的对象,而不是集合类这样的管理对象的对象。这个从语义上就是集合类的Cloneable接口和Serializable接口应该又集合中具体的类型实现,而不是又集合类来实现序列化。假设集合类实现了这两个接口,如果我要生成一个不需要序列化,不需要clone的集合,那么集合类就强行实现,这样有违集合的设计原则。
 

 54. 请你说明一下ConcurrentHashMap的原理?

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

在ConcurrentHashMap 中,在散列时如果产生“碰撞”,将采用“分离链接法”来处理“碰撞”:把“碰撞”的 HashEntry 对象链接成一个链表。由于 HashEntry 的 next 域为 final 型,所以新节点只能在链表的表头处插入。 下图是在一个空桶中依次插入 A,B,C 三个 HashEntry 对象后的结构图:

图1. 插入三个节点后桶的结构示意图:

注意:由于只能在表头插入,所以链表中节点的顺序和插入的顺序相反。

Segment 类继承于 ReentrantLock 类,从而使得 Segment 对象能充当锁的角色。每个 Segment 对象用来守护其(成员对象 table 中)包含的若干个桶。
 

55. 请你说明concurrenthashmap有什么优势以及1.7和1.8区别?

1、JDK1.7:

HashEntry数组 + Segment数组 + Unsafe 「大量方法运用」

JDK1.7中数据结构是由一个Segment数组和多个HashEntry数组组成的,每一个Segment元素中存储的是HashEntry数组+链表,而且每个Segment均继承自可重入锁ReentrantLock,也就带有了锁的功能,当线程执行put的时候,只锁住对应的那个Segment 对象,对其他的 Segment 的 get put 互不干扰,这样子就提升了效率,做到了线程安全。

额外补充:我们对 ConcurrentHashMap 最关心的地方莫过于如何解决 HashMap 在 put 时候扩容引起的不安全问题?

在 JDK1.7 中 ConcurrentHashMap 在 put 方法中进行了两次 hash 计算去定位数据的存储位置,尽可能的减小哈希冲突的可能行,然后再根据 hash 值以 Unsafe 调用方式,直接获取相应的 Segment,最终将数据添加到容器中是由 segment对象的 put 方法来完成。由于 Segment 对象本身就是一把锁,所以在新增数据的时候,相应的 Segment对象块是被锁住的,其他线程并不能操作这个 Segment 对象,这样就保证了数据的安全性,在扩容时也是这样的,在 JDK1.7 中的 ConcurrentHashMap扩容只是针对 Segment 对象中的 HashEntry 数组进行扩容,还是因为 Segment 对象是一把锁,所以在 rehash 的过程中,其他线程无法对 segment 的 hash 表做操作,这就解决了 HashMap 中 put 数据引起的闭环问题。

2、JDK1.8:

JDK1.7:ReentrantLock+Segment+HashEntry
JDK1.8:Synchronized+CAS+Node+红黑树

JDK1.8屏蔽了JDK1.7中的Segment概念呢,而是直接使用「Node数组+链表+红黑树」的数据结构来实现,并发控制采用 「Synchronized + CAS机制」来确保安全性,为了兼容旧版本保留了Segment的定义,虽然没有任何结构上的作用。

总之JDK1.8中优化了两个部分:

放弃了 HashEntry 结构而是采用了跟 HashMap 结构非常相似的 Node数组 + 链表(链表长度大于8时转成红黑树)的形式

Synchronize替代了ReentrantLock,我们一直固有的思想可能觉得,Synchronize是重量级锁,效率比较低,但为什么要替换掉ReentrantLock呢?

1、随着JDK版本的迭代,本着对Synchronize不放弃的态度,内置的Synchronize变的越来越“轻”了,某些场合比使用API更加灵活。

2、加锁力度的不同,在JDK1.7中加锁的力度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点),也就是1.8中加锁力度更低了,在粗粒度加锁中 ReentrantLock 可能通过 Condition 来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了,所以使用内置的 Synchronize 并不比ReentrantLock效果差。

56.请解释一下TreeMap?

TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。

TreeMap的底层实现是红黑树数据结构,也就是说是一棵自平衡的排序二叉树,这样就可以保证快速检索指定节点。该映射根据键的自然顺序进行排序,或者根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法。

TreeMap的特性:

①根节点是黑色。

②每个节点只能是红色或者黑色。

③每个叶子节点是黑色。

④如果一个节点是红色的,则它两个节点都是黑色的,也就是在一条路径上不能出现两个红色的节点。

⑤从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
 

 57.请说明ArrayList是否会越界?

如果数组长度小于默认的容量10,则调用扩大数组大小的方法grow()。

其中 函数grow()解释了基于数组的ArrayList是如何扩容的。数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中
每次数组容量的增长大约是其原容量的1.5倍。
 接下来回到Add()函数,继续执行,elementData[size++] = e; 这行代码就是问题所在,当添加一个元素的时候,它可能会有两步来完成:
1. 在 elementData[Size] 的位置存放此元素;
2. 增大 Size 的值。

在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;

 如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。那好,我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了

58.请你简单介绍一下ArrayList和LinkedList的区别,并说明如果一直在list的尾部添加元素,用哪种方式的效率高? 

ArrayList采用数组实现的,查找元素的效率比LinkedList高。

LinkedList采用双线链表实现,插入和删除的效率比ArrayList要高。

如果一直在list的尾部添加元素,用哪种效率更高?

当插入的数据一直是小于千万级的时候,大部分是LinkedList效率高,而当数据量大于千万级时,就会出现ArrayList的效率比较高了。

LinkedList每次添加元素的时候,会new一个Node对象来存新增加的元素,所以当数据量小的时候,这个时间并不明显,而ArrayList需要扩容,所以LinkedList的效率比较高,其中,如果ArrayList出现不需要扩容的时候,那么ArrayList的效率是比LinkedList要高的,当数据量很大的时候,new对象的时间大于扩容的时间,那么ArrayList的效率高过LinkedList。
 

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

hashcode()和equals()都继承于object,在Object类中的定义为:

equals()方法在Object类中的定义:
public boolean equals(Object obj){
    return (this == obj);
}
equals()的定义为:
public native int hashCode();
是一个本地方法,返回的对象的地址值。

     1.hashcode()和equals()是在哪里被用到的?什么用的?

     HashMap是基于散列函数,以数组和链表的方式实现的。
 而对于每一个对象,通过其hashCode()方法可为其生成一个整形值(散列码),该整型值被处理后,将会作为数组下标,存放该对象所对应的Entry(存放该对象及其对应值)。
 equals()方法则是在HashMap中插入值或查询时会使用到。当HashMap中插入 值或查询值对应的散列码与数组中的散列码相等时,则会通过equals方法比较key值是否相等,所以想以自建对象作为HashMap的key,必须重写 该对象继承object的equals方法。

    
     2.本来不就有hashcode()和equals()了么?干嘛要重写,直接用原来的不行么?
     
    HashMap中,如果要比较key是否相等,要同时使用这两个函数!因为自定义的类的hashcode()方法继承于Object类,其hashcode码为默认的内存地 址,这样即便有相同含义的两个对象,比较也是不相等的,例如,

Student st1 = new Student("wei","man");
Student st2 = new Student("wei","man"); 

正常理解这两个对象再存入到hashMap中应该是相等的,但如果你不重写 hashcode()方法的话,比较是其地址,不相等!
    HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等 的。若equals()不相等则认为他们不相等。如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为 同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。HashMap用来判断key是否相等的方法,其实是调用了HashSet判断加入元素 是否相等。
 

60. 请你解释一下hashMap具体如何实现的?

更多:Java 8系列之重新认识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左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。

61. 如何保证线程安全?

JDK里面提供了很多atomic类,比如AtomicInteger, AtomicLong, AtomicBoolean等等,这些类本身可以通过CAS来保证操作的原子性;另外Java也提供了各种锁机制,来保证锁内的代码块在同一时刻只能有一个线程执行,与此同时,java还提供了一种轻量级的锁,即volatile关键字,要优于synchronized的性能,同样可以保证修改对其他线程的可见性。

62.请你简要说明一下线程的基本状态以及状态之间的关系?

1.新建

用new语句创建的线程对象处于新建状态,此时它和其他java对象一样,仅被分配了内存。

2.等待

当线程在new之后,并且在调用start方法前,线程处于等待状态。

3.就绪

当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态。处于这个状态的线程位于Java虚拟机的可运行池中,等待cpu的使用权。

4.运行状态

处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU,那么任何时刻只会有一个线程处于这个状态。

只有处于就绪状态的线程才有机会转到运行状态。

5.阻塞状态

阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才会有机会获得运行状态。

阻塞状态分为三种:

1、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。

2、同步阻塞:运行的线程在获取对象同步锁时,若该同步锁被别的线程占用,则JVM会把线程放入锁池中。

3、其他阻塞:运行的线程执行Sleep()方法,或者发出I/O请求时,JVM会把线程设为阻塞状态。当Sleep()状态超时、或者I/O处理完毕时,线程重新转入就绪状态。

6.死亡状态

当线程执行完run()方法中的代码,或者遇到了未捕获的异常,就会退出run()方法,此时就进入死亡状态,该线程结束生命周期。

63. 请你解释一下什么是线程池(thread pool)?

在面向对象编程中,创建和销毁对象是很费时间的,所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:
- newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
- newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
- newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
- newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
 

64.举例说明同步和异步

如果系统中存在临界资源,例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取(数据库操作中的排他锁就是最好的例子)。

当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

事实上,所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。
 

 65.请介绍一下线程同步和线程调度的相关方法。

- wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

补充:Java 5通过Lock接口提供了显式的锁机制(explicit lock),增强了灵活性以及对线程的协调。Lock接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了newCondition()方法来产生用于线程之间通信的Condition对象;此外,Java 5还提供了信号量机制(semaphore),信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用Semaphore对象的acquire()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用Semaphore对象的release()方法)。
 

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

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

67.请简述一下线程的sleep()方法和yield()方法有什么区别?

更多:https://blog.csdn.net/qq_41447460/article/details/107920154?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-11.essearch_pc_relevant&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-11.essearch_pc_relevant

1.sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会

2.yield()方法只会给相同优先级或更高优先级的线程以运行的机会

3.线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态

4.sleep()方法声明会抛出InterruptedException,而yield()方法没有声明任何异常

5.sleep()方法比yield()方法具有更好的移植性(跟操作系统CPU调度相关)

68. 请回答以下几个问题: 第一个 问题:Java中有几种方法可以实现一个线程? 第二个问题:用什么关键字修饰同步方法?  第三个问题:stop()和suspend()方法为何不推荐使用,请说明原因?

有三种使用线程的方法:

  • 实现 Runnable 接口;
  • 实现 Callable 接口;
  • 继承 Thread 类。

synchronized关键字修饰同步方法

为什么不推荐用 stop()和 suspend()

  • stop这个方法将终止所有未结束的方法,包括run方法。当一个线程停止时候,他会立即释放所有他锁住对象上的锁。这会导致对象处于不一致的状态。假如一个方法在将钱从一个账户转移到另一个账户的过程中,在取款之后存款之前就停止了。那么现在银行对象就被破坏了。因为锁已经被释放了。当线程想终止另一个线程的时候,它无法知道何时调用stop是安全的,何时会导致对象被破坏。所以这个方法被弃用了。你应该中断一个线程而不是停止他。
  • suspend被弃用的原因是因为它会造成死锁。suspend方法和stop方法不一样,它不会破换对象和强制释放锁,相反它会一直保持对锁的占有,一直到其他的线程调用resume方法,它才能继续向下执行。假如有A,B两个线程,A线程在获得某个锁之后被suspend阻塞,这时A不能继续执行,线程B在或者相同的锁之后才能调用resume方法将A唤醒,但是此时的锁被A占有,B不能继续执行,也就不能及时的唤醒A,此时A,B两个线程都不能继续向下执行而形成了死锁。

69.  请说出你所知道的线程同步的方法

* 同步方法(synchronized 修饰方法)
* 同步代码块    (synchronized 修饰代码块)
* 使用volatile实现线程同步(使用volatile 修饰变量)
* 使用重入锁实现线程同步(ReentractLock)
* 使用局部变量实现线程同步(ThreadLocal)
 

70.启动一个线程是用run()还是start()?

 启动一个线程调用start(),使得线程处于就绪状态,之后可以被调度称为运行状态

run()是线程执行的代码

71.请使用内部类实现线程设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。

package mystudy;

public class ManyThreads {

    private int j;

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ManyThreads many = new ManyThreads();
        Inc inc = many.new Inc();
        Dec dec = many.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 {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            for (int i = 0; i < 20; i++) {
                inc();
            }
        }

    }

    class Dec implements Runnable {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            for (int i = 0; i < 20; i++) {
                dec();
            }
        }

    }
}

72. 请说明一下sleep() 和 wait() 有什么区别?

sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复。

wait()是 Object 类的方法,调用对象的 wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的 notify()方法(或 notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。
 

73. 请你说明一下在监视器(Monitor)内部,是如何做到线程同步的?在程序又应该做哪种级别的同步呢? 

在Java虚拟机中,每个对象(object和class)通过某种逻辑关联监视器,每个监视器和一个对象引用相关联,为了实现监视器的互斥功能,每个对象都关联着一把锁。

一旦方法或者代码块被synchronized修饰,那么这个部分就放入了监视器的监视区域,确保一次只有一个线程执行该部分代码,线程必须在获取锁之前不允许执行该部分代码。

Java提供了显示监视器(Lock)和隐式监视器(synchronized)两种锁方案。

74. 请分析一下同步方法和同步代码块的区别是什么?

同步方法默认用this或者当前类class对象作为锁;
同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;
同步方法使用关键字 synchronized修饰方法,而同步代码块主要是修饰需要进行同步的代码,用 synchronized(object){代码内容}进行修饰;
 

75.请解释一下Java多线程回调是什么意思?

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

76.  请列举一下启动线程有哪几种方式,之后再说明一下线程池的种类都有哪些?

①启动线程有如下三种方式:

一、继承Thread类创建线程类

(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。

(2)创建Thread子类的实例,即创建了线程对象。

(3)调用线程对象的start()方法来启动该线程。

代码:

package com.thread;
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()方法来启动该线程。

package com.thread;
 
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()方法来获得子线程执行结束后的返回值

package com.thread;
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提供四种线程池,分别为:
newCachedThreadPool创建之初里面一个线程都没有,当execute方法或submit方法向线程池提交任务时,会自动新建线程;如果线程池中有空余线程,则不会新建;这种线程池一般最多情况可以容纳几万个线程,里面的线程空余60s会被回收
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。第一个参数:接收一个Runnable任务。第二个参数:初始延迟时间,任务提交到线程池后延迟这么多时间才开始执行,如果设为0,那就是任务提交过来就开始执行了。第三个参数:这个任务会每隔这么多时间重新执行一次。第四个参数:为前两个参数的单位。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

77.请简要说明一下JAVA中cyclicbarrier和countdownlatch的区别分别是什么?

CountDownLatch是一个同步的辅助类,允许一个或多个线程,等待其他一组线程完成操作,再继续执行。
CyclicBarrier是一个同步的辅助类,允许一组线程相互之间等待,达到一个共同点,再继续执行。

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

CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;

而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;

另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。

78. 请说明一下线程池有什么优势?

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能执行。

第三:提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

79. 请简短说明一下你对AQS的理解。

AQS的全称是:AbstractQueuedSynchronizer,抽象队列同步器,给大家画一张图,看看,ReentrantLock和AQS的之间的关系。

    

     ReentrantLock里面包含了一个AQS对象,这个AQS对象是ReentrantLock可以实现加锁和释放锁的关键性核心组件。

 AQS是一个并发包的基础组件,用来实现各种锁,各种同步组件,它包含state变量,加锁线程,等待队列等并发中的核心组件。

 ReentrantLock加锁和释放锁的底层原理

首先我们来看看,如果用java并发包下的ReentrantLock来加锁和释放锁,是个什么样的感觉?

这个基本学过java的同学应该都会吧,毕竟这个是java并发基本API的使用,应该每个人都是学过的,所以我们直接看一下代码就好了:

上面那段代码应该不难理解吧,无非就是搞一个Lock对象,然后加锁和释放锁。

你这时可能会问,这个跟AQS有啥关系?关系大了去了!因为java并发包下很多API都是基于AQS来实现的加锁和释放锁等功能的,AQS是java并发包的基础类。

举个例子,比如说ReentrantLock、ReentrantReadWriteLock底层都是基于AQS来实现的。

那么AQS的全称是什么呢?AbstractQueuedSynchronizer,抽象队列同步器。给大家画一个图先,看一下ReentrantLock和AQS之间的关系。

我们来看上面的图。说白了,ReentrantLock内部包含了一个AQS对象,也就是AbstractQueuedSynchronizer类型的对象。这个AQS对象就是ReentrantLock可以实现加锁和释放锁的关键性的核心组件。

三、ReentrantLock加锁和释放锁的底层原理

好了,那么现在如果有一个线程过来尝试用ReentrantLock的lock()方法进行加锁,会发生什么事情呢?

很简单,这个AQS对象内部有一个核心的变量叫做state,是int类型的,代表了加锁的状态。初始状态下,这个state的值是0。

另外,这个AQS内部还有一个关键变量,用来记录当前加锁的是哪个线程,初始化状态下,这个变量是null。

接着线程1跑过来调用ReentrantLock的lock()方法尝试进行加锁,这个加锁的过程,直接就是用CAS操作将state值从0变为1。

如果不知道CAS是啥的,请看上篇文章,《大白话聊聊Java并发面试问题之Java 8如何优化CAS性能?》

如果之前没人加过锁,那么state的值肯定是0,此时线程1就可以加锁成功。

一旦线程1加锁成功了之后,就可以设置当前加锁线程是自己。所以大家看下面的图,就是线程1跑过来加锁的一个过程。

其实看到这儿,大家应该对所谓的AQS有感觉了。说白了,就是并发包里的一个核心组件,里面有state变量、加锁线程变量等核心的东西,维护了加锁状态。

你会发现,ReentrantLock这种东西只是一个外层的API,内核中的锁机制实现都是依赖AQS组件的

这个ReentrantLock之所以用Reentrant打头,意思就是他是一个可重入锁。

可重入锁的意思,就是你可以对一个ReentrantLock对象多次执行lock()加锁和unlock()释放锁,也就是可以对一个锁加多次,叫做可重入加锁。

大家看明白了那个state变量之后,就知道了如何进行可重入加锁!

其实每次线程1可重入加锁一次,会判断一下当前加锁线程就是自己,那么他自己就可以可重入多次加锁,每次加锁就是把state的值给累加1,别的没啥变化。

接着,如果线程1加锁了之后,线程2跑过来加锁会怎么样呢?

我们来看看锁的互斥是如何实现的?线程2跑过来一下看到,哎呀!state的值不是0啊?所以CAS操作将state从0变为1的过程会失败,因为state的值当前为1,说明已经有人加锁了!

接着线程2会看一下,是不是自己之前加的锁啊?当然不是了,“加锁线程”这个变量明确记录了是线程1占用了这个锁,所以线程2此时就是加锁失败。

给大家来一张图,一起来感受一下这个过程:

接着,线程2会将自己放入AQS中的一个等待队列,因为自己尝试加锁失败了,此时就要将自己放入队列中来等待,等待线程1释放锁之后,自己就可以重新尝试加锁了

所以大家可以看到,AQS是如此的核心!AQS内部还有一个等待队列,专门放那些加锁失败的线程!

同样,给大家来一张图,一起感受一下:

接着,线程1在执行完自己的业务逻辑代码之后,就会释放锁!他释放锁的过程非常的简单,就是将AQS内的state变量的值递减1,如果state值为0,则彻底释放锁,会将“加锁线程”变量也设置为null!

整个过程,参见下图:

接下来,会从等待队列的队头唤醒线程2重新尝试加锁。

好!线程2现在就重新尝试加锁,这时还是用CAS操作将state从0变为1,此时就会成功,成功之后代表加锁成功,就会将state设置为1。

此外,还要把“加锁线程”设置为线程2自己,同时线程2自己就从等待队列中出队了。

最后再来一张图,大家来看看这个过程。 

    其实看到这里,大家对AQS应是有感觉了的,就是并发包里面的一个核心组件,里面维护有state变量,加锁线程变量等核心东西,维护了加锁状态。

    ReentrantLock这种东西只是也给外层的API,内核中的锁机制都是依赖AQS组件的。

    除此之外,AQS内部还维护着一个先进先出的等待队列,因为如果线程2这个时候来加锁,就会加锁失败,所以AQS还有维护一个等待队列,专门来存放那些加锁失败的线程。

    ReentrantLock是可重入锁,知道了state变量和当前加锁线程是谁之后,就可以很容易实现可重复进入的锁了。

    如果线程1,在完成自己的业务逻辑代码之后,就会释放锁,他释放锁的过程非常简单,就是将AQS内的State变量的值递减1,如果state值为0,就彻底释放锁了,会将加锁线程也变为null。

    这就是AQS的整个过程。

80. 请简述一下线程池的运行流程,使用参数以及方法策略等

1.线程池的好处
降低资源的消耗。
提高响应速度
提高线程的可管理性
2.线程池的运行流程
首先判断核心线程是否已满,如果未满,则直接从核心线程中创建一个线程;如果满了,判断任务队列是否全满,如果任务队列没有满,就把任务放入任务队列,满了就判断线程池的全部线程是否都在忙,如果忙就交给饱和策略去处理,否则就创建一个线程来帮助核心线程处理任务。

3.重要参数

https://blog.csdn.net/ye17186/article/details/89467919?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.essearch_pc_relevant&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.essearch_pc_relevant
CorePoolSize:核心线程数大小

MaximumPoolSize:最大核心线程数

KeepAlivieTime:空闲线程存活时间

unit 空闲线程存活时间单位

WorkQueue:任务缓存队列

ThreadFactory:线程工厂,主要用来创建线程

handler 拒绝策略

当任务大小到达coreSize大小时,任务可以正常运行,当任务个数大于coreSize的大小时,任务就先会放在等待队列中,当等待队列也放满了,接下来才会创建线程,直到当前线程数等于最大线程数,当队列也满了,也达到最大线程数了,接下来添加的任务都会被拒绝掉,直接抛出异常

4.饱和处理策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出异常

ThreadPoolExecutor.DiscardPolicy:丢弃任务,不抛出异常

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列任务最前面的任务,然后重试

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

81.  如何在线程安全的情况下实现一个计数器?

package test;
 
import java.util.concurrent.atomic.AtomicInteger;
 
public class No5MySafeCalcThread1 implements Runnable {
 
	private static AtomicInteger count = new AtomicInteger(0);
 
	//在Java面经的5 个    答案集合中
	//https://blog.csdn.net/puzimengya/article/details/81064455
	/*
	 * 题目:如何线程安全的实现一个计数器?
	 * 思路:使用synchronized关键字修饰计数器方法  
	 * 	    并且使用AtomicInteger类线程安全的一种原子操作的Integer类进行操作
	 * 
	 * */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//设置了6个线程,对其中静态变量count进行自增,并输出当前的线程和计数值
		for(int i = 0;i < 6;i ++) {
			No5MySafeCalcThread1 thread = new No5MySafeCalcThread1();
			Thread t = new Thread(thread);
			t.start();
			
		}		 
	}
	
	
	
	public void run() {
		while(true) {
			No5MySafeCalcThread1.calc();
			
				try {
					Thread.sleep(0);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			
		}
		
	}
 
 
 
	public synchronized static void calc() {
		// TODO Auto-generated method stub
		if((count.get())<1000) {
			int c = count.incrementAndGet(); //自增1,并返回更新值
			System.out.println("正在运行的线程"+Thread.currentThread().getName()+":"+c);
		}
	}
 
}

 82. 多线程中的i++线程安全吗?请简述一下原因?

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

83.JAVA中如何确保N个线程可以访问N个资源,但同时又不导致死锁? 

产生死锁的四个条件

互斥条件

一个资源每次只能被一个进程使用,互斥条件是非共享设备所必须的,不仅不能改变还需要加以保证。

请求和保持

一个进程因请求资源而阻塞且对已获得的资源保持不放

不可剥夺(不可抢占)

线程已获得的资源在在未使用完之前,不能强行剥夺

循环等待

若干个线程之间形成一种头尾相接的循环等待资源关系

产生死锁的原因

竞争不可抢占性资源
竞争可消耗资源
线程推进顺序不当


预防死锁,预先破坏产生死锁的四个条件。互斥不可能破坏,所以有以下三种方法。

破坏请求和保持条件

解决方案有两种:

在线程开始之前一次性申请所有资源(必须等待所有请求的资源空闲时才能申请资源)

在线程开始时只申请运行初期所需的资源便开始运行运行过程中逐步释放自己已分配的且使用完毕的资源例如,一个线程要获取磁盘资源再通过打印机打印,那么初期获得磁盘资源即可,等待从磁盘中取出数据后便释放磁盘资源再申请打印机资源

破坏不可抢占条件

当一个已经保持了某些不可抢占资源的线程提出新的资源请求但不能满足时释放所有保持的资源。但是该方法实现起来比较复杂花费代价太大。

破坏循环等待条件

对系统中所有资源类型进行线性排列并赋予不同序号。每个线程必须按序号增序请求资源。这种方法对资源的利用率比前两种都高,但是前期要为设备指定序号,新设备加入会有一个问题,其次对用户编程也有限制。
 

84. 请讲一下非公平锁和公平锁在reetrantlock里的实现过程是怎样的。

所谓公平锁,就是线程按照执行顺序排成一排,依次获取锁,但是这种方式在高并发的场景下极其损耗性能;这时候,非公平锁应运而生了,所谓非公平锁,就是不管执行顺序,每个线程获取锁的几率都是相同的,获取失败了,才会采用像公平锁那样的方式。这样做的好处是,JVM可以花比较少的时间在线程调度上,更多的时间则是用在执行逻辑代码里面。



非公平锁: 当线程争夺锁的过程中,会先进行一次CAS尝试获取锁,若失败,则进入acquire(1)函数,进行一次tryAcquire再次尝试获取锁,若再次失败,那么就通过addWaiter将当前线程封装成node结点加入到Sync队列,这时候该线程只能乖乖等前面的线程执行完再轮到自己了。
公平锁: 当线程在获取锁的时候,会先判断Sync队列中是否有在等待获取资源的线程。若没有,则尝试获取锁,若有,那么就那么就通过addWaiter将当前线程封装成node结点加入到Sync队列中。

85. Java中的LongAdder和AtomicLong有什么区别?

java longadder_Java中的LongAdder和AtomicLong的区别_weixin_39838231的博客-CSDN博客

AtomicLong 是基于 CAS 方式自旋更新的;LongAdder 是把 value 分成若干cell,并发量低的时候,直接 CAS 更新值,成功即结束。并发量高的情况,CAS更新某个cell值和需要时对cell数据扩容,成功结束;更新失败自旋 CAS 更新 cell值。取值的时候,调用 sum() 方法进行每个cell累加。

synchronized—重量级锁;Atomic—自旋锁(CAS);LongAdder—分段自旋锁。因此该实验其实就是三种锁在不同场景下的性能对比。
竞争不激烈,建议使用自旋锁。
竞争激烈,建议使用重量级锁。
循环次数越高,分段自旋锁的性能优势越明显。

86.  请说明一下JAVA中反射的实现过程和作用分别是什么?

JAVA语言编译之后会生成一个.class文件,反射就是通过字节码文件找到某一个类、类中的方法以及属性等。反射的实现主要借助以下四个类:Class:类的对象,Constructor:类的构造方法,Field:类中的属性对象,Method:类中的方法对象。

作用:反射机制指的是程序在运行时能够获取自身的信息。在JAVA中,只要给定类的名字,那么就可以通过反射机制来获取类的所有信息。

更多:java反射的原理,作用 - 一支会记忆的笔 - 博客园

87.请简单描述一下JVM加载class文件的原理是什么?

 编译:.java文件编译后生成.class字节码文件
 加载:类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例
 连接:细分三步
     验证:格式(class文件规范) 语义(final类是否有子类) 操作
     准备:静态变量赋初值和内存空间,final修饰的内存空间直接赋原值,此处不是用户指定的初值。
     解析:符号引用转化为直接引用,分配地址
   初始化:有父类先初始化父类,然后初始化自己;将static修饰代码执行一遍,如果是静态变量,则用用户指定值覆盖原有初值;如果是代码块,则执行一遍操作。

更多:描述一下JVM加载class文件的原理机制 - 割肉机 - 博客园

88.jvm最大内存限制多少?

首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽 然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系 统下为2G-3G),而64bit以上的处理器就不会有限制了。

89.为什么Java被称作是“平台无关的编程语言”?

Java虚拟机可以理解为一个特殊的“操作系统”,只是它连接的不是硬件,而是一般的操作系统和java程序。

正是因为有这样一层操作系统与程序之间的连接,Java程序就能在一台机子上编译后到处都能运行——只要有对应不同系统的Java虚拟机就可以了。因此Java被称为“平台无关”。

90.  jvm是如何实现线程的?

实现线程的方式

  • 主流OS都提供线程实现。Java语言提供对线程操作的同一API,每个已经执行start(),且还未结束的java.lang.Thread类的实例,代表了一个线程。
    Thread类的关键方法,都声明为Native。这意味着这个方法无法或没有使用平台无关的手段来实现,也可能是为了执行效率。

1.1 使用内核线程实现
内核线程(Kernel-Level Thread, KLT)就是直接由操作系统内核支持的线程。

  • 内核来完成线程切换
  • 内核通过调度器Scheduler调度线程,并将线程的任务映射到各个CPU上
  • 程序使用内核线程的高级接口,轻量级进程(Light Weight Process,LWP)–>(!!!名字是进程,实际是线程)
  • 用户态和内核态切换消耗内核资源

1.2 使用用户线程实现

  • 系统内核不能感知线程存在的实现
  • 用户线程的建立、同步、销毁和调度完全在用户态中完成
  • 所有线程操作需要用户程序自己处理,复杂度高

1.3 用户线程加轻量级进程混合实现

  • 轻量级进程作为用户线程和内核线程之间的桥梁

91.请问什么是JVM内存模型?

Java虚拟机管理的内存分为五大区域,程序计数器、虚拟机栈、本地方法栈、堆以及方法区。程序计数器、虚拟机栈和本地方法栈都是线程私有的,即每个线程都有自己的程序计数器、虚拟机栈和本地方法栈;堆和方法区是线程共享的,即所有线程共享堆和方法区。
JVM内存模型:

JVM内存模型

1) 是为java方法提供运行空间的
2) 方法一旦被调用就会在栈中创建对应的栈帧,而方法的整个执行过程就是方法对应的栈帧从入栈到出栈的过程。换言之,就是方法被调用进栈(压栈 入栈),方法执行结束出栈(弹栈)。
3) 栈是先进后出后进先出(先被调用的方法最后结束,后被调用的方法最先结束)
4) 栈中的变量都是属于方法的,所以都是局部变量,且局部变量必须初始化值。
5) 栈生命周期与其所属线程的生命周期一致,可以认为栈具有自动销毁机制。


1) 是为实体对象来开辟空间的,换言之就是实体对象的空间都在堆中开辟。凡是被new出来的都是对象。
2) 堆中的变量是对象变量,因为是属于对象的,且是随着对象的创建而产生随着对象的销毁而销毁。
3) 堆中的变量(对象变量)都有默认值:
整数:0 浮点型:0.0 布尔型:false char型:’ ’ 对象:null
4) 堆没有自动销毁机制,它里面的垃圾由垃圾回收器负责收集(通过收集算法判断哪个对象属于垃圾然后再清理)
5) 堆中的空间都有一个16进制的首地址作为地址进行区分。

方法区
方法区中存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
常量是存放在方法区中的运行时常量池中的。
 

92. 请列举一下,在JAVA虚拟机中,哪些对象可作为ROOT对象?

1. 虚拟机栈中的引用对象
2. 本地方法栈中JNI引用对象
3. 方法区中类静态属性引用的对象
4. 方法区中常量引用对象(如字符串常量池)
5. 被同步锁持有的对象
6. 内部引用(如基本数据类型对应的class对象、常驻的异常对象NullPointerException等、系统类加载器)

7. 反映java虚拟机内部情况的JMXBean、JVMT中注册的回调,本地代码缓存等。

93.GC中如何判断对象是否需要被回收?

一、引用计数

Java在GC时会看这个对象是否与其他引用有关联,如果存在引用关系则表示这个对象还有用,不能被回收,如果不存在引用关系则可基本定性为可被回收的对象。优点:效率高;缺点:无法解决循环引用的问题。

二、可达性分析
为了解决循环引用的问题,Java采用了可达性分析的方式,主要是通过Roots对象作为起点进行搜索,搜索的路径被称为“引用链”,当一个对象到Roots没有任何引用链相连的时,证明此对象不可用,当然被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就会成为可回收对象。能够被回收其实主要看finalize()方法有没有与引用链上的对象关联,如果在finalize()方法中有关联则自救成功,该对象不可被回收,反之如果没有关联则成功被 二次标记,就可以称为要被回收的垃圾了。

更多:必知必会JVM垃圾回收——对象搜索算法与回收算法 | Zack's Blog

GC是如何判断一个对象为"垃圾"的?被GC判断为"垃圾"的对象一定会被回收吗?_不能说的秘密的博客-CSDN博客

94.请说明一下JAVA虚拟机的作用是什么?

解释运行字节码程序消除平台相关性。

jvm将java字节码解释为具体平台的具体指令。一般的高级语言如要在不同的平台上运行,至少需要编译成不同的目标代码。而引入JVM后,Java语言在不同平台上运行时不需要重新编译。Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。

95.请说明一下eden区和survial区的含义以及工作原理?

目前主流的虚拟机实现都采用了分代收集的思想,把整个堆区划分为新生代和老年代;新生代又被划分成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% 的空间浪费、复制对象的开销等。

96. 请简单描述一下类的加载过程

【a】加载

通过一个类的全限定名获取该类的二进制流;
将该二进制流的静态存储结构转化为方法区运行时数据结构;
在内存中生成该类的Class对象,作为该类的数据访问入口;
【b】链接

链接阶段又可以分为验证、准备和解析三个阶段。

验证: 验证的目的是为了确保Class文件的字节流中的信息不会危害到虚拟机;
        文件格式验证 :验证字节流是否符合Class文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型等等;
        元数据验证 :对字节码描述的信息进行语义分析,如这个类是否有父类,是否继承了不被继承的类等等;
        字节码验证 :通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等等;
        符号引用验证 :为了确保解析动作能正确执行;


准备: 准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。   

 //在准备阶段value初始值为0,在初始化阶段才会变为123
 public static int value = 123  
 
 //注意下面是常量, 在准备阶段之后,number 的值将是 3,而不是 0
 public static final int number = 3;

解析: 主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。


【c】初始化

初始化是类加载的最后一步,真正开始执行类中定义的Java程序代码;
【d】使用

当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码;
【e】卸载

当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存;
 

 97.请简单说明一下JVM的回收算法以及它的回收器是什么?还有CMS采用哪种回收算法?使用CMS怎样解决内存碎片的问题呢?

更多:必知必会JVM垃圾回收——对象搜索算法与回收算法 | Zack's Blog

掘金

CMS:采用标记清除算法

解决这个问题的办法就是可以让CMS在进行一定次数的Full GC(标记清除)的时候进行一次标记整理算法,CMS提供了以下参数来控制:

-XX:UseCMSCompactAtFullCollection -XX:CMSFullGCBeforeCompaction=5

也就是CMS在进行5次Full GC(标记清除)之后进行一次标记整理算法,从而可以控制老年带的碎片在一定的数量以内,甚至可以配置CMS在每次Full GC的时候都进行内存的整理。

98.请简单描述一下垃圾回收器的基本原理是什么?还有垃圾回收器可以马上回收内存吗?并且有什么办法可以主动通知虚拟机进行垃圾回收呢?

对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。

  可以。

  程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。强制执行垃圾回收:System.gc()。Runtime.getRuntime().gc()

99. 请问,在java中会存在内存泄漏吗?请简单描述一下。

更多:什么是内存溢出以及java中内存泄漏5种情况的总结 - 甜菜波波 - 博客园

java中的内存泄露的情况:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是java中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。
检查java中的内存泄露,一定要让程序将各种分支情况都完整执行到程序结束,然后看某个对象是否被使用过,如果没有,则才能判定这个对象属于内存泄露。

如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。

内存泄露的另外一种情况:当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露

100. 什么原因会导致minor gc运行频繁?同样的,什么原因又会导致minor gc运行很慢?请简要说明一下

更多:JVM: GC过程总结(minor GC 和 Full GC)_H.SH的博客-CSDN博客

minor gc运行的很频繁可能是什么原因引起的?

1、 产生了太多朝生夕灭的对象导致需要频繁minor gc

2、 新生代空间设置的比较小

minor gc运行的很慢有可能是什么原因引起的?

1、 新生代空间设置过大。

2、 对象引用链较长,进行可达性分析时间较长。

3、 新生代survivor区设置的比较小,清理后剩余的对象不能装进去需要移动到老年代,造成移动开销。

4、 内存分配担保失败,由minor gc转化为full gc

5、 采用的垃圾收集器效率较低,比如新生代使用serial收集器

GC触发条件
Minor GC触发条件:Eden区满时

Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法区空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
 

101. 请问运行时异常与受检异常有什么区别?

异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。

102.请问什么是java序列化?以及如何实现java序列化?

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

更多:什么是Java序列化,如何实现java序列化 - 牧之君 - 博客园

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

字节流----字节输入流:InputStream,字节输出流:OutputStream
字符流----字符输入流:Reader,字符输出流:Writer
什么时候使用字节流、什么时候使用字符流?
InputStream 和OutputStream,两个是为字节流设计的,主要用来处理字节或二进制对象,
Reader和 Writer.两个是为字符流(一个字符占两个字节)设计的,主要用来处理字符或字符串.
更多:java io中有几种类型的流?JDK为每种类型的流 提供了一些抽象类以供继承,请说出他们分别是哪些类?_不见风雨怎能见彩虹-CSDN博客

104.请问error和exception有什么区别?

Exception和Error都是继承了Throwable类,在java中只有Throwable类型的实例才可以被抛出(throw)或者捕获(catch),他是异常处理机制的基本组成类型。
Exception和Error体现了java平台设计者对不同异常情况的分类,Exception是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应的处理。
Error是指正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序(比如JVM自身)处于非正常状态,不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如OutOfMemoryError之类,都是Error的子类。
Exception又分为可检查(checked)异常和不检查(unchecked)异常,可检查异常在源码里必须显示的进行捕获处理,这里是编译期检查的一部分。前面我们介绍的不可查的Error,是Throwable不是Exception。
不检查异常就是所谓的运行时异常,类似NullPointerException,ArrayIndexOutOfBoundsExceptin之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译器强制要求。

105.synchronized锁升级

更多:synchronized同步锁的四种状态 | 嘎里三分熟

偏向锁通过对比 Mark Word 解决加锁问题,避免执行CAS操作。

轻量级锁是通过用 CAS 操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。

重量级锁是将除了拥有锁的线程以外的线程都阻塞。

106.JDK动态代理

107.单例模式

108.ThreadLocal继承

109.类加载器

jvm之java类加载机制和类加载器(ClassLoader)的详解_翻过一座座山-CSDN博客_类加载器

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值