面试题汇总(持续完善中)

一、mysql的复制原理

  • 当数据写入到Master节点后,会将操作写入Binarylog中,salve接待你会通过I/ thread 线程读取Binarylog,并写入到Relay log中,然后SQL thread线程会读取Relay log信息写道salve库中
  • 使用binlog 的三种格式来保证主从一致
    • Statement(Statement-Based Replication,SBR):每一条会修改数据的 SQL 都会记录在 binlog 中。
      • 存在的问题:如果根据多个条件(索引)删除数据,主库可能根据a条件删除,从库可能根据b条件删除,会造成数据不一致问题
    • Row(Row-Based Replication,RBR):不记录 SQL 语句上下文信息,仅保存哪条记录被修改。
      • 问题:占用空间比较多,并且如果执行的事务比较大,影响行数比较多的话,binlog会比较大,并且写binlog也会消耗IO资源,影响执行速度
    • Mixed(Mixed-Based Replication,MBR):Statement 和 Row 的混合体。
      • mysql会自己判断是否 存在索引影响,从而选择保存格式
  • 双M架构下的循环复制
    • 双m模式下,互为从,因此会存在互相写log的问题:借助server id,在前面的实验中,我们已经知道在binlog中会记录server id
      • 主备库server id必须不同,如果相同不允许设置为主备关系
      • 一个备库在binlog的重放过程中,生成与原binlog的server id相同的新的binlog
      • 每个库在收到主库发过来的binlog日志时,先判断server id,如果与自己的相同说明是自己生成的,就会直接丢弃这个日志。

二、分库分表策略

  • 垂直分库:根据不同的业务将表分配到不同的数据库上,减轻数据库的请求压力
  • 水平分表:将不同的业务数据分到不同的表中

三、聚簇索引(索引和数据存放一块)和非聚簇索引(索引和数字分开存放)

  • 索引都是存在磁盘
    • innodb的索引和数据共同存放在同一个文件,但是有多个索引的情况,只会有一个索引和数据存在同一文件中,其他的索引的叶子节点会存储索引的key,当查询数据时,会根据key查找数据(回表查询)
    • myisam都是非聚簇索引
  • 索引存储会根据存储类型,存储到不同文件
  • mysql默认会有三种存储文件
    • .frm:表结构
    • .MYD存放myisam数据
    • . MYI:存放myisam索引
    • .ibd 存放innodb的索引

四、MYSQL锁的类型

  • 基于锁的属性:共享锁和排他锁
    • 共享锁又称为读锁:读锁的不影响读,但是会禁止写锁
    • 排他锁:又称为写锁:禁止添加其他锁
  • 基于锁的粒度:行级锁(innodb)、表级锁、页级锁、记录锁、间隙锁、临建锁
    • 间隙锁:只会出现在可重复读事务中 所著某一个区间,当相邻的ID出现空隙则会出现间隙锁
  • 基于锁的状态:意向锁(当某一线程需要加锁的时候,会先去判断是否有更高粒度的锁存在)、意向排他锁

五、Hibernate和Mybatis的区别

  • 相同点
    • 构建流程相同:通过SessionFactoryBuilder根据xml配置文件生成SessionFactory, 然后由SessionFactory生成session,通过session执行sql
    • 都支持JDBC和JTA事务
  • 不同点
    • H 全自动ROM框架 M 半自动ROM框架
    • H 具有完整的日志模块
    • M的二级缓存建立在每一个映射的xml中通过cache-ref实现 H 完全代理二级缓存
    • M 便于sql优化

六、面向对象的特征

  • 继承:通用共性属性、方法;划分职能作用,便于数据的独立和区分性
  • 封装:保护内部数据,对外抛出通用方法,提供代码的维护性
  • 多态:对父类方法的调用,会呈现子类重写的方法逻辑
  • 抽象(非java特有)

七、抽象类(abstract class)和接口(interface)有什么异同

  • 都不可以被实例化、接口和抽象方法都需要被重写
  • 接口可以定义构造函数
  • 抽象类可以有具体的方法,接口只能定义方法
  • 接口中的方法都是public,因为接口的方法都需要被实现
  • 抽象类中可以定义成员变量、接口只能定义常量
  • 抽象方法必须在抽象类中,抽象方法必须被重写
  • 抽象类中可以包含静态方法,接口不能
    • 静态方法不能被重写
  • 单继承和多实现

八、AOP的详细理解:面向切面编程,它是为了解耦而生

  • 切面:创建一个切面类,来自定义切面逻辑
  • 连接点:确定切入的点位
  • 通知:在执行连接点的时候,确定执行的规则:befor、after、around
  • 切点:匹配连接点
  • 引入:
  • 目标对象:被通知的对象
  • AOP代理:

九、引用(指针)类型

  • 强引用:基本的new 对象,队中的内存块一直被指针引用
  • 软引用:java.lang.ref.SoftReference类来表示软引用
    • 当JVM内存不足,软引用会被回收
  • 弱引用: java.lang.ref.WeakReference来表示弱引用
    • gc会处理掉所有的弱引用
  • 虚引用: PhantomReference 类来表示
    • 随时可能被gc回收,虚引用必须要和 ReferenceQueue 引用队列一起使用
  • ReferenceQueue
    • 虚引用会被装到队列中,当gc进行回收的时候,会先检查队列,如果队列中的虚引用,引用了堆外数据,则也会回收堆外的内存

十、ThreaLocal管理全局线程对象

  • 底层通过ThreadLocalMap实现的k-v形式
    • Thread 线程创建的时候,内部有一个Map
  • 线程的局部变量:为每一个线程提供一个变量副本,避免了并发下变量冲突
    • 本质在JVM在每一个线程中创建一个ThreadLocalMap,以线程(ThreadLocal)为key,并且继承了 WeakReference 弱引用
      • 当ThreaLocal tl = new ThreaLocal();
        • ThreaLocal 内部引用entry的key
        • 当tl断开引用,但是当前线程依然存在,但是threadLocal对象因为key一直在引用,因此无法被回收,因此key这里设置为弱引用,因此,tl没有引用的时候,可以直接被回收,此时key也会成为null
      • 不使用ThreadLocal时,一定要remove掉
        • 当key是null,value将无法被获取,因此无法回收value
        • ThreadLocal.remove() : 移除entry
  • 系统任意地方使用,在同一个线程中都会使用同一个对象(local.set(xxx);local.get(xxx))

十一、synchronized和ReentrantLock

  • synchronized:关键字,无法判断锁状态,会一直等待锁释放,适合锁小块代码
  • lock:类,可以获取锁状态,lock.tryLock() 尝试重新获取锁,适合锁大量代码
  • 延伸问题:之所以使用锁,是因为多线程操作同一个变量引发的是数据错问题
    • 原子性:原子是最小的不可分割的单位,当前原子操作,不会被其他线程干扰
    • 可见性,线程所操作的变量,会被立即同步到主内存中,其他线程可以获取到最新的数据
      • volatile:线程直接操作主内存变量,而不是复制的本地内存
    • 有序性:指令串行执行,不会出现指令重排序

十二、线程的状态

  • 新建:线程被创建但未被运行
  • 就绪:线程已进入jvm,但不确定是否在运行还是在等待
  • 阻塞
  • 等待:wait
  • 终止

十三、死锁

  • AB分别占用AB锁,AB此时要获取对方的锁后释放自身锁,因此,永远释放不了锁
  • jps -l :获取当前java的进程
  • jstack 进程号: 查看进程信息 :found a deadlock (发现一个死锁)

十四、AtomicInteger底层实现原理

  • AbstractQueuedSynchronizer(AQS):其是 Java 并发包中,实现各种同步结构和部分其他组成单元(如线程池中的 Worker)的基础
    • AQS内部可以分为三部分
      • 一个使用volatile修饰的变量,包括了set和get
      • 一个先进先出的队列
      • 基于cas的操作方法
    • 实现AQS的两个必备条件
      • acquire :获取资源的独占权
      • release:释放资源

十五、 ReentrantLock 解析AQS的实现

  • AQS内部提供了acquire获取资源锁的方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
//tryAcquire:尝试获取资源锁
//acquireQueued:获取锁失败
  • ReentrantLock 提供了两种获取资源所的方法
    • NonfairSync:非公平锁
    • FairSync:公平锁
  • 以非公平锁讲解
    • 当一个线程尝试获取资源锁(tryAcquire),首先获取AQS的状态:0 代表资源未被占用
      • 当未被占用的情况,则会使用cas修改AQS状态
      • 当被占用的情况下,会出现两种情况
        • 被当前线程占用:当前线程占用了同一个对象的其他方法,此时会进行重入锁,锁状态+1
        • 被其他线程占用:重新回到锁竞争的队列
    • 当前一个节点获取锁失败后,会执行acquireQueued方法
      • 循环获取当前节点,判断当前节点的前一个节点是否是头节点以及尝试获取锁
      • 如果满足上述条件并且获取锁,设置当前线程为头节点

十六、类加载过程以及双亲委派

  • 加载:java将字节码数据加载到jvm中并映射成jvm可识别的class对象,数据可以是class文件、jar文件以及网络资源等
  • 链接:核心步骤,将原始的类定义信息转入JVM运行过程
    • 验证:jvm检验字节信息是否符合要求
    • 准备:创建类或者接口中的静态变量并初始化值(分配内存空间)
    • 解析:将常量池中的符号引用替换为直接引用
  • 初始化:初始化类,包括变量的赋值、静态代码块的执行,父级的相关初始化优先于子类
  • 双亲委派:加载子类的时候,会优先在父级中查找相关的类型,查找不到才会加载子类
    • jvm加载class的时候,会先执行loadclass方法,在父级中查找相关类型
    • 当没有找到后,会执行当前类的findclass方法:根据名称读取文件的二进制字节数组
    • dnfineclass:把字节转化为class文件

十七、自定义类加载器

  • 加密class文件,避免被轻易的反编译
    • 编译:javac -xxx.java
    • 反编译:javap -v xxx.class
  • 从其他地方获取class文件,需要自定义加载器
  • 根据双亲委派原则,可以实现自定义类加载器,继承loadclass,实现findclass
    • 自定义MyClassLoader,需要定义一个People类并编译成class文件
    MyClassLoader mcl = new MyClassLoader(); 
    Class<?> clazz = Class.forName("People", true, mcl); 
    Object obj = clazz.newInstance();
    

十八、关于运行时动态生成Java类

  • java程序生成源码或者提前预备好源码,通过ProcessBuilder 操作系统命令执行javac编译源码,然后通过自定义的类加载器加载编译后的class文件

十九、谈谈JVM内存区域的划分,哪些区域可能发生OutOfMemoryError

  • 计数器:每一个线程都会有自己的程序计数器,程序计数器会存储当前线程正在执行的java方法的JVM指令地址,如果执行的是本地方法,则地址为undefined
  • 虚拟机栈:每个线程在创建的时候都会创建一个虚拟机栈,内部保存着各个栈帧,对应着java方法一次次的调用,同一时间只会有一个活动的栈帧
    • 栈帧:存储着局部变量、操作数栈、动态链接、方法退出等信息
  • 堆:java内存的核心管理区域,用来存放对象的实例,几乎所有的对象实例都存储在堆中,堆中的信息得所有的线程共享,启动虚拟机的是否,可以通过Xmx等参数指定虚拟机的内存大小
    • 因为存在垃圾收集器,因此为了垃圾回收问题,堆内存还会被分为新生代、老年代
  • 方法区:用于存储元数据,例如:类结构信息、以及对应的运行时常量、字段、方法代码等;属于共享区域,jdk1.8之前被称为永久代,现在已经移除了永久代增加了此元数据区
    • 运行时常量池:存放着各种形态的常量信息,不管是编译过程生成的字面量,还是在运行期决定的符号引用
    • 编译常量:被final修饰的变量或者字符串相加的常量,在编译阶段都会被jvm优化放入常量池,而不会等待运行时再获取并放入常量池
      • 经典案例:Sting s = “a” + “b” + “c”:本质上如果常量池中没有 abc,则会创建一个abc对象,否则不会创建新对象而是直接去常量池中拿
    • 本地方法栈:和java虚拟机非常相似,支持堆本地方法(native)的调用,每一个线程都会创建一个对应的本地方法栈
    • jvm本身就是一个本地程序,除了上述之外,其实jvm还会使用其他内存来完成自身的一些基本任务
    • 直接内存、codeCache等其他内存

二十、关于OOM

  • 可能发生OOM的原因
    • 内存泄漏、堆的大小不合理,不能支撑class对象的加载
    • 栈溢出:当函数不断地被调用,会将函数的参数不断地积压在栈中,可能会出现StackOverFlowError,如果jvm尝试扩展栈的内存大小失败,也会抛出OutOfMemoryError
    • 方法区(旧版本的永久代):当不断地创建新的实例化对象或者填充运行时常量,可能会导致堆内存OOM

二十一、如何监控和诊断JVM堆内和堆外内存使用

  • 堆外内存

    • 除了jvm管理的内存之外的系统内存
    • ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
      • 直接调用对外内存缓存到buffer
  • 可视化工具JConsole、VisualVM

  • JDK 的jar包中已经引入了jconsole

    • windows系统直接 命令窗口 输入jconsole 即可打开可视化界面
    • linux在启动java 项目的时候,添加上启动参数
    nohup java  -Xms128M -Xmx256M  -Djava.rmi.server.hostname=192.169.1.71 -Dcom.sun.management.jmxremote.rmi.port=10099 -Dcom.sun.management.jmxremote.port=10099 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false  -jar web.jar  &
    Djava.rmi.server.hostname:服务所在的服务器ip
    Dcom.sun.management.jmxremote.port:JConsole链接的端口号,不能和其他端口重复
    
    Dcom.sun.management.jmxremote.authenticate:false代表可以不需要账号密码认证
    
  • JDK 的jar包中已经引入了VisualVM

    • windows系统直接 命令窗口 输入jconsole 即可打开可视化界面
    • linux在启动java 项目的时候,添加上启动参数,同JConsole
    • 在服务器{JAVA_HOME}/bin目录建立文件:jstatd.all.policy(名字随便,符合*.policy即可)
    grant codebase "file:${java.home}/../lib/tools.jar" {    
    	permission java.security.AllPermission;    
    };
    # 启动policy:nohup $JAVA_HOME/bin/jstatd -J-Djava.security.policy=jstatd.all.policy - 1009 &
    # 默认端口1009
    

二十二、JVM通过GC年代划分

  • 新生代:对象创建和销毁的区域
    • Eden(伊甸园):对象初始化分配的区域
      • 内部还会被分为多个TLAB
        • TLAB:和线程一一对应,每一个线程会有一私有缓存区,被称为TALB
          • TLAB会包含三个标志点
            • start:缓存开始点
            • top:缓存当前位置
            • end:TALB的混村最大值
          • 当top到达end点的时候,会重新分配一个新的TALB
    • Survivor(幸存者):会被分为两个区域,一个from一个to,被用来放置没有被GC清理的对象
    • GC在工作的时候,会把清理不掉的对象拷贝到to区域
    • Virtual:暂时不可用区域,默认JVM只会使用初始化的内存,当需求的内存越来越大,才会使用此区域
  • 老年代
    • Survivor区域的对象会被拷贝到老年代
    • 在Eden中如果对象过大,在新生代中找不到能存储此对象的连续空间,则会被直接分配到老年代
  • 永久代:这部分就是早期 Hotspot JVM 的方法区实现方式了,储存 Java 类元数据、常量池、Intern 字符串缓存
  • 在 JDK 8 之后就不存在永久代,被替换为MetaSpace(元数据区)
    • 可以通过启动参数设置
      • 最大内存:-Xmx value
      • 初始化的内存:-Xms value
      • 老年代和新生代的比例:-XX:NewRatio=value
        • 默认情况下,这个数值是 2,意味着老年代是新生代的 2 倍大;换句话说,新生代是堆大小的 1/3。
      • 设置新生代的大小:-XX:NewSize=value
      • 设置Eden和Survivor 的比例:-XX:SurvivorRatio=value
        • YoungGen=Eden + 2*Survivor ;新生代和永久代的计算比例
          • 如果设置为8,也就是代表Eden是Survivor的八倍

二十三、利用NMT特性,分析JVM

  • 准备工作
-XX:NativeMemoryTracking=summary:启动时候,选择NMT的统计模式
-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics:应用退出的时候打印统计信息
  • 统计信息分析
    • java Heap:java堆内存的信息(剩余内存和被使用的内存)
    • class:统计java类元数据占用的空间
    -XX:MaxMetaspaceSize=value:设置类元数据大小
    #Thread:包括java主线程、Cleaner线程等,也包括了GC线程
    	#如果小系统,可以通过修改使用的GC
    	#G1回收器复杂度最高,效果最好,占用线程数最多
    	#Parallel GC:线程数较少
    #修改GC使用
    -XX:+UseG1GC
    -XX:+UseParallelGC
    -XX:+UseSerialGC
    
    • code:统计了其他内存及codeCache使用的内存,也就是 JIT compiler 存储编译热点方法等信息的地方
      • compiler 部分的内存消耗较大,默认是开启的,如果我们不需要编辑,则可以关闭
      • -XX:-TieredCompilation

二十四、JAVA的垃圾回收器都有哪些

  • JVM提供商有很多家,具体分析Oracle的JDK
    • Serial GC:最古老的GC,使用单线程,并且在运行时会进入Stop-The-World状态,设计简单
      • 老年代区域采用标记-整理算法、新生代采取复制算法
      • -XX:+UseSerialGC
    • ParNew GC:新生代的GC,实际是Serial GC的多线程版本,通常配合CMS GC工作
      • -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
    • CMS GC:基于标记-清除算法
      • 减少了停顿时间,降低了系统响应时间
      • 会存在内存碎片,长时间运行的情况下会存在full gc
      • 在并发情况下会抢占用户线程、占用更多的cpu
      • -XX:+UseParallelGC
    • Parrallel GC:也是使用标记-整理算法,整体复杂度较高,是新生代和老年代是同步进行的,在web环境下效率较高
      • -XX:+UseParallelGC
      • -XX:MaxGCPauseMillis=value:设置停顿时间
    • jdk9之后默认使用了G1GC
      • G1可以直观的设置停顿时间,不能保证每次都是完美的停顿时间,但是避免了超长时间的停顿
      • G1将每个区域作为一个region,region之间是复制算法,总体可以被看作标记-整理算法,避免了内存碎片

二十五、垃圾收集的原理和基础概念

  • 垃圾回收器主要作用域两个地方:对象实例回收和方法区的元数据回收
    • 引用计数法:为对象的引用添加一个计数,当计数为0则代表没有对象引用了
    • 因为java难以处理循环引用,因此java中使用可达性分析
      • 将对象及其引用关系看作一张图,选定活动的对象(栈引用的对象、静态属性引用的都西昂和常量)作为GC Roots,当一个对象无法和GC Roots建立引用关系,则为可回收对象

二十六、常见的垃圾收集算法

  • 复制算法:将存活的对象复制到to区域,拷贝过程中,按照顺序放置对象,可以避免内存碎片
    • 复制存在的问题就是浪费了一部分内存,对于G1,则还要多维护region之间的关系
  • 标记-清除:标记需要被回收的对象,然后清除,此过程会导致对象和对象之间存在空置的内存,也就是所谓的内存碎片
  • 标记-整理:先标记所有需要被回收的对象,然后清理对象后依次移动对象,避免内存碎片

二十七、java程序不断地创建新对象,新对象都会被放置在Eden区域

  • 第一步:当Eden区域达到垃圾回收的标准后,会进行回收,存活下来的对象会被复制到Survivor 区域(当前区域被称为from),对象标记+1
  • 第二步:当Eden再次需要回收的时候,存活下来的对象以及from中的对象会被复制到另外一个Survivor 区域(此区域及为to),对象标记+1
  • 接下来,类似的过程会不不断地发生,直到有的对象的标记达到一定的数值,则会被复制到老年代
    -XX:MaxTenuringThreshold= num:设置标记的数值
  • 老年代中则会采用相关的算法进行GC

二十八、GC的优化思路

  • 主要分为三个点:内存占用、停顿时间以及吞吐量
    • 如果服务偶尔出现抖动,较长时间的服务停顿:则将停顿时间修改控制在可接受的范围之内
    • 通过 jstat 工具监控gc的变化:主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控
    • 命令:jstat [Options] vmid [interval] [count]
      • Options:
        • 一般使用 -gcutil :查看gc情况
        • -gcnewcapacity:查看年轻代对象的信息及其占用量
        • -gcoldcapacity:查看老年代对象的信息及其占用量
        • -gcnew:查看年轻代对象的信息
        • -gcold:查看老年代对象的信息
        • -gc显示gc的信息,查看gc的次数,及时间
        • 使用top -Hp显示进程所有的线程信息查找CPU耗时最长线程PID
        • 使用jstack查找耗时进程是哪个函数
      • vmid,VM的进程号,即当前运行的java进程号
      • interval,间隔时间,单位为秒或者毫秒
      • count,打印次数,如果缺省则打印无数次
    • jstat -gc 12538 5000 :即会每5秒一次显示进程号为12538的java进成的GC情况
      在这里插入图片描述
  • 针对当前环境选择合适的gc
    • 响应时间优先:选择CMS和G1
    • 在乎内存:Parrallel GC
  • 根据服务器的配置,设置相关参数、扩大内存等
  • jstack命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或javacore文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如请求外部资源导致的长时间等待、线程间死锁、死循环等都是导致线程长时间停顿的常见原因。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者等待着什么资源。
    • jstat -xxxx PID
    • 查看当前进程Class类加载的统计: class
    • 查看编译情况:compiler
    • 查看垃圾回收gc统计:gc
      在这里插入图片描述

二十九、Java内存模型中的happen-before是什么

  • 保证顺序执行,保证在某操作之后必须执行后续规定的某一个操作
  • 线程内执行的每个操作,都保证 happen-before 后面的操作,这就保证了基本的程序顺序规则
  • 对于 volatile 变量,对它的写操作,保证 happen-before 在随后对该变量的读取操作
  • 对于一个锁的解锁操作,保证 happen-before 加锁操作
  • 对象构建完成,保证 happen-before 于 finalizer 的开始动作
  • 甚至是类似线程内部操作的完成,保证 happen-before 其他 Thread.join() 的线程等待

三十、Exception和error

  • 都继承了Throwable
  • Exception是运行时异常
  • error是系统异常

三十一、Hashtable、HashMap、TreeMap 有什么不同

  • Hashtable:早期提供的,不支持null键和值,同步问题(synchronized)导致性能较差
    • 每一个add方法都加锁
    • 使用Enumeration
    • 默认大小11,扩容采用old*2 +1
  • HashMap:支持null键和值,不是同步,性能较高,使用put和get
    • 使用iterator
    • hashmap源码中 newTab[e.hash & (newCap - 1)];
    • 默认大小16,加载因子0.75,扩容采用2次幂
      • 1.7之前采用的是重新计算hash
      • 1.8之后的二次幂扩容(图)
        • 容量为16转化为二进制是10000,因此15的二进制是01111
        • 当任意hash 和 01111 进行& 运算的时候,得到结果的后四位都是不同的,因此不会出现hash碰撞问题
          在这里插入图片描述
      • HashMap的默认大小必须是2的,避免hash计算二进制,造成位数0,避免浪费资源
      • 1.7版本 采用数组+链表的进行头部插入
      • 1.8版本 采用数组+链表+红黑树:当链长达到8并且数组数量大于64时,会将链表替换成红黑树
      • 线程不安全:当多个线程竞争的时候,可能存在线程打断,数据错误问题
  • TreeMap:基于红黑树的一种顺序访问的map
  • Collections.synchronizedMap():线程安全
  • ConcurrentHashMap:大量使用了Volatile和cas,保证了线程安全
    • volatile:可见性、禁止重排序、不保证原子性
      • 程1从主内存中拿了一个值为1的数据到自己的工作空间里面进行加1的操作,值变为2,写回主内存,然后还没有来得及通知其他线程,线程1就被线程2抢占了,CPU分配,线程1被挂起,线程2还是拿着原来主内存中的数据值为1进行加1,值变成2,写回主内存,将主内存值为2的替换成2,这时线程1的通知到了,线程2重新去主内存拿值为2的数据。
      • 经典的单例模式,重排序会导致对象引用先于对象初始化,多线程下,会出现线程拿到空对象
      • 所有线程都可见主内存的中被volatile修饰的变量,而不是复制到自身内存的副本

三十二、关于String类为final修饰并且不可变

  • String字符通常都被还在String常量池
  • 因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算
  • 这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

三十三、 & 和 && 的区别

  • 当作为运算符号的时候
    • &不管左边什么结果都会执行右边的操作
    • && 当左边的操作是false的时候,则不会执行右边的逻辑
  • 当作为二进制运算符号的的时候
    • & 与运算:都为1才为1
    • | 或运算:一个为1则为1
    • ^ 异或运算:不同为1、相同为0
    • >> x 右移x位:高位补0
    • >>> x 右移x位:正数高位补0、负数高位补1

三十四、针对String类型的变量初始化和赋值

  • 当创建一个String对象的时候,会被放到常量池中
    • 1.7之后,常量池从方法区被迁移到了堆内存
  • 如果new String(“xxxx”),会先在堆内存分配空间用来存储常量池的地址
    • 所以String aa = “xxx”;和String bb = new String(“xxx”),虽然最终指向的String常量池的地址是一样的,但是bb首先指向到是对内存的地址,堆内存最终指向常量池的地址
  • 当使用+ 拼接的时候,会产生一个新的String对象,而不是修改之前旧的String对象
  • 使用 StringBuilder和StringBuffer,会修改原字符串
    • StringBuilder线程不安全
    • StringBuffer的方法都使用了synchronized

三十五、字符流和字节流

  • 将字节流转化为字符流
    • InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(new File(“”)));
    //将对象写出到文件
    ObjectOutputStream objectOutputStream =
            new ObjectOutputStream(new FileOutputStream(new File("D://obj")));
    objectOutputStream.writeObject(new User());
    //将文件内容读取为对象
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D://obj")));
    User user = (User)objectInputStream.readObject();
    

三十六、深浅克隆以及序列化

  • 浅克隆,引用对象指向同一个内存地址
  • 深克隆,引用对象被创建一个新对象
  • 定义一个通用的clone方法,此方法需要对象实现序列化
 public static <T extends Serializable> T clone(T obj) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
       	ObjectOutputStream oos = new ObjectOutputStream(bout);
      	oos.writeObject(obj);
        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
       	ObjectInputStream ois = new ObjectInputStream(bin);
       	return (T) ois.readObject();
   	 }
  • 实现Serializable 接口代表当前对象可以被实例化,将对象转化为流,则为真正的序列化操作

三十七、final、finally、finalize 的区别

  • final:定义为不可变
  • finally:不论什么结果,都或执行finally中的操作
  • finalize:Object类的方法,当对象被标记为被回收状态,GC会在回收前调用对象的finalize方法

三十八、常见的异常

● java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序试图通过字符串来加载某个类时可能引发异常。
● java.lang.NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
● java.lang.IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对象时发生。
● java.lang.IllegalArgumentException 方法传递参数错误。
● java.lang.ClassCastException 数据类型转换异常。
● java.lang.NoClassDefFoundException 未找到类定义错误。
● SQLException SQL 异常,常见于操作数据库时的 SQL 语句错误。
● java.lang.InstantiationException 实例化异常。
● java.lang.NoSuchMethodException 方法不存在异常。

三十九、Math.round(xxx)

  • 四舍五入的算法:四舍五入的窍门,在数值+0.5取整
  • 例如Math.round(11.5):结果12
  • 例如Math.round(-11.5):结果-11

四十、switch支持的类型

  • char, byte, short, int, Character, Byte, Short, Integer, String, enum

四十一、获取日期相关操作

LocalDate today = LocalDate.now();
//获取本月第一天
LocalDate firstday = LocalDate.of(today.getYear(), today.getMonth(), 1);
//获取本月最后一天
LocalDate lastDay = today.with(TemporalAdjusters.lastDayOfMonth());
//格式化日期
DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date2 = LocalDate.now();
System.out.println(date2.format(newFormatter));
//获取今天
 LocalDateTime today = LocalDateTime.now();
//获取昨天
LocalDateTime yesterday = today.minusDays(1);

四十二、java8新特性排序用法

  • 根据对象的属性排序集合

    userList.stream().sorted(Comparator.comparing(User::getUserNo)).collect(Collectors.toList());
    
  • 根据map的key排序map;默认从小到大排序,reversed从大到小排序

    Map<Integer, Object> result = new LinkedHashMap<>();
    HashMap<Integer, Object> map = new HashMap<>();
    map.entrySet().stream()
            .sorted(Map.Entry.<Integer, Object>comparingByKey().reversed())
            .forEachOrdered(x -> result.put(x.getKey(), x.getValue()));
    
  • 根据map的key对应的值去重

    nowCollectList = nowCollectList.stream()
            .collect(Collectors.collectingAndThen(
                    Collectors.toCollection(() ->
                            new TreeSet<>(Comparator.comparing(value -> value.get("id") + ";"))),
                    ArrayList::new));
    

四十三、事务的四大特性(ACID)

  • 原子性(atomicity):不可分割的最小单元,同时失败或成功
  • 通过undolog保证原子性,它记录了需要回滚的日志信息,事务回滚会撤销已经完成的sql操作
  • 一致性(consistency):从一种状态到另一种状态保持一致
    • 由其他三大特性共同保证
  • 隔离性(isolation):事务之间互不干扰
  • 持久性(durability):事物的操作会持久化数据
    • 通过redolog保证,mysql修改数据会记录到redolog中,最终会根据日志进行持久化操作

四十四、MVCC多版本并发控制保证事务的隔离性

  • 读读:不存在问题
  • 读写:有线程安全,可能存在脏读、幻读、不可重复读
  • 写写::有线程安全,可能存在数据丢失
  • MVCC是一种用来解决读写冲突的无锁并发机制,也就是为事务 中的每一项增加个时间戳,为每一次修改增加一个版本号,版本和时间戳相关关联 ,读事务,只会读取事务开始前的数据快照

四十五、MVCC实现原理:undolog、read view和三个隐藏字段

  • DR_TRX_ID:最新修改的事务id
  • DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本,用于配合undolog
  • DB_ROW_ID:隐藏的主键,如果表没有主键,innodb会生成一个6字节主键
    • undolog详解:回滚日志
      • 执行插入操作,事务提交后,会删除log日志
      • 当进行删除或者更新操纵,除了事务提交之后,还要再快照以及其他事务不涉及到此日志的情况下,才会删除log
      • purge 线程进行 log的删除
    • readView
      • 当某个事务在执行快照读的时候,对该记录创建一个readView 视图,把它当作当前事务能够看到的那个面板数据
      • readview遵循可见性算法,去除当前事务的id和系统当前活跃事务id进行比较,如果不符合,则取undolog中查找与之匹配的事务id对应的数据
      • 三个全局属性
        • trx_list:记录了readview生成时刻的活跃事务id集合
        • up_limit_id:记录了trx_list列表中事务ID最小的ID
        • low_limit_id:read view生成时刻系统尚未分配的下一个事务id
      • 在RC隔离模式下,事务中的每一个 select 读操作都会创建 read view 快照
      • 在RR隔离模式下,快照的产生只会在事务开启后第一个 select 读操作后创建 readview 快照

四十六、并发事务

  • 脏读:读到其他事务未提交的数据,
  • 幻读:两次读取,第二次读取到了其他事务提交的新数据
  • 不可重复读:两次读取,第二次读取到了其他事务更新的数据
  • 串行化:按照顺序依次执行

四十七、Spring隔离级别

  • 默认:mysql默认事务
  • 读未提交(READ UNCOMMITEED):会出现脏读、幻读、不可重复读
  • 读已提交(READ COMMITEED):会出现幻读、不可重复读
  • 可重复读(REPEATABLE READ):不可读取未提交的数据,会出现不可重复读
  • 串行读(SERIALIZABLE):按照顺序依次执行sql

四十八、事物的传播特性

  • REQUIRED(默认):加入当前事务
  • Never:不加入事务
  • MANDATORY: 支持当前事务,若没有事务,抛出异常
  • NOT_SUPPORTED: 非事务运行,挂起当前事务
  • REQUIRES_NEW: 挂起当前事务,创建新的事务
  • NETSETD: 嵌套事务,支持当前事务并且和当前事务同时提交和回滚,不影响当前事务
  • SUPPORT: 支持当前事务,不新启事务

四十九、事务失效原因

  • bean没有被spring管理

  • 方法的修饰符不是public

    • TransactionInterceptor 通过 代理 执行invokeWithinTransaction 方法 ,在方法中通过getTransactionAttribute -> computeTransactionAttribute 判断 public方法
  • 方法是static或者final也会失效

    • 声明式事务 本质是动态代理,static和final修饰的方法,无法被代理
  • 自身调用问题

  • 数据没有配置事务管理

  • 不支持事务

  • 异常捕获

  • 异常类型或者数据错误

五十、事务的本质原理

  • 事务管理类DataSourceTransactionManager本质就是一个切面类Aspect
  • 当事务发生异常没有完全执行时,DataSourceTransactionManager的异常增强对事务进行了回滚rollback。当事务完全执行完毕,DataSourceTransactionManager的后置增强对事物进行了提交commit

五十一、微服务遵循的原则

  • 单一职责原则:每个服务都有自己的具体业务职责
  • 服务自治原则:服务之间互相解耦,独立开发独立运行
  • 轻量级通讯原则:服务之前可以跨平台、轻量级通讯;例如restful风格、队列通讯等
  • 粒度进化原则:随着业务的增加,对服务进行粒度划分

五十二、IOC容器基本流程

  • 创建一些MAP结构,用于存储对象
  • 进行配置文件的读取获取注解的解析,将需要的创建的 bean都封装成BeanDefind对象存储
  • 容器将封装好的beanDefind对象通过反射进行实例化,完成对象的 实例化
  • 进行对象的初始化操作,也就是给对象的属性赋值,也就是依赖注入,完成对象的初始化创建,存储在map结构中
  • 通过容器来获取对象
  • 提供对象的销毁操作

五十三、算法复杂度(时间、空间)

  • o(1), o(n), o(logn), o(nlogn)
  • 括号中的数字和运算:数据量的增大,耗时增大的倍数
    • O(1):最优算法,无论数据的大小,耗时和空间都不变,都是直接找到当前元素
      • hash算法:不考虑hash冲突,根据hash值可以直接找到
    • o(n):当数据量增大一倍,耗时增大一倍
    • o(n^2):数据量增大n倍,耗时增大n的二次方倍
      • 经典的冒泡排序:要扫描n×n次,时间就是n的平方
    • O(logn):以2为底数的log算法
      • 当数据增大256倍:256的log值为8,即时间增加8倍;8个2相乘
    • O(nlogn):n倍的以2为底数的log算法
      • 当数据增大256倍:256的log值为8,即时间增加256*8倍

五十四、线程内部细分

  • 每一个线程都有一个本地方法栈:分为三部分
    • 代码区:存放程序代码的地方,cpu指令调用,只读
    • 静态数据区(全局):存放全局变量和静态变量
    • 动态数据区:存放本地变量

五十五、redis线程模型

  • IO模型使用多路线程复用,在Linux中是通过select/poll/epoll机制来实现的,类似netty的EventLoopGroup的线程全部设置为1
  • 完全基于内存操作,时间复杂度为O(1)
  • 单线程操作:避免了加锁、CPU线程切换
  • redis6引入多线程:主要用于网络协议和网络数据读取

五十六、redis除了做缓存,还可以应用于哪些方面

  • 排行榜,redis提供了zset数据类型,zset可以根据value自动按照顺序排序
  • 地理位置:redis提供了geospatial,可以实现部分地理定位问题
  • 计数器
  • 分布式锁:单线程的特质,保证只允许一个线程占用所
    • 通过nx控制只有key不存在的前提下才会设置
    • 通过ex设置过期时间
  • 分布式session:@EnableRedisHttpSession(maxInactiveIntervalInSeconds = AppConstants.API_TOKEN_TIMEOUT)

五十七、zookeeper如何实现的分布式锁

多个jvm同时在zk上创建临时节点 例如:root/node,假设返回的结果为nodeId,所有节点中nodeId最小的相当于获取到了锁,其他的节点从全部的节点中获取比自己小的一个节点,挂其自身线程并监控此节点,当此节点完成事务释放锁并删除自己的nodeId后,会激活监听的节点获取锁进入运行状态

五十八、说一下你对分布式理解是什么样的

  • 分布式服务架构设计初衷是为了保证五个特点:高并发、高可用、高性能、可伸缩、可维护
  • 水平切分 :同一个服务部署在不同服务器上
    • 由一个统一的注册中心管理通过路由网关对外提供统一接口
    • 可以任意伸缩
    • 提高系统处理能力
    • 避免单节点服务宕机风险
  • 垂直切分:将同一个服务根据不同业务拆分成多个服务,减少服务qps
    • 系统解耦,业务独立,便于管理
    • 服务之间互相关联,需要分布式事务以及服务不可用处理
    • 需要堆数据的准确性和一致性进行处理
  • 分布式系统的设计原则
    • cap原则
      • 一致性:保证各节点的数据一致
      • 可用性:保证各个节点都能处理请求
      • 分区容错性:服务器与服务器之间可能存在的网络不同步的问题
        • 服务器之间的网络问题,会导致一致性和可用性不能共存
    • base原则
      • 基本可用:服务节点存在不可预估状态,但是依然可以使用,存在性能和功能上的缺失
      • 软状态:允许各个节点存在数据延迟
      • 最终一致性:保证各节点

五十九、缓存与数据库双写时的数据一致性

  • 先写入到DB,可以使用canal 订阅binlog日志,监听是否写入成功,然后将删除缓存的操作写入到MQ中,消费MQ的数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值