离职原因
对语言的要求
java内存空间
jdk1.7:运行时每个进程有自己独立的一段存储区域。在这片区域里包括了各个线程共享的堆和方法区,还有每个线程独立的程序计数器、本地方法栈和虚拟机栈(虚拟机栈由栈帧组成,每个栈帧中包括局部变量表、操作数栈、动态链接、方法返回地址,栈帧随着方法调用创建,随着方法结束消亡),线程独立的区域随着线程创建而创建,随着线程消亡而消亡。另外就是本地内存中存在一块被频繁使用的区域称为直接内存,如果直接内存被频繁使用也会导致OOM。另外存在字符串常量池在堆里、运行时常量池在方法区里,hotspot虚拟机在该版本对方法区的具体实现是堆的永久代。
jdk1.8:取消了方法区,线程共享区域只有堆,字符串常量池还是在堆里面。本地内存中新增一块区域元空间,运行时常量池挪入到元空间中。
为什么取消方法区?
整个永久代有一个 JVM 本身设置的固定大小上限,无法进行调整,而元空间使用的是本地内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
堆里放什么
几乎所有的对象实例以及数组都在这里分配内存。jdk1.8之前堆分成新生代(Eden、S0、S1)、老生代、永久代。jdk1.8,永久代被元空间取代。
方法区里放什么
当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区里是运行时常量池,包括已被虚拟机加载的 类信息(类、其中的字段、方法)、常量、静态变量、即时编译器编译后的代码缓存等数据。
即时编译器:JVM在执行时,首先会逐条读取IR的指令来执行,这个过程就是解释执行的过程。当某一方法调用次数达到即时编译定义的阈值时,就会触发即时编译,这时即时编译器会将IR进行优化,并生成这个方法的机器码,后面再调用这个方法,就会直接调用机器码执行,这个就是编译执行的过程
对象创建的过程
1.类加载检查
虚拟机遇到一条new指令时,首先会去检查这个指令的参数是否能在运行时常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否被加载、解析、初始化过,如果没有需要先执行类加载过程。
2.分配内存
分配方式有两种,指针碰撞和空闲列表。
指针碰撞适用于内存规整、没有碎片的情况下,将用过的内存全部整合到一边,没有用过的内存放到另一边,中间有个分界指针,只需向着没用过的内存方向将该指针移动对象内存大小位置即可。
空闲列表适用于堆内存不规整的情况,虚拟机维护一个列表记录哪些内存块是可用的。
分配内存时保证线程安全的方式:CAS+失败重试,TLAB(为每个线程预先在Eden区分配一块内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB剩余内存或者TLAB内存用尽,再采用CAS进行内存分配)
3.初始化零值
4.设置对象头
这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
5.执行初始化方法
类加载过程
垃圾回收算法,介绍、缺点,怎么根据分代选择
标记清除/标记清除(老年代)、复制(新生代)
怎么确定对象是要被回收的,GCRoots对象可以选择哪些?
当一个对象到GC Roots没有任何的引用链相连时(从GC Roots)到这个对象不可达)时,证明此对象不可用。
GC Roots可以选择 虚拟机栈、本地方法栈中引用的对象、类的静态属性引用的对象、常量引用的对象。
堆里面对象生存空间的变化过程?进入老年代的阈值如何选择
大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 S0 或者 S1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),此后每次GC年龄都会加一,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold
来设置。
Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的一半时,取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值”
分配担保机制之后full GC了,再分配对象对象放在哪?
分配担保机制是为了解决“如果每次Young GC都有大量对象存活下来,导致S区放不下了,需要转移到老年代,但是老年代也放不下了”这种情况而设计的
画了一个流程图
在每次young GC之前,JVM都会检查老年代最大可用连续空间是否大于新生代所有对象的总和,如果大于,可以进行young GC,如果小于,要检查-XX:HandlePromotionFailure参数是否允许担保失败。
如果允许担保失败,取之前每一次回收晋升到老年代对象容量的平均值大小作为经验值,与老年代的剩余空间进行比较,如果晋升平均大小大于老年代剩余容量则进行Full GC。如果晋升平均大小小于老年代剩余容量,可以放心进行Young GC。
1.在允许担保失败的情况下,如果进行Young GC会有三种情况:1)Young GC存活对象大小小于s区区域大小,存活对象进入s区。2)Young GC存活对象大小大于s区区域大小但小于老年代可用空间大小,存活对象直接进入老年代。3)如果老年代也放不下,分配担保就失败了,进行Full GC,如果full GC后老年代还是没有足够空间,就会OOM。
2.如果不允许担保失败,直接进行full GC。
对垃圾回收器的了解
serial收集器
parNew收集器
Parallel收集器更关注系统的吞吐量,可以通过参数来打开自适应调节策略。
吞吐量:CPU用于运行用户代码的时间与CPU消耗的总时间的比值。
吞吐量 = (执行用户代码时间)/(执行用户代码时间+垃圾回收占用时间)
CMS收集器
Concurrent Mark Sweep,采用标记-清除算法,用于老年代,常与ParNew协同工作。优点在于并发收集与低停顿。
CMS垃圾回收器的步骤(GC发生在这里面哪个阶段)
(1)初始标记
标记老年代中所有的GC Roots对象和年轻代中活着的对象引用到的老年代的对象,时间短;
(2)并发标记
从“初始标记”阶段标记的对象开始找出所有存活的对象;
(3)重新标记
用来处理前一个阶段因为引用关系改变导致没有标记到的存活对象,时间短;
(4)并发清理
清除那些没有标记的对象并且回收空间。
对G1垃圾回收器的理解
G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一,早在JDK7就已加入JVM的收集器大家庭中,成为HotSpot重点发展的垃圾回收技术。
优势:并行(多核CPU)与并发;
分代收集(新生代和老年代区分不明显);
空间整合;
限制收集范围,可预测的停顿。
步骤:初始标记、并发标记、最终标记和筛选回收。
项目用的jdk版本,项目选用的垃圾回收器
JVM参数的了解
jdk自带的监控JVM工具使用过哪些
Sun公司自带了许多虚拟机工具,在bin目录下,其exe文件所依赖的源码在tools.jar包下,利用jar包中的文件可自己开发。
1.jps
Java process status
可查看本地虚拟机唯一id lvmid (local virtual machine id)
2.Jstat
监视虚拟机运行时的状态信息,包括监视类装载、内存、垃圾回收、jit编译信息。官方文档有操作命令:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
3.jinfo实时查看和调整虚拟机的各项参数
4.jmap是一个多功能的命令。它可以生成 java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。
可实现与-XX:+HeapDumpOnOutOfMemoryError相同的效果
5.jstack生成线程快照,定位线程长时间停顿的原因,命令格式为:jstack [option] vmid
6.JConsole是一种基于JMX的可视化监视、管理工具可进行内存管理、线程管理、查看死锁等。
7.VisuaIVM
VisuaIVM(All-in-One Java Troubleshooting Tool)是一款免费的,集成了多个 JDK 命令行工具的可视化工具,它提供强大的分析能力,对 Java 应用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作。是目前为止功能最强大的运行监测和故障处理工具。
线程池流程 怎么保证核心线程不被销毁(和阻塞队列有关)
线上事故排查流程(讲了处理OOM流程)这种处理方法会有线程安全问题吗 回答是单实例的 单实例也会有线程安全问题。如果没有就创建,如果有就取出来,业务上限制了每个集群同时只能进行一个集群级操作,所以在进行集群级操作之前也会检查缓存里是否已经有在进行的操作了,且因为服务是单实例,所以写入不会有并发操作,不会有线程安全问题。
项目用到的技术 Spring boot Spring cloud等 介绍项目中的一个 用到了哪些技术
Spring boot自动装配如何实现
Spring cloud和Spring boot的差别
对DDD的理解
DDD是领域驱动设计,意味着我们在设计软件时,先从业务出发,理解真实的业务含义,将业务中的一些概念吸收到软件建模中来,避免造出“大而无用”软件。也避免软件设计没有内在联系,否则一团散沙,无法继续演进。
redis怎么进行缓存和数据库的同步
缓存适用于读多写少的场景。
(1)先删缓存,再更新库
线程1把缓存删了,在修改库的过程中,线程2把没修改的库里的数据重新放回缓存,这次变更丢失了,缓存和数据库出现了不一致。
(2)先更新库,再删缓存
线程1在更新库呢,线程2会读到原缓存,然后线程1更新成功了删除了缓存,线程2读到了无效的缓存(影响不大)。另外一个影响是,线程1删掉了缓存,以后的线程来读时因为缓存不在,会回原,如果此时mysql主从不一致,回原得到了错误数据并写入到缓存里,再之后的线程都只能从缓存中读到错误的数据了,可以说缓存放大了数据库主从不一致的问题。
(3)先更新库,再改缓存(第2好)
如果两个线程都要改数据库中的同一条数据并更改缓存(第一个获得锁,更新,改缓存,第二个随后获得锁,更新,也要改缓存,但是两个同时要改缓存),缓存里的数据就会和数据库不一致。
(解决方法:用额外的线程更新缓存,比如用一个线程监听binlog,保证更改缓存顺序和更改数据库顺序一样)
缓存如果过期了,就会和(2)一样出现缓存里一直是错误的问题。(设置缓存永不失效)
(4)延迟双删(第1好)
先更新DB,再删缓存,过段时间再删缓存。相当于(2)的改进版。
先删缓存,再更新DB,再过段时间删缓存,也可以。
(5)先删缓存,再更DB,再删缓存,再延迟删缓存。(高一致性,但没必要,问题比较少)
反问:业务、组织架构、技术栈、初中高比例