2023年Java核心技术面试第一篇(篇篇万字精讲)

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: 2023年Java核心技术面试第一篇(篇篇万字精讲)

1.对于Java平台理解



1.1 "一次编译,到处运行"是Java语言跨平台的特性。


Java的跨平台特性与Java虚拟机的存在密不可分,可在不同的环境里面运行,window平台和Linux平台都有对应的JDK,安装JDK后就有了Java语言的运行环境,Java语言和其他编程语言并没有特别大的区别,并不是Java语言可以跨平台,而是在不同的平台都有让Java语言运行的环境,所以才有,Java一次编译,到处运行的结果。  


1.2跨平台的语言中Java是较为成熟的一种。


"一次编译,到处运行"这种效果与编译器有关,编程语言的处理需要编译器和解释器。

程序从源代码到运行的的三个阶段 编码-编译-运行-调试


1.3Java在编译阶段就体现了跨平台的特点。


编译过程:Java源代码转化成.class文件字节码,这是第一次编译。


.class文件就是可以到处运行的文件,然后Java字节码会被转化为目标机器代码,由JVM来执行,这是Java的第二次编译


"到处运行"的关键和前提就是JVM,在第二次编译的时候JVM起到关键的作用,在可以运行Java虚拟机的地方都内含一个JVM操作系统,使Java提供了各种平台的虚拟机制,实现了"到处运行"的效果。


1.4Java不是并不是编译机制,而是解释机制。


Java字节码的设计考虑到了JIT即时编译的方式,将字节码直接转化成高性能的本地机器码。


Java特性:


面向对象(封装,继承,多态)

平台无关性(JVM运行.class文件)

语言(泛型,Lambda)

类库(集合,并发,网络,IO/NIO)

JRE(Java运行环境,JVM,类库)

JDK(Java开发工具,包括JRE,Javac)


1.5Java不是解析运行的。


1.Java源代码通过Javac编译成.class文件。


2..class文件经JVM解析或编译运行。


(1)解析:class文件经过JVM内嵌的解析器解析执行。

(2)编译:存在JIT编译器(即时编译器) 把经常运行的代码作为”热点代码“编译与本地平台相关的机器码,并进行各种层次的优化。

(3)AOT编辑器:Java9提供的直接将所有代码编译成机器码执行。


1.6从不同角度看待Java


宏观角度:

与C/C++最大的不同点在于,C/C++编程是面向操作系统的,需要了解操作系统之间的差异。

Java平台通过虚拟机屏蔽了操作系统的底层细节,使得不用关心操作系统的之间差异,通过增加一个间接的中间层来进行解耦,虚拟机操作系统,HTTP,TCP/IP等。


微观角度:


Java语言本身,JDK中所提供的核心类库和相关工具

Java核心类库:集合类,线程相关类,IO,NIO,JUC并发包

大部分情况我们只需要考虑Java语言本身,无需关注底层细节,包括对内存的分配和回收,交给了GC。

Java虚拟机以及其他包含GC


解释JIT,AOT.


写个程序直接执行字节码就是解释执行。写个程序运行时把字节码动态翻译成机器码就是jit。

写个程序把java源代码直接翻译为机器码就是aot。造个CPU直接执行字节码,字节码就是机

器码。


jre为Java提供了必要的运行时环境,JDK为java提供了必要的开发环境


1.7理解扩展:


任何软件问题都可以通过加一层来解决。

有了编译器就屏蔽了不同机器语言的区别。

有了JVM就屏蔽了不同操作系统的区别。

有了TCP/IP就屏蔽了不同系统之间通讯的差异。

有了语音识别和翻译就屏蔽了不同语言的差异。

也许有一天人工智能可以直接把自然语言翻译成机器码直接生产可用的软件。


2 .Exception和Error的区别



2.1 Exception和Error 都是继承throwable类。


在Java里面只有Throwable类型的实例才可以被抛出(throw)和捕获(catch)


2.2 Exception。


程序正常运行中,可以预料的意外情况,并且应该被捕获,进行相应的处理。


Exception分为可检查(checked异常)和不检查(unchecked)异常,可检查异常在源代码里必须显式地进行捕获处理,不检查异常就是类似 NullPointerException,ArrayIndexOutOfBoundsException 之类,根据具体需要来进行判断是否需要捕获


2.3 Error。


正常情况下,不太可能出现的问题,绝大部分的Error都会导致程序处于非正常的不可恢复的状态,因为是非正常情况,所以不便于也不需要捕获,常见OutOfMemoryError类,都是Error的子类。


2.4 考点分析:


分析Exception和区别,从概念的角度考察Java的处理机制

第一:理解Throwable,Exception,Error的设计和分类,掌握应用最为广泛的子类,以及如何自定义异常。


扩展猜想:


如:你了解哪些Error,Exception或者RuntimeException?

NoClassDefFoundError 和ClassNotFoundException 有什么区别?


使用场景:

属于类加载相关的异常,在Java中用于表示类无法被找到的情况。


区别如下:

1.NoClassDefFoundError是Error类型的异常,而ClassNotFoundException是一个Checked Exception(受检异常),

并且NoClassDefFoundError是在运行时发生,通常是虚拟机在加载某个类的过程中,找不到该类的定义,可能是由于编译期间缺少了该类的依赖项,或者在运行时环境中缺少了该类的相关库文件。

ClassNotFoundException 是在编译时或运行时明确引发的,通常是因为尝试使用 Class.forName() 或 ClassLoader.loadClass() 方法来动态加载类时找不到该类。


2.NoClassDefFoundError 是一个致命错误,意味着虚拟机无法找到类的定义,导致程序无法继续执行。一旦出现 NoClassDefFoundError,通常需要检查类路径设置、类文件是否存在以及依赖关系是否正确,


ClassNotFoundException 也表示找不到类,但它是一个异常,可以通过 try-catch 块进行捕获和处理。可以根据具体的情况采取适当的措施,如提供正确的类路径、检查类名拼写、确认依赖是否正确等


小结:NoClassDefFoundError 是一个致命的错误,表示虚拟机在运行时找不到类的定义;ClassNotFoundException 则是一个 Checked Exception,表示编译时或运行时明确引发的找不到类的异常


第二,理解 Java 语言中操作 Throwable 的元素和实践。掌握最基本的语法,如

try-catch-finally 块,throw、throws 关键字等,要懂得处理典型场景。


try-catch-finally 块:

  • try-catch-finally 块用于捕获和处理异常。在 try 块中编写可能引发异常的代码,如果发生异常,则会跳转到与之匹配的 catch 块进行处理。
  • catch 块声明了捕获的异常类型,并在块内部提供处理逻辑。可以有多个 catch 块,每个 catch 块用于处理不同类型的异常。
  • finally 块是可选的,它包含的代码无论是否发生异常都会执行,通常用于释放资源或执行清理操作。


try {
    // 可能引发异常的代码
} catch (ExceptionType1 e1) {
    // 处理 ExceptionType1 异常的逻辑
} catch (ExceptionType2 e2) {
    // 处理 ExceptionType2 异常的逻辑
} finally {
    // 清理操作或释放资源
}


2.5  throw 关键字:


  • throw 关键字用于手动抛出一个异常对象。通过 throw 关键字可以在代码中显式地抛出异常,使程序进入异常处理流程。
    throw 后面跟着一个异常对象,该对象必须是 Throwable 类或其子类的实例
throw new ExceptionType("异常信息");


2.6  throws关键字:


  • throws 关键字用于在方法声明中指定可能会抛出的异常。当一个方法可能引发某种类型的异常时,可以使用 throws 关键字在方法签名中声明该异常。
    throws 关键字后面跟着一个或多个异常类型,表示该方法有可能抛出这些异常,通知调用者处理这些异常。
1. public void methodName() throws ExceptionType1, ExceptionType2 {
2. // 方法体
3. }


2.7 典型场景的处理包括:


  • 使用 try-catch-finally 块捕获和处理特定类型的异常,以防止程序意外终止,同时提供合理的异常处理逻辑。
  • 在 catch 块中根据具体的业务需求进行异常处理,例如打印日志、回滚事务、给用户友好的提示等。
  • 使用 throw 关键字手动抛出自定义异常,在需要的情况下主动中断程序流程,并传递异常信息。
  • 在方法声明中使用 throws 关键字声明可能会抛出的异常,以告知调用者方法可能引发的异常,让其选择相应的处理方式。


注意点:


第一,尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常。

第二,不要生吞(swallow)异常。可能会导致非常难以诊断的诡异情况。

注意finally的使用。


3.final,finally,finalize的不同的地方?



3.1 final。


用来修饰类,方法,变量,进行修饰的class代表不可继承,final的变量代表不可以修改,并且final的方法不可以被重写(override)


finally:则是Java保证重点代码一定要被执行的一种机制,可以使用try-finallly或者try-catch-finally来进行关闭JDBC连接,保证unlock锁等动作。


finalize是基础类java.lang.Object的一个方法,保证对象在垃圾收集前完成特定资源的回收。


不推荐使用了,JDK9中被标记deprecated


3.2 考点分析:


经典的Java基础问题,上面都是从语法和使用实践的角度出发的。

扩展方向往:性能,并发,对象周期或者垃圾收集过程


final:避免API使用者进行更改,保证平台安全的必要手段,使用final进行修饰参数或者变量,避免意外赋值导致的编程错误。

利用final可能有助于JVM将方法进行内联,可以进行改善编译器进行条件编译的能力


3.3 final方法的内联优化可能包括以下方面:


3.3.1直接插入方法体:


编译器可以直接将final方法的代码插入到调用处,避免了方法调用的开销。

例子:


printMessage()是一个被final修饰的方法,它打印一条消息到控制台。由于编译器知道该方法不会被重写,它可以直接将调用处的代码替换为方法体的内容,即System.out.println("Hello, World!");


通过这种直接插入方法体的优化,避免了实际的方法调用和方法体代码的执行开销,提高了程序的执行效率。

public class Example {
    public final void printMessage() {
        System.out.println("Hello, World!");
    }
    public static void main(String[] args) {
        Example example = new Example();
        // 调用final方法
        example.printMessage();
        // 编译器可以将上述代码替换为方法体的直接插入
        System.out.println("Hello, World!");
    }
}


3.3.2编译期间绑定:


由于final方法不可被重写,编译器可以在编译期间确定要调用的确切方法,而不需要等到运行时进行动态绑定。


例子:

printMessage()是一个被final修饰的方法。由于它是final方法,编译器在编译时就可以确定要调用的确切方法是Example类中的printMessage()方法,而不需要在运行时进行动态绑定。


通过编译期间绑定的优化,避免了在运行时进行动态查找的开销,提高了程序的执行效率。


public class Example {
    public final void printMessage() {
        System.out.println("Hello, World!");
    }
}
public class Main {
    public static void main(String[] args) {
        Example example = new Example();
        // 调用final方法
        example.printMessage();
    }
}

3.3.3内联常量引用


如果final方法返回常量值,编译器可以将调用该方法的地方替换为常量值的直接引用,从而消除方法调用和返回值的相关操作。


例子:

getConstantValue()是一个被final修饰的方法,并且它始终返回一个常量值10。由于编译器知道该方法不会被重写,它可以直接将调用处的代码替换为常量值的引用,即int optimizedValue = 10;


通过这种内联常量引用的优化,避免了实际的方法调用和返回值操作,提高了程序的执行效率。

public class Example {
    public final int getConstantValue() {
        return 10;
    }
    public static void main(String[] args) {
        Example example = new Example();
        // 调用final方法并获取返回值
        int value = example.getConstantValue();
        // 编译器可以将上述代码替换为直接使用常量值
        int optimizedValue = 10;
        System.out.println(value);           // 输出:10
        System.out.println(optimizedValue);  // 输出:10
    }
}


3.3.4 finall 不执行的特例


例子:

try块中调用了System.exit(1)方法,它会终止Java虚拟机的运行,并传递一个退出状态码(这里是1)。当System.exit(1)被调用时,Java程序会立即终止,不再执行后续的代码,包括finally块。


try {
System.exit(1);
} finally{
System.out.println(“Print from finally”);
}


3. 4 finalize


Java9中明确将Object.finalize()标记为deprecated,没有特别原因不要去实现finalize方法,不能指望finalize进行资源回收,因为无法保证finalize什么时候执行,执行是否符合预期,使用不当会影响性能,导致程序死锁,挂起。


分析:


3.4.1执行时机不确定:


Java虚拟机对于finalize()方法的调用时机是不确定的,无法保证在对象不再被引用且即将被回收时立即执行。这意味着不能指望finalize()方法能及时地释放资源。


3.4.2不可靠性:


由于无法控制finalize()方法的执行时机,也无法确保其是否会被执行。例如,如果JVM在垃圾回收过程中遇到问题或程序正在运行耗尽内存,finalize()方法可能永远不会被调用。


3.4.3性能影响:


finalize()方法的执行会导致垃圾回收器的延迟和开销增加,可能会影响程序的性能。频繁地创建对象并依赖finalize()方法进行资源回收可能会导致系统性能下降。


3.4.4安全问题:


finalize()方法的执行过程是单线程的,在它执行期间,其他线程可能被挂起或发生死锁。这是因为垃圾回收器执行finalize()方法时会暂停其他的线程

解释:

3.4.5  finalize()方法可能永远不会被调用的原因是由于以下几个方面:


3.4.5.1垃圾回收器优化:


为了提高垃圾回收的效率,JVM的垃圾回收器通常采用了各种优化策略。其中一个优化策略是针对没有覆盖finalize()方法的对象,直接跳过其finalize()方法的调用,将其视为“不需要清理”的对象。这样可以减少垃圾回收的时间和开销。


3.4.5.2内存耗尽问题:


当程序运行过程中耗尽了内存资源,导致无法分配新的内存时,JVM会抛出OutOfMemoryError异常。在这种情况下,JVM处于一种极限状态,无法正常执行垃圾回收操作,包括finalize()方法的调用。因此,finalize()方法可能永远不会被调用。


3.4.5.3垃圾回收过程中的问题:


在垃圾回收过程中,可能会发生意外的错误或异常。例如,如果垃圾回收器本身存在缺陷、遭遇死循环或崩溃等问题,就无法完成垃圾回收操作,包括finalize()方法的执行。  

                                                                                                                                                 

3.4.5.4优化策略解析:                    


将没有覆盖finalize()方法的对象视为“不需要清理”的对象,


主要是出于性能和效率的考虑。当对象没有覆盖finalize()方法时,垃圾回收器可以假设该对象不需要进行任何额外的清理工作。


因此,在垃圾回收过程中,它可以直接跳过对这些对象的finalize()方法的调用,从而减少了垃圾回收的时间和开销。


通常情况下,如果一个对象覆盖了finalize()方法,那么在垃圾回收器执行垃圾回收操作时,会将该对象标记为“需要清理”。垃圾回收器会在回收该对象之前,先调用其finalize()方法进行清理操作。这个过程需要消耗额外的时间,并且会导致垃圾回收的延迟。


然而,由于finalize()方法的不确定性和性能问题,JVM引入了一种优化策略,即对没有覆盖finalize()方法的对象进行特殊处理。


这些对象被认为不需要进行额外的清理操作,因此在垃圾回收过程中会被直接回收,而不会调用其finalize()方法。


相关文章
|
6天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
31 7
|
24天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
64 2
|
12天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
36 14
|
23天前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
29天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
6天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
17天前
|
Java 编译器 程序员
Java面试高频题:用最优解法算出2乘以8!
本文探讨了面试中一个看似简单的数学问题——如何高效计算2×8。从直接使用乘法、位运算优化、编译器优化、加法实现到大整数场景下的处理,全面解析了不同方法的原理和适用场景,帮助读者深入理解计算效率优化的重要性。
25 6
|
23天前
|
监控 前端开发 Java
【技术开发】接口管理平台要用什么技术栈?推荐:Java+Vue3+Docker+MySQL
该文档介绍了基于Java后端和Vue3前端构建的管理系统的技术栈及功能模块,涵盖管理后台的访问、登录、首页概览、API接口管理、接口权限设置、接口监控、计费管理、账号管理、应用管理、数据库配置、站点配置及管理员个人设置等内容,并提供了访问地址及操作指南。
|
24天前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
70 2
|
4月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。