2021春招面经系列--JAVA基础、并发与虚拟机

导读

跨代引用与rememberset跨代引用
三色标记法 三色标记法
Arrays.sort底层 Arrays.sort底层
volatile作用及原理链接

==与equals

== 比较的是两个对象的地址。
equals
情况 1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过==比较这两个对象的地址。
情况 2:类覆盖了 equals() 方法,按照覆盖的方式比较。一般覆盖 equals() 方法来比较两个对象的内容是否相等。

hashcode与equals

hashcode hashcode默认哈希值,该哈希值是int整数,是将内存地址转化为整数值得来的。
为什么要有hashcode
当把对象加入 HashSet时, 会先计算对象的 hashcode 值来判断对象加入的位置,并会与该位置其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。equals的效率较低,这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
hashCode与 equals的相关规定/为什么equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
1.如果两个对象相等,则 hashcode 一定也是相同的。
2.两个对象相等,对两个对象分别调用 equals 方法都返回 true。
3.两个对象有相同的 hashcode 值,它们也不一定是相等的。
因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。

JMM

JMM试图屏蔽各种硬件和操作系统的内存访问差异,实现让 Java 程序在各种平台下都能达到一致的内存访问效果。
JMM 把内存分为本地内存和主存,每个线程都有自己的私有化的本地内存,主存用来存储共享数据,本地内存是在高速缓冲存储器上的。
JMM定义了 8 个操作来完成主内存和工作内存的交互操作:
read:把一个变量的值从主内存传输到工作内存中。
load:在 read 之后执行,把 read 得到的值放入工作内存的变量副本中。
use:把工作内存中一个变量的值传递给执行引擎。
assign:把一个从执行引擎接收到的值赋给工作内存的变量。
store:把工作内存的一个变量的值传送到主内存中。
write:在 store 之后执行,把 store 得到的值放入主内存的变量中。
lock:作用于主内存的变量。
unlock:作用于主内存的变量。
在这里插入图片描述

原子性、可见性、有序性

原子性
一个操作或者多次操作,要么都执行,要么都不执行。
synchronized 和原子类可以保证原子性。valotile不能保证原子性。
可见性
当一个线程修改了共享变量的值,其它线程能够立获取到最新值。
可见性实现方式:
synchronized 和valotile均能保证可见性。
有序性 编译器或者处理器会将指令进行重排,这种操作会影响多线程的执行顺序导致错误。
volatile 关键字通过添加内存屏障的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前。
synchronized保证每个时刻只有一个线程执行同步代码,相当于是让线程顺序执行同步代码。

final可以保证可见性吗

待完成

偏向锁、轻量级锁、重量级锁的适用场景

偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。
轻量级锁:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。
重量级锁:有实际竞争,且锁竞争时间长。

对象头具体包括什么

包括Mark Word、指向类的指针和数组长度(只有数组才有)。当对象状态为无锁状态时,Mark Word包括哈希值、GC分代年龄、偏向锁标志位。当对象状态为偏向锁状态时,Mark Word包括线程ID、GC分代年龄、偏向锁标志位。当对象状态为轻量级锁状态时,Mark Word指向获得锁的线程的Lock Record。当对象状态为重量级锁时,Mark Word指向Monitor对象。

Synchronized重量级锁的底层实现

JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。代码块同步使用monitorenter和monitorexit指令实现,当执行 monitorenter 指令时,线程试图获取对象监视器Monitor的持有权。方法同步使用另一种机制实现,JVM并未明说。
Monitor 对象有一个同步队列和一个等待队列,未竞争到锁的线程存储到同步中,获得锁的线程调用 wait 后放在等待队列中,解锁会唤醒同步队列中的线程来争抢锁,notify会将等待队列中的线程迁移至同步队列。

AQS底层实现

维护一个int型变量state来表示同步状态。维护一个同步队列和多个等待队列。(一个锁上面可以生产多个Condition,每个Condition对象都包含一个等待队列。)
线程使用CAS操作修改 state 变量来争抢锁,争抢不到则进入同步队列等待。调用Condition的await方法,将会使当前线程进入等待队列并释放锁,调用Condition的signal方法时,将会把等待队列的首节点移到同步队列的尾部,然后唤醒该节点。是否是公平锁在于线程获取锁时是直接加入到同步队列尾部还是先利用 CAS 争抢锁。

值传递和引用传递区别

JAVA中只有值传递,参数无论是什么,都会复制一份传递过去。如果传递的是基本数据类型,就会创建一个新的数据传递过去。如果传递的是对象的引用,也会创建一份新的引用传递过去,只不过指向的是同一个对象。在方法中改变参数指向的对象,是不会对原来的参数造成影响的,但可以修改指向的对象的值。
C++中的引用传递不会再创建一个新的引用(指针),而是直接使用原来的引用(指针)。

关于Spring AOP

AOP面向切面编程
连接点被拦截到的程序执行点,如如某个方法调用前、调用后、方法抛出异常后
通知拦截到连接点之后要执行的代码
切入点提供一组规则来匹配连接点,给满足规则的连接点添加通知
切面切面由切入点和通知组成

SpringBoot自动装配

定义指SpringBoot会自动将Bean装配到IoC容器中。例如我们添加了Redis的starter依赖,那么我们需要使用RedisTemplate的时候,直接使用@Autowried将RedisTemplate从IoC容器中拿来注入就可以使用,不需要我们自己去使用XML或者JavaConfig去把RedisTemplate装配到IoC容器。
原理待完成

函数式编程与Lamda表达式

待完成

SpringIOC原理

待完成

重载和重写

重载重载就是同一个类中多个同名方法根据不同的参数列表来执行不同的逻辑。方法名相同,参数列表不同,返回类型和权限修饰符可通可不同。
重写子类对父类方法的重新改造。两同两小一大:方法名相同、参数列表相同,返回值类型、异常比父类更小,访问权限比父类更大。

同步机制和异步机制

同步机制在进行输入输出时,必须等待输入输出完毕后,才能进行后面的操作。
异步机制不必等待输入输出完毕就可进行其它操作。
例子同步就是我叫你吃饭,你听到了就立刻跟我去,若你没有反应,那我就不停叫你,直到你回应。异步就是我叫了你,然后我就去吃饭了,不管你听没听见。

类的加载

加载
通过全类名获取定义此类的二进制字节流;将字节流所代表的静态存储结构转换为方法区的运行时数据结构;在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口。
加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了。
连接:验证、准备、解析
验证验证 Class 文件的文件格式、元数据、字节码、符号引用当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备为类变量分配内存并设置类变量初始零值。使用final修饰的直接赋初值。
解析将常量池内的符号引用替换为直接引用。符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针。
初始化执行初始化方法 ()。初始化方法是由编译器自动收集类中类变量的赋值和静态语句块生成的,主要是为静态变量赋值。

类加载器

分类 从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:
启动类加载器(Bootstrap ClassLoader),使用 C++ 实现,是虚拟机自身的一部分;
所有其它类的加载器,使用 Java 实现,独立于虚拟机,继承自抽象类 java.lang.ClassLoader。
启动类加载器 Bootstrap ClassLoader。属于虚拟机自身的一部分,用 C++ 实现。负责加载 %JAVA_HOME%/lib目录下的jar包和类,或被 -Xbootclasspath参数指定的路径中的所有类。
拓展类加载器 Extension ClassLoader。负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包。
应用程序类加载器 Application ClassLoader。也称为系统类加载器。负责加载用户类路径(ClassPath)上所指定的类库。如果应用程序中没有自定义过自己的类加载器,一般情况下是程序中默认的类加载器。
自定义类加载器的实现继承 java.lang.ClassLoader,重写重findClass() 方法,该方法作用是根据名称或位置加载.class字节码。loadClass() 实现了双亲委派模型的逻辑,自定义类加载器一般不去重写。

双亲委派模型

定义类加载器加载类的时候,会首先把这个类加载请求委派给父类加载器去完成,如果父类还有父类,那么就接着委托,一直递归到顶层。只有父加载器无法完成这个请求时,子类才会尝试去加载。
好处
使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。
例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中所有的 Object 都是这个 Object。
违反双亲委派模型的例子 JDBC。JDBC 的接口是类库定义的,但实现是在各大数据库厂商提供的 jar 包中,那通过启动类加载器是找不到这个实现类的,所以就需要应用程序加载器去完成这个任务,因此违反了双亲委派模型。

New一个Object对象占多少字节

8字节是引用,16字节是堆内存,总共是8+16=24字节。

JAVA异常

所有的异常都继承自 Throwable 类。Throwable 类有两个重要的子类 ,Exception(异常)和 Error(错误)。
Exception 程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为受检查异常(必须处理,没有被 catch/throw 处理的话,就没办法通过编译)和不受检查异常(可以不处理,即使不处理也可以正常通过编译)。
Error 程序无法处理的错误 (只能尽量避免)。
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、ClassNotFoundException 、SQLException。
RuntimeException 及其子类都统称为非受检查异常,例如:NullPoin​terException、NumberFormatException(字符串转换为数字)、ArrayIndexOutOfBoundsException(数组越界)、ClassCastException(类型转换错误)、ArithmeticException(算术错误)等。

垃圾回收器

Serial
采用复制算法。一个单线程收集器,会使用一条垃圾收集线程去完成垃圾收集工作,在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。
ParNew
采用复制算法。是 Serial 收集器的多线程版本。
Parallel Scavenge
采用复制算法。与ParNew相比,关注点是吞吐量,即CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。
Serial Old
采用标记-整理算法。Serial 收集器的老年代版本。
Parallel Old
采用标记-整理算法。Parallel Scavenge 收集器的老年代版本。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。
CMS
是一种以获取最短回收停顿时间为目标的收集器,第一款真正意义上的并发收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
基于标记-清除算法。
初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
并发标记: 同时开启 GC 和用户线程,记录可达对象。但在这个阶段结束,并不能保证记录当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
优点:并发收集、低停顿。
缺点:对 CPU 资源敏感;无法处理浮动垃圾;它使用的“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
G1
主要针对配备多颗处理器及大容量内存的机器。
G1 可以直接对新生代和老年代一起回收,把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离,每个Region可以单独进行垃圾回收。
通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。
分为初始标记、并发标记、最终标记、筛选回收四个阶段。
优点:
空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。

垃圾回收算法

标记清除算法
分为“标记”和“清除”阶段:首先标记出所有活动对象,在标记完成后统一回收掉所有没有被标记的对象。回收对象就是把对象作为分块,连接到被称为 “空闲链表” 的单向链表,之后进行分配时只需要遍历这个空闲链表,就可以找到分块。
不足:标记和清除过程效率都不高;会产生大量不连续的内存碎片,导致无法给大对象分配内存。
标记整理算法
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
优点:不会产生内存碎片。
不足:需要移动大量对象,处理效率比较低。
复制算法
将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
不足:只使用了内存的一半。

GC ROOTS包括什么

虚拟机栈(栈帧中的本地变量表)中引用的对象
本地方法栈(Native 方法)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
所有被同步锁持有的对象

垃圾回收相关

为什么新生代使用复制算法而不使用标记清除算法
新生代为什么有两个Survivor

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值