来源:捡田螺的中厂小男孩 大家好,我是后端田螺。最近一位星球粉丝去面试一个中厂,连问Java后端。中厂他说,后端好几道题答不上来,连问于是中厂我帮忙整理了一波答案 G1收集器JVM内存划分对象进入老年代标志你在项目中用到的是哪种收集器,怎么调优的后端new对象的内存分布局部变量的内存分布Synchronized和Lock的区别Synchronized原理可重入是如何知道当前锁的拥有着的Spring用到的设计模式说说SPI排行榜怎么设计SpringBoot 中注解实现缓存用过没?实现原理是什么。 G1(Garbage-First)收集器是连问 Java 虚拟机中的一种垃圾收集器。它于 JDK 7 中首次引入,中厂并在后续版本中不断改进优化。后端 Java 虚拟机(JVM)的内存分为多个区域,每个区域都有不同的作用和管理方式。JVM 内存划分的主要区域: 通常情况下,老年代用于存放长期存活的对象,以减少垃圾收集的频率和提高垃圾回收的效率。 我列举了常见的垃圾收集器以及调优策略: 串行收集器(Serial Garbage Collector): 并行收集器(Parallel Garbage Collector): 并发标记-清除收集器(Concurrent Mark-Sweep Garbage Collector,CMS GC): G1 收集器(Garbage-First Garbage Collector): ZGC 和 Shenandoah(JDK 11+): 当然,调优的具体策略会根据应用的特点和需求来定,可以通过以下一些常见的调优手段来改进垃圾收集器的性能: 在 Java 中,当使用 new 关键字创建一个对象时,对象在内存中的基本结构通常由对象头、实例数据和填充组成。 栈帧结构: 局部变量表: 局部变量的存储位置: 局部变量的生命周期: synchronized是Java中的关键字,是一种同步锁。synchronized关键字可以作用于方法或者代码块。 一般面试时。可以这么回答: monitor是什么呢?操作系统的管程(monitors)是概念原理,ObjectMonitor是它的原理实现。 在Java虚拟机(HotSpot)中,Monitor(管程)是由ObjectMonitor实现的,其主要数据结构如下: () { _header = NULL; _count = 0; // 记录个数 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; 状态的线程,会被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; // 处于等待锁block状态的线程,会被加入到该列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; } ObjectMonitor中几个关键字段的含义如图所示: 比如,ReentrantLock 是可重入的锁。ReentrantLock 类实现了可重入锁的概念,允许同一个线程在持有锁的情况下多次获取同一个锁,而不会被阻塞。 在 ReentrantLock 中,每个锁都关联着一个持有计数器和一个拥有者线程。当一个线程首次获取锁时,持有计数器会增加;当该线程再次获取锁时,持有计数器会继续增加。每次释放锁时,计数器会相应减少。只有当持有计数器减为零时,锁才会完全释放,其他线程才有机会获取锁。 这样一来,当线程尝试获取锁时,它会检查当前锁的持有计数器以及拥有者线程。如果锁未被任何线程持有,或者当前线程是锁的拥有者,那么锁将立即分配给当前线程。否则,当前线程会被阻塞,直到锁被释放。 SPI,其实就是Service Provider Interface,是Java提供的一种机制,用于在运行时动态装载实现模块,使得应用程序能够扩展、替换特定的服务或实现。 SPI的工作原理主要包括以下几个关键点: 在实际应用中,我们可以利用SPI来实现插件化架构、模块化开发等,从而提高代码的可维护性和可扩展性。 可以考虑:数据库的order by、或者Redis 的zset 大家可以看下我的这篇文章哈: 如何设计一个排行榜 对于游戏排行榜,如果数据量达到亿万级别,需要考虑的确实是分桶策略,而桶排序则是分桶策略的一种可能实现方式之一。 桶排序是一种基于这种分桶策略的排序算法,它将数据划分到若干个有序的桶中,然后分别对每个桶中的数据进行排序,最后将所有桶中的数据按照顺序合并起来,得到排好序的结果。 在游戏排行榜系统中,可以根据玩家的分数范围、等级、地区等因素来设计分桶策略,将玩家数据划分到不同的桶中。这样可以使得每个桶内的数据量相对较小,提高了排序的效率。 常见的缓存注解包括 @Cacheable、@CachePut、@CacheEvict 等。这些注解的实现原理基于Spring提供的缓存抽象。 是的,Spring Boot 中的缓存注解常用于提升系统性能,减少重复计算,常见的缓存注解包括 @Cacheable、@CachePut、@CacheEvict 等。这些注解的实现原理基于 Spring Framework 提供的缓存抽象。 代理机制: 缓存管理器: 缓存注解: 缓存 key 的生成: 缓存注解的执行流程: 我们可以通过减少回表次数来优化。一般有标签记录法和延迟关联法。 就是标记一下上次查询到哪一条了,下次再来查的时候,从该条开始往下扫描。就好像看书一样,上次看到哪里了,你就折叠一下或者夹个书签,下次来看的时候,直接就翻到啦。 假设上一次记录到100000,则SQL可以修改为: 10; 这样的话,后面无论翻多少页,性能都会不错的,因为命中了id索引。但是这种方式有局限性:需要一种类似连续自增的字段。 延迟关联法,就是把条件转移到主键索引树,然后减少回表。假设原生SQL是这样的的,其中id是主键,create_time是普通索引 100000,10; 使用延迟关联法优化,如下: select acct1.id,acct1.name,acct1.balance FROM account acct1 INNER JOIN 100000, 10) AS acct2 on acct1.id= acct2.id; 优化思路就是,先通过idx_create_time二级索引树查询到满足条件的主键ID,再与原表通过主键ID内连接,这样后面直接走了主键索引了,同时也减少了回表。 redis分布式锁,我觉得可以从一下这几个方向来回答: 有关于Redis分布式锁的,大家有兴趣可以看下我之前的这篇文章哈:Redis分布式锁的10个坑前言
1. 说说G1收集器
2. JVM内存划分
堆(Heap):堆是云服务器提供商 JVM 中最大的一块内存区域,用于存储对象实例和数组。堆内存由所有线程共享,是垃圾收集器主要管理的区域。程序计数器(Program Counter):程序计数器是一块较小的内存空间,它是线程私有的,用于存储当前线程正在执行的字节码指令的地址。在多线程环境下,每个线程都有一个独立的程序计数器,以保证线程切换后能够恢复到正确的执行位置。虚拟机栈(Java Virtual Machine Stacks):虚拟机栈也是线程私有的,用于存储方法执行过程中的局部变量、方法参数、中间结果等数据。每个方法在执行时都会创建一个栈帧,栈帧包含了方法的局部变量表、操作数栈、动态链接、高防服务器方法返回地址等信息。本地方法栈(Native Method Stacks):本地方法栈与虚拟机栈类似,用于执行本地方法(Native Method)时的数据存储。与虚拟机栈一样,本地方法栈也是线程私有的。方法区(Method Area):方法区用于存储类信息、常量、静态变量和即时编译器编译后的代码等数据。在jdk7及以前,习惯上把方法区,称为永久代。jdk8开始,使用元空间取代了永久代。本质上,方法区和永久代并不等价。通过-XX:Permsize来设置永久代初始分配空间。可以使用参数 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize指定默认值依赖于平台。运行时常量池(Runtime Constant Pool):运行时常量池是方法区的一部分,用于存储编译时生成的字面量常量和符号引用。与类文件中的常量池(Constant Pool)相对应,运行时常量池具有动态性,可以在运行时动态地添加、修改常量。直接内存(Direct Memory):直接内存不是 JVM 内存的一部分,但是在一些情况下会被 JVM 使用。直接内存是通过使用 java.nio 包中的类来申请和释放的,它允许 JVM 在堆外分配内存,可以提高 I/O 操作的效率。
3. 对象进入老年代标志
对象年龄达到阈值:对象在 Java 堆中的存活时间通常会通过对象年龄(Age)来衡量。在某些垃圾收集器中,当对象经过多次垃圾收集后仍然存活,并且达到了一定的年龄阈值,就会被晋升到老年代。年龄阈值可以通过 JVM 参数进行调整。大对象:大对象通常指的是占用大量内存空间的对象,例如数组或者很大的字符串。在一些垃圾收集器中,为了避免在新生代中频繁复制大对象,会直接将大对象分配在老年代中。长期存活的对象:一些长期存活的对象,例如长期存在的线程、静态变量等,有可能直接被分配到老年代中。晋升失败:当新生代中的对象经过多次垃圾回收仍然存活,并且新生代内存不足以容纳这些存活对象时,会发生一次晋升失败,这时一部分存活对象可能会被直接晋升到老年代。
4. 你在项目中用到的是哪种收集器,怎么调优的
5. new对象的内存分布
6. 局部变量的内存分布
7.Synchronized和ReenTrantLock的区别
Synchronized是依赖于JVM实现的,而ReenTrantLock是API实现的。在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者性能就差不多了。Synchronized的使用比较方便简洁,它由编译器去保证锁的加锁和释放。而ReenTrantLock需要手工声明来加锁和释放锁,最好在finally中声明释放锁。ReentrantLock可以指定是公平锁还是⾮公平锁。⽽synchronized只能是⾮公平锁。ReentrantLock可响应中断、可轮回,而Synchronized是不可以响应中断的8.Synchronized原理
8.1 monitorenter、monitorexit、ACC_SYNCHRONIZED
如果synchronized作用于代码块,反编译可以看到两个指令:monitorenter、monitorexit,JVM使用monitorenter和monitorexit两个指令实现同步;如果作用synchronized作用于方法,反编译可以看到ACCSYNCHRONIZED标记,JVM通过在方法访问标识符(flags)中加入ACCSYNCHRONIZED来实现同步功能。同步代码块是通过monitorenter和monitorexit来实现,当线程执行到monitorenter的时候要先获得monitor锁,才能执行后面的方法。当线程执行到monitorexit的时候则要释放锁。同步方法是通过中设置ACCSYNCHRONIZED标志来实现,当线程执行有ACCSYNCHRONIZED标志的方法,需要获得monitor锁。每个对象都与一个monitor相关联,线程可以占有或者释放monitor。8.2 monitor监视器
8.3 Java Monitor 的工作机理
想要获取monitor的线程,首先会进入_EntryList队列。当某个线程获取到对象的monitor后,进入Owner区域,设置为当前线程,同时计数器count加1。如果线程调用了wait()方法,则会进入WaitSet队列。它会释放monitor锁,即将owner赋值为null,count自减1,进入WaitSet队列阻塞等待。如果其他线程调用 notify() / notifyAll() ,会唤醒WaitSet中的某个线程,该线程再次尝试获取monitor锁,成功即进入Owner区域。同步方法执行完毕了,线程退出临界区,会将monitor的owner设为null,并释放监视锁。
8.4 对象与monitor关联
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header),实例数据(Instance Data)和对象填充(Padding)。对象头主要包括两部分数据:Mark Word(标记字段)、Class Pointer(类型指针)。Mark Word 是用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。重量级锁,指向互斥量的指针。其实synchronized是重量级锁,也就是说Synchronized的对象锁,Mark Word锁标识位为10,其中指针指向的是Monitor对象的起始地址。
9.可重入是如何知道当前锁的拥有着的
10.Spring用到的设计模式
单例模式(Singleton Pattern):Spring 中的 Bean 默认是单例的,即每个 Bean 只有一个实例。这种方式可以提高性能并减少资源消耗。工厂模式(Factory Pattern):Spring 使用工厂模式来创建和管理 Bean。它提供了几种不同类型的工厂,比如 BeanFactory 和 ApplicationContext,用于创建和管理 Bean 对象。代理模式(Proxy Pattern):Spring AOP(面向切面编程)功能基于代理模式实现。它允许通过在运行时为目标对象创建代理对象来添加横切关注点(如日志记录、性能监控等)。装饰者模式(Decorator Pattern):Spring 中的 BeanPostProcessor 接口就是一个装饰器模式的例子。它允许在 Bean 初始化过程中动态地添加新的功能。观察者模式(Observer Pattern):Spring 的事件机制就是基于观察者模式实现的。它允许 Bean 发布事件,其他 Bean 可以注册监听器来响应这些事件。策略模式(Strategy Pattern):Spring 的 IOC(控制反转)和 DI(依赖注入)功能基于策略模式实现。它允许将不同的实现注入到一个接口或抽象类中,以便在运行时选择不同的行为。模板模式(Template Pattern):Spring 的 JdbcTemplate 和 HibernateTemplate 等模板类就是模板模式的应用。它们封装了一些常见的操作,使开发者可以通过简单的方法调用来执行数据库操作。适配器模式(Adapter Pattern):Spring 的 AOP 功能和 Spring MVC 框架都使用了适配器模式。它们允许将现有的类与新的接口进行适配,以便实现新的功能。建造者模式(Builder Pattern):Spring 中的 BeanDefinitionBuilder 和 BeanFactoryBuilder 等构建器模式的应用。它们用于构建复杂的对象,并且允许逐步设置对象的属性。11.聊聊SPI
12.排行榜怎么设计
13.SpringBoot 中注解实现缓存用过没?实现原理是什么。
实现原理:
14.深分页优化
14.1 标签记录法
14.2 延迟关联法
15.分布式锁如何进一步提升性能,答了Redis的实现思路好像不是面试官想听的