1 Java的基本数据类型有哪些?各占多少字节?
整数类型:
byte:1字节
short:2字节
int:4字节
long:8字节
浮点数类型:
float:4字节
double:8字节
字符类型:
char:2字节
布尔类型:
boolean:理论上没有明确规定大小,但在实际使用中,通常被实现为1字节。
这些基本数据类型的大小在不同的Java虚拟机(JVM)实现中可能会有所不同,但上述大小是在大多数常见的JVM上的规定。值得注意的是,这些数据类型的大小在不同平台上可能会有所差异,因为Java的基本数据类型并没有明确定义它们在所有平台上的确切大小,而是根据Java虚拟机的实现而定。
2 面向对象的三大特征
Java的面向对象编程具有三大特征,通常被称为面向对象的三大支柱。这三大特征分别是:
封装(Encapsulation):
封装是指将对象的状态(属性)和行为(方法)封装在一起,形成一个独立的、可控制的单元。通过封装,可以隐藏对象的实现细节,使得对象的使用者只能通过对象对外提供的接口来访问对象的功能,而不需要了解对象的内部实现。
例子: 想象一辆汽车,你作为司机只需要了解一些基本操作,比如踩油门、刹车、转弯等。你不需要知道引擎是如何工作的,汽车内部的复杂机制被封装在引擎盖内部。这样,你只需要与汽车的接口(方向盘、踏板等)交互,而不用关心引擎的具体实现细节。
继承(Inheritance):
继承是指一个类(子类)可以使用另一个类(父类)的属性和方法。通过继承,子类可以复用父类的代码,而且可以在不修改父类代码的情况下扩展或修改父类的功能。继承体现了一种"是什么"的关系,子类是父类的一种特例。
例子: 想象一个动物类,该类有一些通用属性和行为,比如呼吸、进食。现在有狗和猫两个子类,它们都是动物类的子类,继承了动物类的通用特征。这样,你可以在动物类中定义一些基本的属性和方法,而在狗和猫类中只需要专注于它们各自特有的属性和行为,实现了代码的重用。
多态(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
方法被定义为比较对象的引用是否相等,但大多数类(包括String
、List
、Set
等)会重写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)的内存结构主要包括以下几个部分:
程序计数器(Program Counter Register):
每个线程都有一个程序计数器,它的作用是记录当前线程执行的字节码指令的地址。在多线程环境下,程序计数器用于线程切换时记录每个线程当前的执行位置。
Java虚拟机栈(JVM Stack):
每个线程都有一个私有的Java虚拟机栈,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法的调用和执行都是在栈上进行的,每个方法在执行时会创建一个栈帧,方法执行完毕后,栈帧出栈。
本地方法栈(Native Method Stack):
与Java虚拟机栈类似,本地方法栈是为Java虚拟机执行Native方法服务的。它也是线程私有的,用于支持Java调用本地方法。
堆(Heap):
堆是Java虚拟机管理的最大一块内存区域,用于存放对象实例。在堆中,可以被所有线程访问,因此是共享的内存区域。垃圾收集器主要负责在堆上进行垃圾回收。
方法区(Method Area):
方法区用于存储类的结构信息,如类的元数据、静态变量、常量池、方法代码等。在JDK 8之前,方法区是永久代的一部分,而在JDK 8之后,永久代被移除,取而代之的是元空间(Metaspace),元空间也是方法区的一部分。
运行时常量池(Runtime Constant Pool):
运行时常量池是方法区的一部分,用于存储编译时期生成的字面量和符号引用。与Class文件中的常量池不同,运行时常量池是可以动态扩展的。
直接内存(Direct Memory):
直接内存并不是JVM内部的一个区域,但是在一些情况下,Java虚拟机会使用到直接内存,例如NIO中的ByteBuffer。直接内存使用Native函数库直接分配内存,不受Java堆大小的限制,但会受到操作系统的限制。
6 jvm中如何判断一个对象是否是垃圾对象
在Java虚拟机中,判断一个对象是否是垃圾对象的主要方法是通过垃圾回收器的算法。
Java虚拟机使用的是可达性分析算法,通过从一组称为"GC Roots"的起始点开始,追踪对象的引用链,能够被GC Roots直接或间接引用的对象都被认为是存活的,而不可达的对象则被判定为垃圾,可以被回收。
7 有哪些垃圾回收的算法
在Java虚拟机中,有几种常见的垃圾回收算法。这些算法的选择取决于应用程序的性能需求、内存需求以及运行环境等因素。以下是一些常见的垃圾回收算法:
标记-清除算法(Mark and Sweep):
这是一种最基本的垃圾回收算法。它分为两个阶段:标记阶段和清除阶段。首先,通过从根对象开始,标记所有可以被访问到的对象。然后,在清除阶段,没有被标记的对象被认为是垃圾,将其回收。标记-清除算法的缺点是会产生内存碎片。
复制算法(Copying):
复制算法将堆分为两个区域,一半为活动对象,一半为空闲。垃圾回收时,将存活的对象复制到空闲区域,然后清除原有区域的所有对象。这样可以保证内存分配时是连续的,但是需要额外的空间来存储复制时的对象。
标记-整理算法(Mark and Compact):
类似于标记-清除算法,但在标记完成后,会将所有存活的对象向一端移动,然后清理掉边界外的内存。这样可以减少内存碎片的产生。
分代算法(Generational):
基于新生代对象和老年代对象的不同特点,分代算法将堆分为新生代和老年代。大部分对象在新生代被创建,因此采用复制算法;老年代采用标记-清除或标记-整理算法。这样可以针对不同年龄段的对象采用不同的回收策略。
增量式垃圾回收算法(Incremental):
将垃圾回收过程分解为多个步骤,在每个步骤中执行部分回收工作,逐步完成整个垃圾回收过程。这样可以在多个步骤中交替执行垃圾回收和程序代码,减少垃圾回收的停顿时间。
这些垃圾回收算法可以根据具体的应用场景和需求进行选择和组合。 Java虚拟机的不同实现可能采用不同的组合方式,例如,Java HotSpot虚拟机主要使用了分代垃圾回收算法。
8 进程和线程的区别
进程是系统中独立的执行单位,拥有独立的内存和资源,进程间通信复杂。
线程是进程的一部分,共享进程的资源,切换开销小,但线程稳定性相对较差。
在Java中,线程是最小的执行单元,通过Thread类实现,多线程编程相对轻量。
9 线程池的7个参数是什么?线程池执行任务的流程是什么?
Java中的线程池是通过
java.util.concurrent
包中的ThreadPoolExecutor
类来实现的。线程池的构造函数有七个参数:
corePoolSize(核心线程数):
线程池的基本大小,即在没有任务需要执行的时候,线程池的大小。
maximumPoolSize(最大线程数):
线程池允许创建的最大线程数。
keepAliveTime(线程空闲时间):
线程池中的线程在空闲状态下,超过这个时间就会被回收。
unit(时间单位):
用于指定keepAliveTime的时间单位。
workQueue(工作队列):
用于保存等待执行的任务的阻塞队列。
threadFactory(线程工厂):
用于创建线程的工厂。
handler(拒绝策略):
当工作队列已满,且线程池中的线程数达到最大线程数时,新任务的处理策略。
线程池执行任务的基本流程:
当一个任务通过
execute
方法提交给线程池时,线程池会判断当前线程池中的线程数是否达到corePoolSize
,如果没有,则创建一个新的线程执行任务。如果当前线程池中的线程数已经达到
corePoolSize
,任务会被放入工作队列。如果工作队列已满,且当前线程池中的线程数还未达到
maximumPoolSize
,则创建一个新的线程执行任务。如果工作队列已满,且当前线程池中的线程数已经达到
maximumPoolSize
,采用拒绝策略处理新任务。当一个线程完成任务后,它会从工作队列中取出下一个任务执行。
当一个线程空闲时间超过
keepAliveTime
,且当前线程池中的线程数大于corePoolSize
,则该线程会被回收。线程池通过合理配置参数,可以有效控制线程的数量,提高系统的性能。
10 线程池的拒绝策略有哪些?
Java中线程池的拒绝策略(Rejection Policies)主要有以下几种:
AbortPolicy(中止策略):
默认的拒绝策略,当任务无法被执行时,直接抛出
RejectedExecutionException
异常。new ThreadPoolExecutor.AbortPolicy();
CallerRunsPolicy(调用者运行策略):
当任务无法被执行时,由提交任务的线程执行该任务,即调用者自己执行。
new ThreadPoolExecutor.CallerRunsPolicy();
DiscardPolicy(丢弃策略):
当任务无法被执行时,直接丢弃掉这个任务,不做任何处理。
new ThreadPoolExecutor.DiscardPolicy();
DiscardOldestPolicy(丢弃最老任务策略):
当任务无法被执行时,丢弃等待时间最长的任务,然后尝试将新任务加入队列。
new ThreadPoolExecutor.DiscardOldestPolicy();
自定义拒绝策略:
实现
RejectedExecutionHandler
接口,可以自定义拒绝策略,处理无法被执行的任务。用户可以根据具体业务需求定义自己的处理逻辑。new MyCustomRejectionHandler();选择合适的拒绝策略通常取决于应用程序的需求和特定业务场景。例如,如果对任务的实时性要求较高,可以选择
CallerRunsPolicy
策略,将任务由提交任务的线程直接执行。如果对任务的执行优先级没有特殊要求,可以使用默认的AbortPolicy
。
11 项目中哪里使用过线程池?
在项目中,使用线程池的场景可能包括:
签到送积分:
大量用户进行签到操作时,可以使用线程池来异步处理签到任务,提高系统的并发处理能力。
点赞功能:
处理用户点赞操作时,使用线程池可以有效处理大量的点赞请求,将点赞任务异步执行,提高系统的性能和响应速度。
排行榜更新:
定期更新排行榜时,可以使用线程池来异步执行排行榜计算和更新操作,避免阻塞主线程。
ERP 系统任务处理:
在一个企业资源计划(ERP)系统中,可能有大量的后台任务,如订单处理、库存管理等,这些任务可以交由线程池异步执行,提高系统的吞吐量。
文章发布:
处理文章发布请求时,使用线程池可以实现异步发布,避免用户发布操作阻塞,提升用户体验。
在以上场景中,线程池的作用是通过异步执行任务来提高系统的并发能力,降低响应时间,同时能够更好地管理和控制系统中的线程资源。线程池的使用可以有效地处理高并发和大规模任务处理的情况。
12 ThreadLocal的作用是什么?实现原理是什么?
ThreadLocal
是 Java 中的一个线程封闭技术,用于在多线程环境下提供每个线程独立的变量副本,从而实现线程间的数据隔离。其主要作用包括:
保存线程私有变量:
ThreadLocal
允许在每个线程中创建一个独立的变量,每个线程都可以存取自己的变量,而互不影响。
线程间数据隔离:
通过
ThreadLocal
,可以在多线程环境下,为每个线程维护一份独立的数据,避免了线程间的数据共享问题。
避免传递参数的繁琐:
可以避免在方法之间传递一些共享变量,而让每个线程可以直接访问自己的局部变量。
ThreadLocal
的实现原理主要涉及到一个ThreadLocalMap
类。每个线程都有一个私有的ThreadLocalMap
实例,其中可以存储多个键值对,键是ThreadLocal
实例,值是要保存的线程私有变量。ThreadLocal
实例在多个线程之间不共享,每个线程对应一个独立的ThreadLocalMap
。简要的实现原理如下:
ThreadLocal
类:
每个
ThreadLocal
对象都有一个唯一的标识符,用于在ThreadLocalMap
中作为键。这个标识符通常是ThreadLocal
实例的引用。
ThreadLocalMap
类:
每个线程都有一个
ThreadLocalMap
对象,用于存储线程私有的变量。这个 map 是线程私有的,不共享给其他线程。
get
方法:
当调用
ThreadLocal
的get
方法时,会首先获取当前线程的ThreadLocalMap
对象,然后使用ThreadLocal
实例作为键,在 map 中查找对应的值。
set
方法:
当调用
ThreadLocal
的set
方法时,会在当前线程的ThreadLocalMap
中存储ThreadLocal
实例和相应的值。
remove
方法:
当
ThreadLocal
不再需要时,通过remove
方法可以清理当前线程的ThreadLocalMap
中对应的实例。需要注意的是,由于
ThreadLocal
使用线程私有的ThreadLocalMap
,需要小心防止内存泄漏,即在不再需要使用ThreadLocal
的时候及时清理。否则,ThreadLocal
中的对象可能会一直存在于线程的ThreadLocalMap
中,无法被垃圾回收。