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动态代理,AnnotationInvocationHandlerProxy实现动态代理。

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(),则升级为重量级锁
  • 轻量级锁
    • 标志位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系统变量指定的路径中的所有类库
    • 应用类加载器
      • 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

JVM调优及出错查找

  • Java8新特性

  • 接口的默认方法(虚拟扩展方法)

    • Java7及之前的版本不支持接口实现方法。

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 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" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏