java面试题含答案

1、Redis操作原子性怎么实现

1、 平常如果想要redis原子性操作的话,可以使用incrBy ()和decrBy ()方法进行原子性的加减,但是对于事务性的逻辑操作,没有办法实现原子性,Redis 使用单个 Lua 解释器去运行所有脚本。
2、把多个操作写到一个 Lua 脚本中,以原子性方式执行单个 Lua 脚本。Redis 会把整个 Lua 脚本作为一个整体执行,在执行的过程中不会被其他命令打断,从而保证了 Lua 脚本中操作的原子性。

2、JVM运行时数据区

在这里插入图片描述
程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
Java虚拟机栈描述的是Java方法执行的内存模型,存储局部变量表、操作数栈、动态链接、方法出口
本地方法栈与虚拟机的作用是相似的
Java堆存放对象实例
方法区用于存储已被虚拟机加载的类信息、常量、静态变量

3、垃圾回收机制

回收对象确定
确定回收对象有两个算法:引用计数法与可达性分析法。

标记-清除算法
首先标记需要回收的对象,然后进行回收;缺点:回收速度慢,回收之后产后生大量不连续的内存碎片,后期运行过程中需要分配较大对象时无法找到足够的连续内存而造成内存空间浪费。

复制算法

将内存空间等分为两份,每次只使用其中一份,当满了之后将还有效的对象复制到另一份内存中,然后把原来的空间进行清除,不会产生内存碎片,但是可用内存空间减半。

标记-整理算法

不仅对需要回收的对象进行整理,还对有效对象进行整理,不会产生内存碎片。

分代收集算法

新生代:目的是回收那些生命周期短的对象,主要存放新产生的对象。新生代按照8:1:1分为eden区、survivor0、survivor1,大部分对象在eden区中生成,当eden满时,将存活的对象复制到survivor0,然后清空eden,当eden、survivor0都满了时,将这两个区中存活的对象复制到survivor1,然后清空eden、survivor0,当着三个区都满了时则把存货对象复制到老年代,如果老年代也满了则触发FullGC。新生代的全回收叫MinorGC,MinorGC发生频率比较高,不一定等到新生代满了时才进行。

老年代:存放对象生命周期较长,且内存大概是新生代的两倍,老年代存活对象生命周期长,因此MajorGC发生频率较低。

永久代:主要存放静态文件,如Java类,方法等。

4、Synchronized原理

修饰实例方法,相当于对当前实例对象this加锁,this作为对象监视器。

修饰静态方法,相当于对当前类的Class对象加锁,当前类的Class对象作为对象监视器。
修饰代码块
指定加锁对象,对给定对象加锁,括号括起来的对象就是对象监视器。
修饰代码块原理

1、monitorenter,如果当前monitor的进入数为0时,线程就会进入monitor,并且把进入数+1,那么该线程就是monitor的拥有者(owner)。

2、如果该线程已经是monitor的拥有者,又重新进入,就会把进入数再次+1。也就是可重入的。

3、monitorexit,执行monitorexit的线程必须是monitor的拥有者,指令执行后,monitor的进入数减1,如果减1后进入数为0,则该线程会退出monitor。其他被阻塞的线程就可以尝试去获取monitor的所有权。
修饰方法原理
多了一个标志位ACC_SYNCHRONIZED,作用就是一旦执行到这个方法时,就会先判断是否有标志位,如果有这个标志位,就会先尝试获取monitor,获取成功才能执行方法,方法执行完成后再释放monitor。在方法执行期间,其他线程都无法获取同一个monitor。归根结底还是对monitor对象的争夺,只是同步方法是一种隐式的方式来实现。

5、锁优化

减少锁持有时间,只需要在有线程安全要求的程序代码上加锁。
减小锁粒度,将大对象(这个对象可能会被很多线程访问),拆成小对象
锁分离,根据功能进行分离成读锁和写锁,这样读读不互斥,读写互斥,写写互斥
锁粗化,整合成一次锁请求
锁消除,在一些不会有线程安全的情况下使用这些类的方法时,达到某些条件时,编译器会将锁消除来提高性能。

6、自旋锁原理

自旋锁的定义:当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。这种采用循环加锁 -> 等待的机制被称为自旋锁(spinlock)。
原理:如果持有锁的线程能在短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞状态,它们只需要等一等(自旋),等到持有锁的线程释放锁之后即可获取,这样就避免了用户进程和内核切换的消耗。

7、volatile原理

如果一个变量被volatile所修饰的话,在每次数据变化之后,其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个volatile在并发编程中,其值在多个缓存中是可见的。
volatile关键字通过“内存屏障”来防止指令被重排序。
ReentrantLock主要用到unsafe的CAS和park两个功能实现锁(CAS + park )

8、ReentrantLock实现

多个线程同时操作一个数N,使用原子(CAS)操作,原子操作能保证同一时间只能被一个线程修改,而修改数N成功后,返回true,其他线程修改失败,返回false,
这个原子操作可以定义线程是否拿到锁,返回true代表获取锁,返回false代表为没有拿到锁。

拿到锁的线程,自然是继续执行后续逻辑代码,而没有拿到锁的线程,则调用park,将线程(自己)阻塞。

线程阻塞需要其他线程唤醒,ReentrantLock中用到了链表用于存放等待或者阻塞的线程,每次线程阻塞,先将自己的线程信息放入链表尾部,再阻塞自己;之后需要拿到锁的线程,在调用unlock 释放锁时,从链表中获取阻塞线程,调用unpark 唤醒指定线程
ReentrantLock的可重入功能基于AQS的同步状态:state。

其原理大致为:当某一线程获取锁后,将state值+1,并记录下当前持有锁的线程,再有线程来获取锁时,判断这个线程与持有锁的线程是否是同一个线程,如果是,将state值再+1,如果不是,阻塞线程。 当线程释放锁时,将state值-1,当state值减为0时,表示当前线程彻底释放了锁,然后将记录当前持有锁的线程的那个字段设置为null,并唤醒其他线程,使其重新竞争锁。

9、事务并发问题

脏读、 不可重复读和 幻读
数据库的四个隔离级别
读未提交(Read Uncommitted):一个事务还没提交时,它做的变更就能被别的事务看到。解决更新丢失问题。如果一个事务已经开始写操作,那么其他事务则不允许同时进行写操作,但允许其他事务读此行数据。

读已提交(Read Committed):一个事务提交之后,它做的变更才会被其他事务看到。解决了脏读。读取数据的事务允许其他事务继续访问(访问指读和写)该行数据,但是未提交的写事务将会禁止其他事务访问该行。

可重复读取(Repeatable Read):可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。解决了不可重复读取和脏读取,但是有时可能出现幻读数据。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。Mysql默认使用该隔离级别。

串行化(Serializable):它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。

10、分库分表之后,id 主键如何处理

1、数据库自增 id

这个就是说你的系统里每次得到一个 id,都是往一个库的一个表里插入一条没什么业务含义的数据,然后获取一个数据库自增的一个 id。拿到这个 id 之后再往对应的分库分表里去写入。
2、UUID

好处就是本地生成,不要基于数据库来了;不好之处就是,UUID 太长了,作为主键性能太差了
3、获取系统当前时间
4、snowflake 算法

11、类加载器

类加载器负责加载 Java 类的字节代码到 Java 虚拟机中
类加载器主要步骤分为以下3步:

1.装载。(根据查找路径找到相对应的calss文件,然后导入。)

2.链接 (链接又分为3个小步骤:1。检查:检查待加载的class文件的正确性。2。准备:给类中的静态变量分配存储空间。3。解析:将符号引用转换成直接引用.)

3.初始化。(对静态变量和静态代码块执行初始化工作。)

引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自java.lang.ClassLoader。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类

12、类加载机制

Java中的类加载机制指虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用、卸载七个阶段。

13、对象的创建过程

我们在使用一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化。

14、B+树

每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值