参考来源:
Java基础面试:
https://thinkwon.blog.csdn.net/article/details/104390612
Java基础:https://github.com/Snailclimb/JavaGuide#%E5%9F%BA%E7%A1%80
类变量初始化顺序:
https://www.cnblogs.com/cafebabe-yun/p/11499080.html
局部变量和全局变量的区别:
https://blog.csdn.net/wanghuan0/article/details/81158007
接口和抽象类的十二种异同:
https://www.zhihu.com/question/20149818
基础
1. 请你讲讲什么叫Write once,Run Anywhere
一次编写,到处执行!
支持方式:在不同的操作系统上安装不同的Java虚拟机(JVM),就可以运行编写好的Java程序啦!
2. JDK和JRE有什么区别?
JDK:Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等
JRE
Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包
3. 面向过程和面向对象的区别?面向对象有什么优点?
面向过程:性能更高,因为类调用需要实例化,开销更大。
面向对象:易维护、易复用、易扩展,可以设计出低耦合的系统。
4. 详述面向对象的特点
抽象:把一类对象的共同特征抽象总结出来构造类的过程,包括数据抽象的行为抽象两方面,抽象只关注对象有哪些属性和行为,并不关心这些行为的细节。
面向对象三大特性:封装、继承、多态。
封装性:封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法。(隐藏独享)
继承:继承是使用已经存在的类作为基类建立新类的技术,新类的定义可以增加新的数据或者新的功能,也可以用父类的功能,但不能选择性地继承父类,通过使用继承继承我们可以非常方便地复用以前的代码。
多态:指的是程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时不确定,而是在程序运行期才确定。
5. 8种基本数据类型分别所占存储空间的大小?
神秘代码1248842?(单位是字节)
byte,short,int,long,float,double,char,boolean
boolean类型所占存储空间的大小没有明确指定,仅定义为能够取字面值true或false(参考自《Java编程思想》(第4版) Page 23)
6. 各基本数据类型的包装类
Byte,Short,Integer,Long,Float,Double,Character,Boolean
Java泛型了解么?什么是类型擦除?介绍一下常用的通配符?
不了解,没听过,通配符是什么?
7. &和&&的区别
&不短路,&&短路
短语与只的是&&前面的条件为false则&&后面的语句就不进行判断(也不执行了)
&可用于按位与和逻辑与
8. 请你讲讲<<和>>和>>>的区别
左移右移,循环右移
9. 请你讲讲==和equals的区别?
==:
- 对于基本类型数据:比较的是值
- 对于引用数据类型:比较的是其在内存中的地址是否一致
equals:
- 对于基本数据类型:请使用==
- 对于引用数据类型:比较的是对象是否相等
10. equals()方法是干什么用的?
11. 抽象类和接口
对于成员变量:接口默认修饰为public static final;抽象类全部支持
对于方法:接口只接受public,abstract(1.8还接受默认方法,静态方法);抽象类只不支持抽象静态方法和抽象最终方法(abstract本就不能与static和final共存)
12. hashcode()方法是干什么用的?
13. 为什么重写equals()要重写hashcode()?
14. 为什么两个对象有相同的hashcode值,它们也不一定是相等的?
不一定相等
15. 请你简述一下自动装箱和拆箱?
16. 你知道常量池吗
运行时常量池是Java方法区的一部分
字符串常量池1.6和1.6之前在方法区中,1.7以后移入了堆中。
17. 你知道序列化的反序列化吗(Serializable和transient)
18. 为什么Java只有值传递 *
19. 重载和重写的异同
区别与相同 | 重载方法 | 重写方法 |
---|---|---|
位置 | 同一个类中 | 父子类中 |
方法名 | 不变 | 不变 |
参数 | 类型或数量或顺序应该改变 | 不能改变 |
抛出异常 | 可修改 | 更小或相等 |
返回类型 | 可以修改 | 更小或者相等 |
访问修饰符 | 可以修改 | 可以降低限制 |
使用细节 | 取决于传入参数的静态类型 | 取决于传入参数的实际类型 |
发生阶段 | 编译期 | 运行期 |
- 重载方法的返回类型可以修改,但是不能作为区别重载方法的依据,同参的同名的两个方法返回值不同,编译不一定可以通过,但是可以存在于同一个Class文件中
参考:https://blog.csdn.net/Hellw0rld/article/details/109588473
20. 深拷贝和浅拷贝
https://blog.csdn.net/riemann_/article/details/87217229
21. 构造器 Constructor 是否可被 override
22. 在 Java 中定义一个不做事且没有参数的构造方法的作用
23. 成员变量与局部变量的区别有哪些 (1+4种)4种区别
24. 构造方法的作用和特性
25. 你知道具有继承关系的两个类之间的初始化顺序是什么吗(有static成员变量的情况呢?)
26. 对象的相等与指向他们的引用相等,两者有什么不同?
27. 在一个静态方法内调用一个非静态成员为什么是非法的
28. 接口和抽象类的异同(12种!)
29. this关键字的三种用法
30. super关键字的三种用法
31. 请介绍一下String,StringBuilder,StringBuffer的特点和异同
32. Object类的11种方法
33. 获取用键盘输入常用的两种方法
34. 请简述Throwable和它的两个子类
35. try-catch-finally
容器
36. 请你手绘Java容器类库的简化图
?????
37. 你知道Collection容器的19种基本方法吗
38. ArrayList和LinkedList
39. Vector和Stack
40. ArrayDeque和PriorityQueue
41. HashSet和TreeSet
42. HashMap和TreeMap
43. HashTable和ConcurrentHashTable和ConcurrentHashMap
44. LinkedHashMap和WeakHashMap
45. CopyOnWriteArrayList和CopyOnWriteArraySet
46. 请你讲出上述各容器的底层数据结构构成,查找插入删除时间复杂度,是否允许空键或者空值,扩容机制,线程安全性,常用方法
多线程篇
47. 进程有哪些基本状态,他们之间是如何进行转换的(3种或更多)
48. 线程有哪些基本状态,以及他们之间是如何转换的(6种)
49. 进程和线程之间的区别
50. 你知道有哪些锁
情比金坚锁,强人锁男锁,亚索
51. 讲讲什么是乐观锁和悲观锁
https://www.jianshu.com/p/d2ac26ca6525
52. synchronized关键字和lock类的异同
53. Java的I/O流分为几种
54. 既然有了字节流为啥还要字符流昂?
55. BIO,NIO,AIO
Java虚拟机
第二部分:自动内存管理
56. 运行时数据区域有哪些
- 程序计数器(线程独占)
- Java虚拟机栈(线程独占)
- 本地方法栈(线程独占)
- Java堆
- 方法区
57. 程序计数器
- 可以被看作是当前线程所执行的字节码的行号指示器;是程序控制流的指示器
- 可以保证线程切换后能恢复到正确的执行位置
- 唯一一个没有规定任何OOME情况的区域
58. Java虚拟机栈
- 是线程私有的;生命周期与线程相同
- 每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
- 局部变量表包括:基本数据类型(8种),对象引用,returnAddress类型
- 线程请求的栈深度大于虚拟机允许的深度,抛SOE异常。
59. 本地方法栈
- 为虚拟机使用到的本地(Native)方法服务
- 栈深度溢出或栈扩展失败抛SOE和OOM异常
60. Java堆
- 线程共享
- 几乎所有对象实例都在这里分配内存
- 是垃圾收集管理的内存区域
- 没有内存完成实例分配 || 堆无法再扩展,抛出OOME异常。
61. 方法区(非堆)
- 线程共享
- 用于存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
- 方法区无法满足新的内存分配需求时,抛出OOME异常
62. 运行时常量池
- 是方法区的一部分
- 用于存放编译器生成的各种字面量和符号引用
- 运行期也可以将新的常量放入池中
- 常量池无法申请到内存时会抛出OOME异常
63. 直接内存
直接内存( Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。
在JDK 1.4中新加人了NIO ( New Input/Output) 类,引入了一种基干通道( Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配准外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作,这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括物理内存、SWAP 分区或者分页文件)大小以及处理器寻址空间的限制,一般服务器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,但经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。
64. 对象的创建过程
- 检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
- 虚拟机为新生对象分配内存。
- 虚拟机对对象进行必要的设置,例如这个对象是那个哪的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的GC分代年龄等信息。
65. Java虚拟机规范中的两种异常
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
- 如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出OutOfMemoryError异常。
66. 运行时常量池是属于方法区的一部分
67. JDK7起,原本存放在永久代的字符串常量池被移至Java堆之中
68. JDK8后,永久代完全退出了历史舞台,元空间作为其替代者登场
69. 垃圾回收的对象
程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭这几个区域就不需要过多考虑如何挥手的问题,当方法结束或者线程结束时,内存自然就跟随着回收了。
70. 如何判断对象已死
策略1:计数算法。在对象中添加一个引用计数器,每有一个地方引用它,计数器值+1,当引用失效时,计数器值就-1。任何时刻计数器为零的对象就是不可能再被使用的。
弊端:难以解决对象相互循环引用的问题。
策略2:可达性分析算法。通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
71. 引用类型
- 强引用:Object obj = new Object();
- 软引用:还有用,但非必须。快要发生内存异常前,把这些对象列入回收范围进行第二次挥手
- 弱引用:被该引用关联的对象只能生存到下一次垃圾回收集发生为止
- 虚引用:不对其生存时间构成影响,是为了能在这个对象被收集器回收时收到一个系统通知
72. 两次标记过程
- 对对象进行可达性分析后发现没有与GC Roots相连接的引用链,被第一次标记。没有覆盖finalize()方法 or finalize()方法已经被虚拟机调用过,则虚拟机视其为“没有必要执行”。
- 如果这个对象被判定为有必要执行finalize()方法,对象就被放在一个队列中,等待finalize()方法被执行;稍后收集器对队列中的对象进行第二次小规模标记(此过程对象可在finalize()中拯救自己),它被移出“即将回收”集合。对象还没被逃脱,那就真的被回收了。
73. 分代假说
弱分代假说:绝大多数对象都是朝生夕灭的。
强分代假说:熬过越多次垃圾收集过程的对象越难消亡。
74. 哪些对象可以当做 GC Roots
75. 垃圾收集算法
- 标记-清除算法:标记并清除,或者标记并不清除
缺点:执行效率不稳定;内存碎片化 - 标记-复制算法:半区复制,当一块内存用完了,就将还存活着的对象复制- 到另外一块上面,然后把使用过的内存空间一次清理掉。(有其他优化设计)。
缺点:空间浪费大 - 标记-整理算法:标记并将所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存、
缺点:移动存活对象并更新所有引用这些对象的地方是负重极高的操作,而且必须全程暂停用户应用程序
第三部分:虚拟机执行子系统
76. 类加载的时机
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段。
这些过程按照此顺序按部就班地开始(解析有小例外)
加载
- 通过一个类的全限定类名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证
确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当做代码运行后不会危害虚拟机自身的安全,可大致分为以下4个阶段的检验动作。
- 文件格式验证:验证字节流是否符合Class文件格式的规范
- 元数据验证:对字节码描述的信息进行语义分析
- 字节码验证:通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的。
- 符号引用验证:对类自身以外的各类信息进行匹配性校验
准备
正式为类中定义的变量(静态变量)分配内存并设置类变量初始值的阶段
解析
是Java虚拟机将常量池内的符号引用替换为直接引用的过程
初始化
初始化阶段就是执行类构造器< clinit >()方法的过程
77. 类加载器
通过一个类的全限定名来获取描述该类的二进制字节流,实现这个动作的代码被称为“类加载器”。
78. 双亲委派模型
双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。
如果没有双亲委派模型,系统出现同名类时,Java类型体系中最基础的行为就无从保证了(可以正常编译,无法被加载运行)
第五部分:高效并发
并发处理的广泛应用是Amdahl定律代替摩尔定律成为计算机性能发展源动力的根本原因,也是人类压栈计算机运算能力的最有力武器。
每条线程有自己的工作内存,线程的工作内存中保存了被该线程使用的变量的主内存副本,线程对变量的所有操作(读取、赋值)等都必须在工作内存中进行,而不能直接读写主内存中的数据
79. Java内存模型中的8种操作(是原子操作)
lock、unlock、read、load、use、assign、store、write
80. volatile关键字
保证变量对所有线程的可见性;禁止指令重排序优化
81. 原子性保证
synchronized关键字、java.util.concurrent中的锁或原子类
82. 可见性保证
synchronized关键字、final关键字、volatile关键字、
83. 有序性保证
synchronized关键字、volatile关键字
84. 先行发生原则(Happends-Before原则)
- 程序次序规则:在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。注意,这里说的是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
- 管程锁定规则:一个unlock操作先行发生与后面对同一个锁的lock操作。
- volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样指时间上的先后。
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
- 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
- 线程终结规则:一个对象的初始化完成先行发生于它的finalize()方法的开始。
- 传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行于操作C的结论。
85. 线程有哪些状态
- 新建
- 运行
- 无限期等待
- 限期等待
- 阻塞
- 结束
86. 什么是线程安全
当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的。
87. 什么是同步
同步是指在多个线程并发访问共享数据时,保证共享数据在同一时刻只被一条(或者是一些,当使用信号量的时候)线程使用
88. 什么是互斥
互斥是实现同步的一种手段,临界区、互斥量、信号量都是常见的互斥实现方式
89. synchronized关键字使用注意
synchronized是Java语言中一个重量级操作
持有锁是一个重量级操作,因为阻塞或者唤醒一条线程,需要在操作系统中进行用户态和核心态的转换,而进行这种状态转换需要耗费很多处理器时间。
90. 基于Lock接口,用户能够以非块结构来实现互斥同步
和synchronized相比增加的高级功能有
- 等待可中断:持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情
- 公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序来一次获得锁。
- 锁绑定多个条件:一个RenntrantLock对象可以同时绑定多个Condition对象。
91. CAS的ABA问题
如果一个变量初次读取是A值,在CAS准备赋值的时候检查到它仍然为A值,但可能在初次读取和再次检查期间,这个变量被修改成为了B值,后来又改成了A值,那么CAS操作就会误认为它从来没有被改变过。
锁优化策略
92. 自旋锁
如果物理机器有一个以上的处理器或者处理器核心,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一会”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。
93. 自适应自旋锁
自适应意味着自旋的时间不再是固定的了,而是由前一次在同个锁上的自旋时间及锁的拥有者的状态来决定的。 如果在同一个锁对象上, 自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而允许自旋等待持续相对更长的时间,比如持续100次忙循环。另一方面,如果对于某个锁,自旋很少成功获得过锁,那在以后要获取这个锁时将有可能直接省略掉自旋过程,以避免浪费处理器资源。
94. 锁消除
如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当做栈上数据对待,认为它们是线程私有的,同步加锁自然就无须再进行。
95. 锁粗化
如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。
96. 轻量级锁
97. 偏向锁