Java面试题及答案整理(最新版)持续更新

栏目:教育机构  时间:2023-07-31
手机版

  Java 最常见的面试题的答案已经全部更新完了,有些答案是自己总结的,也有些答案是在网上搜集整理的。这些答案难免会存在一些错误,仅供大家参考。如果发现错误还望大家多多包涵,不吝赐教,谢谢~

  很长时间,没有更新的,最近有空就更新一些面试题

  如果不背 Java 面试题的答案,肯定面试会挂!

  已经整理成7000多页,面试手册PDF版架构师专栏:300道SpringCloud面试题(持续更新)架构师专栏:SpringBoot面试题及答案 110道(持续更新)架构师专栏:Jvm面试题总结及答案 300道(针对Jvm的面试题 )架构师专栏:110道 Elasticsearch面试题及答案(持续更新)架构师专栏:110道 Redis面试题及答案 (持续更新)使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。

  当前线程到了就绪状态,那么接下来哪个线程会从就绪状态变成执行状态呢?可能是当前线程,也可能是其他线程,看系统的分配了。

  在两个线程间共享变量即可实现共享。

  一般来说,共享变量要求变量本身是线程安全的,然后在线程内使用的时候,如果有对共享变量的复合操作,那么也得保证复合操作的线程安全性。

  由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。

  不行,你不能在没有强制类型转换的前提下将一个 double 值赋值给 long 类型的变量,因为 double 类型的范围比 long 类型更广,所以必须要进行强制转换。

  下面有几条可以遵循的方法重载的最佳实践来避免造成自动装箱的混乱。

  a)不要重载这样的方法:一个方法接收 int 参数,而另个方法接收 Integer 参数。

  b)不要重载参数数量一致,而只是参数顺序不同的方法。

  c)如果重载的方法参数个数多于 5 个,采用可变参数。

  GC最基础的算法有三种:标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。

  标记 -清除算法

  “标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

  复制算法

  “复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

  标记-压缩算法

  标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

  分代收集算法

  “分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法

  首先通过 Javac 编译器将 转为 JVM 可加载的 字节码文件。Javac 是由 Java 编写的程序,编译过程可以分为:① 词法解析,通过空格分割出单词、操作符、控制符等信息,形成 token 信息流,传递给语法解析器。② 语法解析,把 token 信息流按照 Java 语法规则组装成语法树。③ 语义分析,检查关键字使用是否合理、类型是否匹配、作用域是否正确等。④ 字节码生成,将前面各个步骤的信息转换为字节码。字节码必须通过类加载过程加载到 JVM 后才可以执行,执行有三种模式,解释执行、JIT 编译执行、JIT 编译与解释器混合执行(主流 JVM 默认执行的方式)。混合模式的优势在于解释器在启动时先解释执行,省去编译时间。之后通过即时编译器 JIT 把字节码文件编译成本地机器码。Java 程序最初都是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁,就会认定其为"热点代码",热点代码的检测主要有基于采样和基于计数器两种方式,为了提高热点代码的执行效率,虚拟机会把它们编译成本地机器码,尽可能对代码优化,在运行时完成这个任务的后端编译器被称为即时编译器。还可以通过静态的提前编译器 AOT 直接把程序编译成与目标机器指令集相关的二进制代码。

  抽象工厂模式:通常由工厂方法模式来实现。但一个工厂中往往含有多个工厂方法生成一系列的产品。这个模式强调的是客户代码一次保证只使用一个系列的产品。当要切换为另一个系列的产品,换一个工厂类即可。

  原型模式:工厂方法的最大缺点就是,对应一个继承体系的产品类,要有一个同样复杂的工厂类的继承体系。我们可以把工厂类中的工厂方法放到产品类自身之中吗?如果这样的话,就可以将两个继承体系为一个。这也就是原型模式的思想,原型模式中的工厂方法为clone,它会返回一个拷贝(可以是浅拷贝,也可以是深拷贝,由设计者决定)。为了保证用户代码中到时可以通过指针调用clone来动态绑定地生成所需的具体的类。这些原型对象必须事先构造好。

  原型模式想对工厂方法模式的另一个好处是,拷贝的效率一般对构造的效率要高。

  出现线程安全问题的原因一般都是三个原因:

  1、 线程切换带来的原子性问题 解决办法:使用多线程之间同步synchronized或使用锁(lock)。

  2、 缓存导致的可见性问题 解决办法:synchronized、volatile、LOCK,可以解决可见性问题

  3、 编译优化带来的有序性问题 解决办法:Happens-Before 规则可以解决有序性问题

  一种实践是用 volatile 修饰 long 和 double 变量,使其能按原子类型来读写。double 和 long 都是64位宽,因此对这两种类型的读是分为两部分的,第一次读取第一个 32 位,然后再读剩下的 32 位,这个过程不是原子的,但 Java 中 volatile 型的 long 或 double 变量的读写是原子的。volatile 修复符的另一个作用是提供内存屏障(memory barrier),例如在分布式框架中的应用。简单的说,就是当你写一个 volatile 变量之前,Java 内存模型会插入一个写屏障(write barrier),读一个 volatile 变量之前,会插入一个读屏障(read barrier)。意思就是说,在你写一个 volatile 域时,能保证任何线程都能看到你写的值,同时,在写之前,也能保证任何数值的更新对所有线程是可见的,因为内存屏障会将其他所有写的值更新到缓存。

  1、 Byte,short,int,long默认是都是0

  2、 Boolean默认值是false

  3、 Char类型的默认值是’’

  4、 Float与double类型的默认是0.0

  5、 对象类型的默认值是null

  在 Java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在 JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

  有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等,必须有相同的hashcode 值,但是没有关于不相等对象的任何规定。

  1、 首先synchronized是Java内置关键字,在JVM层面,Lock是个Java类;

  2、 synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。

  3、 synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。

  4、 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

  Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,访问它比访问ArrayList慢很多

  ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。ArrayList的缺点是每个元素之间不能有间隔。

  建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有使用句柄和直接指针2种:

  句柄:如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。

  直接指针:如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。

  这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。

  通俗点讲:是看看左右是不是一个东西。equals是看看左右是不是长得一样。如何记住嘛。如果单纯是想记住,:等于。equals:相同。两个长得一样的人,只能说长的相同(equals),但是不等于他们俩是一个人。你只要记住equals,==就不用记了。

  术语来讲的区别:

  1、 ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同

  2、 ==是指对内存地址进行比较 equals()是对字符串的内容进行比较3.==指引用是否相同 equals()指的是值是否相同

  Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多线程的标记-整理算法,在 JDK1.6才开始提供。

  在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量, Parallel Old 正是为了在年老代同样提供吞吐量优先的垃圾收集器, 如果系统对吞吐量要求比较高,可以优先考虑新生代Parallel Scavenge和年老代 Parallel Old 收集器的搭配策略。

  对象创建十分频繁,即使修改一个指针的位置在并发下也不是线程安全的,可能正给对象 A 分配内存,指针还没来得及修改,对象 B 又使用了指针来分配内存。

  解决方法:① CAS 加失败重试保证更新原子性。② 把内存分配按线程划分在不同空间,即每个线程在 Java 堆中预先分配一小块内存,叫做本地线程分配缓冲 TLAB,哪个线程要分配内存就在对应的 TLAB 分配,TLAB 用完了再进行同步。

  如果其他方法没有synchronized的话,其他线程是可以进入的。

  所以要开放一个线程安全的对象时,得保证每个方法都是线程安全的。

  Serial 与 Parallel 在 GC 执行的时候都会引起 stop-the-world。它们之间主要不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而parallel 收集器使用多个 GC 线程来执行。

  因为Java所有类的都继承了Object,Java想让任何对象都可以作为锁,并且 wait(),notify()等方法用于等待对象的锁或者唤醒线程,在 Java 的线程中并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中。

  有的人会说,既然是线程放弃对象锁,那也可以把wait()定义在Thread类里面啊,新定义的线程继承于Thread类,也不需要重新定义wait()方法的实现。然而,这样做有一个非常大的问题,一个线程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?当然了,这种设计并不是不能实现,只是管理起来更加复杂。

  redux-thunk优点:

  1、 体积小: redux-thunk的实现方式很简单,只有不到20行代码

  2、 使用简单: redux-thunk没有引入像redux-saga或者redux-observable额外的范式,上手简单

  redux-thunk缺陷:

  1、 样板代码过多: 与redux本身一样,通常一个请求需要大量的代码,而且很多都是重复性质的

  2、 耦合严重: 异步操作与redux的action偶合在一起,不方便管理

  3、 功能孱弱: 有一些实际开发中常用的功能需要自己进行封装

  redux-saga优点:

  1、 异步解耦: 异步操作被被转移到单独 saga.js 中,不再是掺杂在 action.js 或 component.js 中

  2、 action摆脱thunk function: dispatch 的参数依然是一个纯粹的 action (FSA),而不是充满 “黑魔法” thunk function

  3、 异常处理: 受益于 generator function 的 saga 实现,代码异常/请求失败 都可以直接通过 try/catch 语法直接捕获处理

  4、 功能强大: redux-saga提供了大量的Saga 辅助函数和Effect 创建器供开发者使用,开发者无须封装或者简单封装即可使用

  5、 灵活: redux-saga可以将多个Saga可以串行/并行组合起来,形成一个非常实用的异步flow

  6、 易测试,提供了各种case的测试方案,包括mock task,分支覆盖等等

  redux-saga缺陷:

  1、 额外的学习成本: redux-saga不仅在使用难以理解的 generator function,而且有数十个API,学习成本远超redux-thunk,最重要的是你的额外学习成本是只服务于这个库的,与redux-observable不同,redux-observable虽然也有额外学习成本但是背后是rxjs和一整套思想

  2、 体积庞大: 体积略大,代码近2000行,min版25KB左右

  3、 功能过剩: 实际上并发控制等功能很难用到,但是我们依然需要引入这些代码

  4、 ts支持不友好: yield无法返回TS类型

  redux-observable优点:

  1、 功能最强: 由于背靠rxjs这个强大的响应式编程的库,借助rxjs的操作符,你可以几乎做任何你能想到的异步处理

  2、 背靠rxjs: 由于有rxjs的加持,如果你已经学习了rxjs,redux-observable的学习成本并不高,而且随着rxjs的升级redux-observable也会变得更强大

  redux-observable缺陷:

  1、 学习成本奇高: 如果你不会rxjs,则需要额外学习两个复杂的库

  2、 社区一般: redux-observable的下载量只有redux-saga的1/5,社区也不够活跃,在复杂异步流中间件这个层面redux-saga仍处于领导地位

  关于redux-saga与redux-observable的详细比较可见此链接

  最近在备战面试的过程中,整理一下面试题。大多数题目都是自己手敲的,网上也有很多这样的总结。自己感觉总是很乱,所以花了很久把自己觉得重要的东西总结了一下。

  双亲委托模型的重要用途是为了解决类载入过程中的安全性问题。

  1、 假设有一个开发者自己编写了一个名为的类,想借此欺骗JVM。现在他要使用自定义来加载自己编写的类。

  2、 然而幸运的是,双亲委托模型不会让他成功。因为JVM会优先在的路径下找到类,并载入它

  Java的类加载是否一定遵循双亲委托模型?

  1、 在实际开发中,我们可以通过自定义ClassLoader,并重写父类的loadClass方法,来打破这一机制。

  2、 SPI就是打破了双亲委托机制的(SPI:服务提供发现)。

  如果没有找到符合条件的记录,get方法返回null,load方法抛出异常。

  get方法直接返回实体类对象,load方法返回实体类对象的代理。

  在Hibernate 3之前,get方法只在一级缓存中进行数据查找,如果没有找到对应的数据则越过二级缓存,直接发出SQL语句完成数据读取;load方法则可以从二级缓存中获取数据;从Hibernate 3开始,get方法不再是对二级缓存只写不读,它也是可以访问二级缓存的。

  eden区、s0区、s1区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden区->Survivor 区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。另外,大对象和长期存活的对象会直接进入老年代。

  String s="123";

  int i;

  第一种方法:i=Integer.parseInt(s);

  第二种方法:i=Integer.valueOf(s).intValue();

  ThreadPoolExecutor就是线程池

  ThreadPoolExecutor其实也是JAVA的一个类,我们一般通过Executors工厂类的方法,通过传入不同的参数,就可以构造出适用于不同应用场景下的ThreadPoolExecutor(线程池)

  构造参数图:

  

  corePoolSize 核心线程数量 maximumPoolSize 最大线程数量 keepAliveTime 线程保持时间,N个时间单位 unit 时间单位(比如秒,分) workQueue 阻塞队列 threadFactory 线程工厂 handler 线程池拒绝策略

  Java 7 开始,新引入的字节码指令,可以实现一些动态类型语言的功能。Java 8 的 Lambda 表达式就是通过 invokedynamic 指令实现,使用方法句柄实现。

  1、 synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。

  2、 volatile 提供多线程共享变量可见性和禁止指令重排序优化。

  3、 CAS 是基于冲突检测的乐观锁(非阻塞)

  Iterator 使用代码如下:

  List<String> list = new ArrayList<>(); Iterator<String> it = list、iterator(); while(it、hasNext()){ String obj = it、next(); System、out、println(obj); }

  Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。

  不一定,看 Reference 类型,弱引用在 GC 时会被回收,软引用在内存不足的时候,即 OOM 前会被回收,但如果没有在 Reference Chain 中的对象就一定会被回收。

  1、 ArithmeticException(算术异常)

  2、 ClassCastException (类转换异常)

  3、 IllegalArgumentException (非法参数异常)

  4、 IndexOutOfBoundsException (下标越界异常)

  5、 NullPointerException (空指针异常)

  6、 SecurityException (安全异常)

  1、 Init()

  2、 Service()

  3、 doGet或者doPost

  4、 destroy

  静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。

  补充:在Java开发中,上下文类和工具类中通常会有大量的静态成员。基本定义:

  双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器没有找到所需的类时,子加载器才会尝试去加载该类。

  双亲委派机制:

  1、 当 AppClassLoader 加载一个 class 时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器 ExtClassLoader 去完成。

  2、 当 ExtClassLoader 加载一个 class 时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给 BootStrapClassLoader 去完成。

  3、 如果 BootStrapClassLoader 加载失败,会使用 ExtClassLoader 来尝试加载;

  4、 若 ExtClassLoader 也加载失败,则会使用 AppClassLoader 来加载,如果 AppClassLoader 也加载失败,则会报出异常 ClassNotFoundException。

  如下图所示:

  

  双亲委派作用:

  1、 通过带有优先级的层级关可以避免类的重复加载;

  2、 保证 Java 程序安全稳定运行,Java 核心 API 定义类型不会被随意替换。

  都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。

上一篇:湖南绿色金融蓬勃发展—— 向绿而行,促“绿”生“金”
下一篇:“热忱、热情、热烈”

最近更新教育机构