Java面试要点提醒
Java 基础知识
装箱与拆箱
Integer a = 1; // 调用的是Integer.valueOf(int i)方法
int m = a; // 调用的是Integer.intValue()方法
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
/**********************************/
Integer a = new Integer(1);
System.out.println(a == b);
// false
Byte
,Short
,Integer
,Long
类型的缓冲池值的范围均为-128 ~ 127
Character
类型的缓冲池为0 ~ 127
Boolean
缓冲池为TRUE/FALSE
Double
,Float
没有缓冲池
为什么重写equals()时,一定要重写hashCode()
hashCode()方法一般在HashMap中使用
hashCode()是和equals()配对使用的
Java泛型机制
Java泛型其实是伪泛型,在编译过程中会进行类型擦除。
而我们在使用一些泛型类时,IDE会出现错误提示。这是因为Java编译器通过先检查代码中泛型的类型,然后进行类型擦除,再进行编译。
Java编译器的泛型类型检查针对泛型的引用。类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
由于类型擦除的存在,泛型类型不可实例化,泛型数组也不可初始化(泛型数组可通过通配符?
初始化,不过需要强制转换);泛型数组可通过反射
初始化,java.lang.reflect.Array.newInstance(Class<T> componentType, int length)
。
泛型类中的静态方法和静态变量*不可以使用泛型类所声明的泛型类型参数;
如何获取泛型:可以通过反射(java.lang.reflect.Type
)获取泛型。
异常中泛型的使用:不能抛出或者捕获泛型类的对象;不能在*catch语句中使用泛型;但是在异常声明中可以使用泛型。例子如下:
public static<T extends Throwable> void doWork(T t) throws T {
try{
...
} catch(Throwable realCause) {
t.initCause(realCause);
throw t;
}
}
泛型擦除会导致继承出现问题?
继承会重写方法,但是Java泛型在编译期会将泛型擦除,那么继承泛型的子类重写的方法和父类方法的参数类型不一致,这还是重写吗?
解决方法:JVM会在内部生成一个桥方法(桥方法的参数类型是擦除后的类型)来调用我们重写的方法,这样就另类实现了重写。Methon
类中的isBridge()
方法就是判断该方式是否为桥方法。
Java注解机制
不能使用关键字extends来继承某个@interface
,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation
接口.
内置注解
- @Override
- @Deprecated
- @SupressWarnings
元注解
Java的注解由原注解组合得到的。
- @Target
- @Retention & @RetetionTarget
- @Documented
- @Inherited
- @Repeatable(Java8)
- @Native(Java8)
注解与反射
注解可以通过反射获得,也是通过反射对注解进行拦截,从而实现各种各样的功能。
注解的原理
注解是一个接口,继承自Annotation
接口。
如何实例化接口?通过jdk动态代理,AnnotationInvocationHandler
和Proxy
实现动态代理。
Java异常机制
try-catch-finally执行顺序
当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句
Java SPI(Service Provider Interface) 服务提供发现机制
Java容器
String的底层
String的底层是一个final char[]
数组,String
支持运算符+
是因为jvm内部将String
转化为StringBuilder
。
Java并发
volatile关键字
- 保证变量的可见性
- 并不能保证并发情况下volatile变量操作后的一致性
- volatile变量在各个线程内可以看作是一致的(物理存储上可能会存在不一致,但是volatile变量在使用时会询问主存并更新状态,跟更新后也回以及写回主存)
- 禁止指令重排序(线程内指令串行化执行)
无锁状态、偏向锁、轻量级锁、重量级锁
- 无锁
- 标识位01
- 偏向锁
- Mark Word中hashcode位记为线程id,标识位01
- 如果hashcode位已经有值,则升级为重量级锁
- 偏向锁过程中,使用了hasCode(),则升级为重量级锁
- Mark Word中hashcode位记为线程id,标识位01
- 轻量级锁
- 标志位00
- 原Mark Word中的值,存储至线程栈帧中
- Mark Word中存储 指向调用栈中的指针地址
- 重量级锁
AQS
- 作用:Java锁和同步器的框架。ReentrantLock等都是基于该
JVM相关知识
Class文件
- 魔数(4个字节 0xCAFEBABE):很多文件使用魔数来验证文件格式,而不是扩展名,这样更安全
- 版本号(4个字节):前两个字节次版本号,后两个字节主版本号
- 常量池入口
- 常量池容量计数值(u2)
- 字面量:文本字符串、被声明为
final
的值 - 符号引用
- 被模块导出或者开放的包
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
- 方法句柄和方法类型(Method Handle, Method type, Invoke Dynamic)
- 动态调用点和动态常量
- 访问标识(2个字节):标注是类还是接口,虚类以及访问权限
- 类索引、父类索引、接口索引
- 字段表集合:声明的变量
- 方法表集合:声明的方法
- 属性表集合
JVM的组成
- 类加载器
- 执行引擎(解释器)
- 本地接口库
- 让其他语言的接口可以为Java所用
- 运行时数据区:(Java内存模型)
- 堆
- 线程共享
- 存放对象实例
- 线程本地分配缓冲(Thread Local Allocation Buffer, TLAB)
- 方法区
- Class实例(类加载)、常量、静态变量、即时编译器编译后的代码缓存
- 运行时常量池
- 类的版本、字段、方法、接口等描述信息
- 常量池表(编译期间生成的字面量和符号引用,有时候直接引用也会存储在常量池表)
- 虚拟机栈
- 局部变量表
- 基本变量、引用、ReturnAdress类型
- 使用slot(槽)存储数据
- 运行时大小不会改变
- 操作数栈
- 动态链接
- 方法出口
- 局部变量表
- 本地方法栈
- 调用本地(native)方法,基本结构同虚拟机栈相似
- 在HotSpot虚拟机中,将虚拟机栈和本地方法栈合二为一了
- 程序计数器
- 堆
对象创建过程
- 类加载检查:检查指令的参数能否在常量池中定位到该类的符号引用, 并检查这个符号引用代表的类是否被加载过、解析和初始化过。如果没有,执行类加载过程。
- 分配内存:对象所需空间在类加载时已经确定,分配内存有两种方法:①指针碰撞和②空闲列表。需要与GC收集器(GC算法)配合使用。
- 同步问题(多线程同时申请分配内存):①同步措施:CAS + 失败重试 ② 使用TLAB(本地线程分配缓冲),当TLAB使用后,分配新的缓冲区使用同步措施。
- 初始化零值:将除对象头外的所有值设为0。如果使用了TLAB,则这一步在分配TLAB时顺便完成。
- 设置对象头
- 执行init方法
并不是所有对象都在堆上分配
- 逃逸分析
- 类型:不逃逸、方法逃逸、线程逃逸
- 分配在栈上
- 分离对象或标量替换:将对象分解为多个局部变量
- 同步锁消除
对象的内存布局
- 对象头
- MarkWord:包含hash code、锁标识位、GC年龄、线程持有的锁、偏向线程的ID、偏向时间戳等。
- 类型指针:指向元数据的指针,表明该对象是哪个类的实例
- 实例数据
- 对齐填充
类加载机制
- 类加载过程
- 加载
- 连接
- 验证:文件格式验证、元数据验证、字节码验证、符号引用验证
- 准备:将类成员变量初始化为0;
final
修饰的成员变量在编译期间已经分配。 - 解析:将符号引用转化为直接引用
- 符号引用:符号引用就是一组符号来描述目标,可以是任何字面量
- 直接引用:直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
- 初始化
- 若有超类,先初始化超类
- 执行静态初始化器和静态初始化成员变量(赋值)
- 类加载器
- 启动类加载器
- C++编写,是JVM的一部分;从JVM看来,类加载器只有两种:1)启动类加载器;2)其他加载器
- 加载
{JAVA_HOME}/lib
目录下的类 或-Xbootclasspath
参数所指定存放路径中的类 - Java程序无法直接引用,使用null来指代
- 扩展类加载器
- Sun公司实现,负责加载
{JAVA_HOME}/lib/ext
目录 或java.ext.dirs
系统变量指定的路径中的所有类库
- Sun公司实现,负责加载
- 应用类加载器
ClassLoader.getSystemClassLoader()
实现,又称 系统类加载器- 负责加载
ClassPath
上的所有类库
- 启动类加载器
GC垃圾回收
- 判断一个对象是否存活
- 引用计数法
- 难以解决循环引用问题
- 可达性分析算法
- 从
GC Roots
向下搜索,在引用链上的对象都是存活对象GC Roots
对象包括:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象、同步锁持有的对象等。
- 从
- 引用计数法
- 回收方法区
- 废弃常量
- 不再使用的类型
- 该类的所有实例已经被回收
- 该类的加载器已经被回收
- 该类的
java.lang.Class
对象没有在任何地方被引用
- 垃圾收集算法
- 标记清除算法
- 复制算法
- 标记整理算法
- 分代收集算法
- HotSpot执行细节
- 根节点枚举
- 所有的收集器需要在根节点枚举时暂停用户线程
- 借助OopMap结构
- 安全点
- 不能因为每一条指令,而更新OopMap,只会在特定的位置记录这些信息,这些位置被成为安全点。
- 安全区域
- 确保在某一段代码片段之中,引用关系不会发生变化
- 记忆集和卡表
- 记忆集:用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。
- 包含三种记录精度:字长精度、对象精度、卡精度
- 卡表:记忆集卡精度实现的方式。也是最常用的记忆集形式。
- 记忆集:用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。
- 写屏障
- 作用:动态维护卡表状态
- 理解:类似于“引用类型字段赋值”的AOP操作
- 根节点枚举
- 垃圾收集器
- Serial
- 最基础、历史最悠久的收集器
- 单线程收集器、工作时必须暂停所有工作线程
- 简单高效
- 新生代采用复制算法,老年代使用标记-整理算法
- ParNew
- Serial收集器的多线程版本
- 与CMS配合使用
- Parallel Scavenge
- 更关注吞吐量(高效率利用cpu),CMS等收集器更关注的是用户线程的停顿时间(提高用户体验)
- JDK1.8默认收集器
- Serial Old收集器
- Serial收集器的老年代版本
- ①搭配Parallel Scavenge使用 ②作为CMS收集器的后备方案
- Parallel Old
- Parallel Scavenge的老年代版本
- CMS收集器(Concurrent Mark Sweep)
- 以获取最短回收停顿时间为目标的收集器
- HotSpot第一款并发收集器;并发收集、低停顿
- 步骤:
- 初始标记:stop the world
- 并发标记
- 重新标记:stop the world
- 并发清除
- 缺点:
- 对cpu资源敏感
- 无法处理浮动垃圾
- 标记清除算法会产生大量的空间碎片,当碎片过多的时候,会合并空间,该过程是无法并发的,会浪费大量的时间。
- Garbage First收集器
- “全功能的垃圾收集器”
- 面向服务器端应用的垃圾收集器,主要针对配备多颗处理器及⼤容量内存的机器. 以极⾼概率满⾜ GC 停顿时间要求的同时,还具备⾼吞吐量性能特征
- 收集器面向局部的设计思路——基于Region的内存布局形式
- 将Java堆分为许多大小相等的独立区域(Region),每一个Region可以根据需要,扮演Eden区、Survivor区和老年代。
- Region中有一类特殊的Humongous区域,专门存储大对象(超过Region一半的空间都是大对象)
- Region的大小必须是2的N次幂
- 特点:
- 并行与并发:充分利用多cpu、多核的优势
- 分代收集:不需要其他收集器配合使用,但仍保留分代的概念
- 空间整合:从整体上看是“标记-整理”,从部分上看是“标记-复制”
- 可预测的停顿:低停顿,并能建立可预测的停顿时间模型
- 步骤:
- 初始标记(stop the world)
- 并发标记
- 最终标记(stop the world)
- 筛选回收(stop the world)
- G1收集器在后台维护了一个优先队列,每次根据允许的收集时间,有限选择回收价值最大的Region
- Serial
JVM调优及出错查找
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 525244039@qq.com
文章标题:Java面试要点提醒
文章字数:3.6k
本文作者:Zikun
发布时间:2021-08-24, 15:56:02
最后更新:2021-08-24, 15:56:02
原始链接:http://zikun97.github.io/2021/08/24/Java%E9%9D%A2%E8%AF%95%E8%A6%81%E7%82%B9%E6%8F%90%E9%86%92/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。