八股文---常见面试题1—12

发布时间:2024年01月04日
1 Java的基本数据类型有哪些?各占多少字节?
  1. 整数类型:

  • byte:1字节

  • short:2字节

  • int:4字节

  • long:8字节

  1. 浮点数类型:

  • float:4字节

  • double:8字节

  1. 字符类型:

  • char:2字节

  1. 布尔类型:

  • boolean:理论上没有明确规定大小,但在实际使用中,通常被实现为1字节。

这些基本数据类型的大小在不同的Java虚拟机(JVM)实现中可能会有所不同,但上述大小是在大多数常见的JVM上的规定。值得注意的是,这些数据类型的大小在不同平台上可能会有所差异,因为Java的基本数据类型并没有明确定义它们在所有平台上的确切大小,而是根据Java虚拟机的实现而定。

2 面向对象的三大特征

Java的面向对象编程具有三大特征,通常被称为面向对象的三大支柱。这三大特征分别是:

  1. 封装(Encapsulation):

  • 封装是指将对象的状态(属性)和行为(方法)封装在一起,形成一个独立的、可控制的单元。通过封装,可以隐藏对象的实现细节,使得对象的使用者只能通过对象对外提供的接口来访问对象的功能,而不需要了解对象的内部实现。

  • 例子: 想象一辆汽车,你作为司机只需要了解一些基本操作,比如踩油门、刹车、转弯等。你不需要知道引擎是如何工作的,汽车内部的复杂机制被封装在引擎盖内部。这样,你只需要与汽车的接口(方向盘、踏板等)交互,而不用关心引擎的具体实现细节。

  1. 继承(Inheritance):

  • 继承是指一个类(子类)可以使用另一个类(父类)的属性和方法。通过继承,子类可以复用父类的代码,而且可以在不修改父类代码的情况下扩展或修改父类的功能。继承体现了一种"是什么"的关系,子类是父类的一种特例。

  • 例子: 想象一个动物类,该类有一些通用属性和行为,比如呼吸、进食。现在有狗和猫两个子类,它们都是动物类的子类,继承了动物类的通用特征。这样,你可以在动物类中定义一些基本的属性和方法,而在狗和猫类中只需要专注于它们各自特有的属性和行为,实现了代码的重用。

  1. 多态(Polymorphism):

  • 多态是指一个对象可以在不同的情境下表现出不同的形态。在Java中,主要通过方法的重载(Overloading)和方法的重写(Overriding)来实现多态。方法的重载是指一个类中可以有多个方法,它们的方法名相同但参数列表不同;方法的重写是指子类重新定义了父类中已经定义的方法。多态使得一个对象可以以多种形态存在,提高了代码的灵活性和可扩展性。

这三大特征共同构成了面向对象编程的基础,使得Java程序具有更好的可维护性、可复用性和扩展性。

3 值传递和引用传递区别?

在Java中,参数传递是值传递。这意味着在方法调用时,传递给方法的是实际参数的副本而不是实际参数本身。在方法内对参数的修改不会影响到方法外的变量。 Java中没有直接的引用传递,但有一些情况可以让对象的引用被传递,但本质上仍然是值传递。

  • 值传递: 方法得到的是实际参数值的一份拷贝,对这份拷贝的修改不会影响到实际参数。

  • 引用传递: Java中虽然不直接支持引用传递,但通过对象的引用传递可以让方法修改对象的状态。

在Java中,基本数据类型(如int、float)是按值传递的,而对象类型(如数组、自定义类的对象)是按引用传递的。然而,需要注意的是,对象引用的传递依然是传递的引用的副本,而不是引用本身。这意味着对于对象引用,虽然可以修改对象的状态,但如果在方法内部给引用赋予一个新的对象,不会影响到方法外部的引用。

4 ==和equals区别

在Java中,==运算符和equals方法有着不同的作用。

==运算符:

  • 比较对象引用: ==比较的是两个对象的引用是否相同,即是否指向同一块内存地址。

  • 基本数据类型: 对于基本数据类型(int、float等),==比较的是它们的值是否相等。

  • 示例:

 String str1 = new String("Hello");
 String str2 = new String("Hello");
 
 System.out.println(str1 == str2); ?// false,因为是两个不同的对象

equals方法:

  • 比较对象内容: equals方法默认比较的是对象的内容,即在Object类中,equals方法被定义为比较对象的引用是否相等,但大多数类(包括StringListSet等)会重写equals方法以比较对象的内容。

  • 示例:

 String str1 = new String("Hello");
 String str2 = new String("Hello");
 
 System.out.println(str1.equals(str2)); ?// true,因为String类重写了equals方法,比较内容是否相等

总结:

  • ==比较的是对象引用,用于比较两个对象是否指向同一块内存地址。

  • equals方法默认比较的也是对象引用,但它可以被重写以实现比较对象内容的目的。在比较内容时,常用于字符串、集合等类。

5 简述jdk8中,JVM的内存结构?

在JDK 8中,Java虚拟机(JVM)的内存结构主要包括以下几个部分:

  1. 程序计数器(Program Counter Register):

  • 每个线程都有一个程序计数器,它的作用是记录当前线程执行的字节码指令的地址。在多线程环境下,程序计数器用于线程切换时记录每个线程当前的执行位置。

  1. Java虚拟机栈(JVM Stack):

  • 每个线程都有一个私有的Java虚拟机栈,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法的调用和执行都是在栈上进行的,每个方法在执行时会创建一个栈帧,方法执行完毕后,栈帧出栈。

  1. 本地方法栈(Native Method Stack):

  • 与Java虚拟机栈类似,本地方法栈是为Java虚拟机执行Native方法服务的。它也是线程私有的,用于支持Java调用本地方法。

  1. 堆(Heap):

  • 堆是Java虚拟机管理的最大一块内存区域,用于存放对象实例。在堆中,可以被所有线程访问,因此是共享的内存区域。垃圾收集器主要负责在堆上进行垃圾回收。

  1. 方法区(Method Area):

  • 方法区用于存储类的结构信息,如类的元数据、静态变量、常量池、方法代码等。在JDK 8之前,方法区是永久代的一部分,而在JDK 8之后,永久代被移除,取而代之的是元空间(Metaspace),元空间也是方法区的一部分。

  1. 运行时常量池(Runtime Constant Pool):

  • 运行时常量池是方法区的一部分,用于存储编译时期生成的字面量和符号引用。与Class文件中的常量池不同,运行时常量池是可以动态扩展的。

  1. 直接内存(Direct Memory):

  • 直接内存并不是JVM内部的一个区域,但是在一些情况下,Java虚拟机会使用到直接内存,例如NIO中的ByteBuffer。直接内存使用Native函数库直接分配内存,不受Java堆大小的限制,但会受到操作系统的限制。

6 jvm中如何判断一个对象是否是垃圾对象

在Java虚拟机中,判断一个对象是否是垃圾对象的主要方法是通过垃圾回收器的算法。

Java虚拟机使用的是可达性分析算法,通过从一组称为"GC Roots"的起始点开始,追踪对象的引用链,能够被GC Roots直接或间接引用的对象都被认为是存活的,而不可达的对象则被判定为垃圾,可以被回收。

7 有哪些垃圾回收的算法

在Java虚拟机中,有几种常见的垃圾回收算法。这些算法的选择取决于应用程序的性能需求、内存需求以及运行环境等因素。以下是一些常见的垃圾回收算法:

  1. 标记-清除算法(Mark and Sweep):

  • 这是一种最基本的垃圾回收算法。它分为两个阶段:标记阶段和清除阶段。首先,通过从根对象开始,标记所有可以被访问到的对象。然后,在清除阶段,没有被标记的对象被认为是垃圾,将其回收。标记-清除算法的缺点是会产生内存碎片。

  1. 复制算法(Copying):

  • 复制算法将堆分为两个区域,一半为活动对象,一半为空闲。垃圾回收时,将存活的对象复制到空闲区域,然后清除原有区域的所有对象。这样可以保证内存分配时是连续的,但是需要额外的空间来存储复制时的对象。

  1. 标记-整理算法(Mark and Compact):

  • 类似于标记-清除算法,但在标记完成后,会将所有存活的对象向一端移动,然后清理掉边界外的内存。这样可以减少内存碎片的产生。

  1. 分代算法(Generational):

  • 基于新生代对象和老年代对象的不同特点,分代算法将堆分为新生代和老年代。大部分对象在新生代被创建,因此采用复制算法;老年代采用标记-清除或标记-整理算法。这样可以针对不同年龄段的对象采用不同的回收策略。

  1. 增量式垃圾回收算法(Incremental):

  • 将垃圾回收过程分解为多个步骤,在每个步骤中执行部分回收工作,逐步完成整个垃圾回收过程。这样可以在多个步骤中交替执行垃圾回收和程序代码,减少垃圾回收的停顿时间。

这些垃圾回收算法可以根据具体的应用场景和需求进行选择和组合。 Java虚拟机的不同实现可能采用不同的组合方式,例如,Java HotSpot虚拟机主要使用了分代垃圾回收算法。

8 进程和线程的区别

进程是系统中独立的执行单位,拥有独立的内存和资源,进程间通信复杂。

线程是进程的一部分,共享进程的资源,切换开销小,但线程稳定性相对较差。

在Java中,线程是最小的执行单元,通过Thread类实现,多线程编程相对轻量。

9 线程池的7个参数是什么?线程池执行任务的流程是什么?

Java中的线程池是通过java.util.concurrent包中的ThreadPoolExecutor类来实现的。线程池的构造函数有七个参数:

  1. corePoolSize(核心线程数):

  • 线程池的基本大小,即在没有任务需要执行的时候,线程池的大小。

  1. maximumPoolSize(最大线程数):

  • 线程池允许创建的最大线程数。

  1. keepAliveTime(线程空闲时间):

  • 线程池中的线程在空闲状态下,超过这个时间就会被回收。

  1. unit(时间单位):

  • 用于指定keepAliveTime的时间单位。

  1. workQueue(工作队列):

  • 用于保存等待执行的任务的阻塞队列。

  1. threadFactory(线程工厂):

  • 用于创建线程的工厂。

  1. handler(拒绝策略):

  • 当工作队列已满,且线程池中的线程数达到最大线程数时,新任务的处理策略。

线程池执行任务的基本流程:

  1. 当一个任务通过execute方法提交给线程池时,线程池会判断当前线程池中的线程数是否达到corePoolSize,如果没有,则创建一个新的线程执行任务。

  2. 如果当前线程池中的线程数已经达到corePoolSize,任务会被放入工作队列。

  3. 如果工作队列已满,且当前线程池中的线程数还未达到maximumPoolSize,则创建一个新的线程执行任务。

  4. 如果工作队列已满,且当前线程池中的线程数已经达到maximumPoolSize,采用拒绝策略处理新任务。

  5. 当一个线程完成任务后,它会从工作队列中取出下一个任务执行。

  6. 当一个线程空闲时间超过keepAliveTime,且当前线程池中的线程数大于corePoolSize,则该线程会被回收。

线程池通过合理配置参数,可以有效控制线程的数量,提高系统的性能。

10 线程池的拒绝策略有哪些?

Java中线程池的拒绝策略(Rejection Policies)主要有以下几种:

  1. AbortPolicy(中止策略):

  • 默认的拒绝策略,当任务无法被执行时,直接抛出 RejectedExecutionException 异常。

 ?new ThreadPoolExecutor.AbortPolicy();
  1. CallerRunsPolicy(调用者运行策略):

  • 当任务无法被执行时,由提交任务的线程执行该任务,即调用者自己执行。

 ?new ThreadPoolExecutor.CallerRunsPolicy();
  1. DiscardPolicy(丢弃策略):

  • 当任务无法被执行时,直接丢弃掉这个任务,不做任何处理。

 ?new ThreadPoolExecutor.DiscardPolicy();
  1. DiscardOldestPolicy(丢弃最老任务策略):

  • 当任务无法被执行时,丢弃等待时间最长的任务,然后尝试将新任务加入队列。

 ?new ThreadPoolExecutor.DiscardOldestPolicy();
  1. 自定义拒绝策略:

  • 实现 RejectedExecutionHandler 接口,可以自定义拒绝策略,处理无法被执行的任务。用户可以根据具体业务需求定义自己的处理逻辑。

 ?new MyCustomRejectionHandler();

选择合适的拒绝策略通常取决于应用程序的需求和特定业务场景。例如,如果对任务的实时性要求较高,可以选择 CallerRunsPolicy 策略,将任务由提交任务的线程直接执行。如果对任务的执行优先级没有特殊要求,可以使用默认的 AbortPolicy

11 项目中哪里使用过线程池?

在项目中,使用线程池的场景可能包括:

  1. 签到送积分:

  • 大量用户进行签到操作时,可以使用线程池来异步处理签到任务,提高系统的并发处理能力。

  1. 点赞功能:

  • 处理用户点赞操作时,使用线程池可以有效处理大量的点赞请求,将点赞任务异步执行,提高系统的性能和响应速度。

  1. 排行榜更新:

  • 定期更新排行榜时,可以使用线程池来异步执行排行榜计算和更新操作,避免阻塞主线程。

  1. ERP 系统任务处理:

  • 在一个企业资源计划(ERP)系统中,可能有大量的后台任务,如订单处理、库存管理等,这些任务可以交由线程池异步执行,提高系统的吞吐量。

  1. 文章发布:

  • 处理文章发布请求时,使用线程池可以实现异步发布,避免用户发布操作阻塞,提升用户体验。

在以上场景中,线程池的作用是通过异步执行任务来提高系统的并发能力,降低响应时间,同时能够更好地管理和控制系统中的线程资源。线程池的使用可以有效地处理高并发和大规模任务处理的情况。

12 ThreadLocal的作用是什么?实现原理是什么?

ThreadLocal 是 Java 中的一个线程封闭技术,用于在多线程环境下提供每个线程独立的变量副本,从而实现线程间的数据隔离。其主要作用包括:

  1. 保存线程私有变量:

  • ThreadLocal 允许在每个线程中创建一个独立的变量,每个线程都可以存取自己的变量,而互不影响。

  1. 线程间数据隔离:

  • 通过 ThreadLocal,可以在多线程环境下,为每个线程维护一份独立的数据,避免了线程间的数据共享问题。

  1. 避免传递参数的繁琐:

  • 可以避免在方法之间传递一些共享变量,而让每个线程可以直接访问自己的局部变量。

ThreadLocal 的实现原理主要涉及到一个 ThreadLocalMap 类。每个线程都有一个私有的 ThreadLocalMap 实例,其中可以存储多个键值对,键是 ThreadLocal 实例,值是要保存的线程私有变量。ThreadLocal 实例在多个线程之间不共享,每个线程对应一个独立的 ThreadLocalMap

简要的实现原理如下:

  1. ThreadLocal 类:

  • 每个 ThreadLocal 对象都有一个唯一的标识符,用于在 ThreadLocalMap 中作为键。这个标识符通常是 ThreadLocal 实例的引用。

  1. ThreadLocalMap 类:

  • 每个线程都有一个 ThreadLocalMap 对象,用于存储线程私有的变量。这个 map 是线程私有的,不共享给其他线程。

  1. get 方法:

  • 当调用 ThreadLocalget 方法时,会首先获取当前线程的 ThreadLocalMap 对象,然后使用 ThreadLocal 实例作为键,在 map 中查找对应的值。

  1. set 方法:

  • 当调用 ThreadLocalset 方法时,会在当前线程的 ThreadLocalMap 中存储 ThreadLocal 实例和相应的值。

  1. remove 方法:

  • ThreadLocal 不再需要时,通过 remove 方法可以清理当前线程的 ThreadLocalMap 中对应的实例。

需要注意的是,由于 ThreadLocal 使用线程私有的 ThreadLocalMap,需要小心防止内存泄漏,即在不再需要使用 ThreadLocal 的时候及时清理。否则,ThreadLocal 中的对象可能会一直存在于线程的 ThreadLocalMap 中,无法被垃圾回收。

文章来源:https://blog.csdn.net/opbbxxx/article/details/135358399
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。