1、java内存模型?
1、程序计数器;是一块较小的内存的空间,它作用可以看作是当前线程所执行字节码的行号指标器。如果线程正在执行的是一个java方法,这个计数器记录的是正在执行虚拟机字节码指令地址,如果正在执行的是Natvice方法(非java代码实现的方法)这个计数器值则为空。
2、java虚拟机栈;与程序计数器,java虚拟机栈也是线程私有的,它的生命周期与线程相同。每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量,操作栈,动态链接,方法出口等信息。
3、本地方法栈;与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的native方法服务的。
4、方法区(元数据区 MateSpace);与java堆一样是各个线程共享的内存区域,它用于已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
5、运行时常量池;是方法区的一部分,用于存放编译其生成的各种字面量和符号的引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
6、直接内存;并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,在JDK1.4加入NIO,引入了一种基于通道与缓冲区的I/O方式,它可以使用native函数库直接分配堆外内存。
7、堆(heap);对于大多数应用来说,java堆是java虚拟机所管理的内存中最大的一块,java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。java堆是垃圾收集器管理 的主要区域,堆分成“新生代”与“老年代”,默认内存比例是1:2
7.1、新生代;新生代分为三个区域,一个Eden区和Survivor区,它们之间的比例为8:1:1,这个比例是可以修改的。通常情况下,对象主要分配在新生代的Eden区上,少数情况下也可能会直接分配在老年代中。java虚拟机每次使用新生代的Eden和其中一块Survivor(from),在经过一次minorGC后,将Eden和Survivor中还存活的对象一次性复制到另一块Survivor空间上(复制算法进GC),最后清理掉Eden和刚才用过的Survivor(From)空间。将此时在Survivor存活下来的对象的年龄设置为1,以后这些对象每在Survivor区熬过一次GC,它们的年龄就加1,当对象年龄达到某个年龄(默认15)时,就会把它们移到老年代中。在新生代中进行GC,有可能遇到另外一块Survivor空间没有足够空间存放上一次新生代收集下来的 存活对象,这些对象将直接通过分配担保机制进入老年代
7.2、老年代;老年代里存放的都是存活时间较久,大小较大的对象 ,因此老年代使用标记整理算法。当老年代容量满的时候,会触发一次MajorGC(Full GC),回收新生代与老年代不再被使用的对象资源。
二、java类加载的全过程是怎样的?什么是双亲委派机制?有什么作用?一个对象从加载到JVM再被GC清除都经历什么过程?
java默认的三种类加载器:
AppClassLoader:加载来自在命令java中的classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的JAR类包和类路径
ExtClassLoader:扩展类加载器,主要负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有jar包或者由java.ext.dirs系统属性指定的jar包
BootStrap ClassLoader:称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等
每个类加载吕对加载过的类,都有一个缓存的。
AppClassLoader->ExtClassLoader->BootStrap ClassLoader(由下到上)
双亲委派机制:向上委托查找,向下委托加载。作用:保护Java底层的类不会被应用程序的类给覆盖了。
类加载过程:加载->连接->初始化
加载:把java的字节码数据加载到JVM内存中,并映射成JVM认可的数据结构
连接:分为三个小阶段:
1.验证:检查加载到的字节信息是否符合JVM规范
2:准备:创建类或接口的静态变量,并赋默认值(例如:int 0,引用对象为null)
3:初始化
类加载的全过程:
1、用户创建一个对象,JVM首先老板娘要到方法区去找对象的类型信息。然后再创建对象。
2、JVM要实例化一个对象,首先要在堆当中先创建一个对象。
3、对象首先会分配在堆内存中新生代的Eden,然后经过一次MinorGC,对象如果存活,就会进入Survivor区。在后续的每次GC中,如果对象一直存活,就会在Survivor区来加拷贝,每移动一次,年龄加1。默认年龄15,对象就会转入老年代。
4、当方法执行结束后,栈中的指标会先移除掉。
5、堆中的对象,经过Full GC ,就会被标记为垃圾,然后被GC线程清理掉。
三、怎么确定一个对象到底是不是垃圾,什么是GC Root?
1、引用计数:这种方式是给堆内存当中的每个对象记录一个引用个数。引用个数为0就认为是垃圾。这是早期JDK中使用的方式。引用计数无法解决循环引用的问题。
2、根可达算法:这种方式是在内存中,从引用根对象向下一直找引用,找不到的对象就是垃圾。
哪些是GC Root?
1、当前正在被调用的方法里局部变量引用的对象,即虚拟机栈的局部变量表中引用的对象
2、方法区中静态变量引用的对象
3、方法区中常量引用的对象
4、本地方法栈Native方法引用的对象
四、JVM有哪些垃圾回收算法?
MarkSweep 标记清除算法
这个算法分为两个阶段,标记阶段:把垃圾内存标记出来,清除阶段:直接将垃圾内存回收
缺点:会产生大量的内存碎片
Copying 拷贝算法
为了解决标记清除算法的内存碎片问题,就产生了拷贝算法,拷贝算法 将内存分为大小相等的两半,每次只使用其中一半。垃圾回收时,将当前这一块存活对象全部拷贝到别一半,然后当前这一半内存就可以直接清除。
缺点:浪费空间而且他的效率与存活对象的个数有关
MarkCompack 标记压缩算法
为了解决拷贝算法的缺陷,就提出了标记压缩算法,这种算法在标记阶段和标记清除算法是一样的,但是在完成标记之后,不是直接清理垃圾内存,而是将存活对象往一端移动,然后将端边界以外的所有内存直接清除。
缺点:效率较低,移动对象的同时,如果对象被其他对象引用,还需要调整引用的地址
这三算法各有利弊,各自有各自的适合场景
五、JVM有哪些垃圾回收器?他们都是怎么工作的?什么是STW?他都发生在哪些阶段?什么是三色标记?如何解决错标和漏标的问题?为什么要设计这么多的垃圾回收器?
STM:Stop-The-World。是在垃圾回收算法执行过程当中,需要将JVM内存冻结的一种状态。在STW状态下,JAVA的所有线程都是执行的(GC线程除外)
垃圾回收器:
Serial(串行):
需要GC时,直接暂停所有线程,GC完了再继续运行。
这个垃圾回收器,是早期垃圾回收器,只有一个线程执行GC。在多CPU架构下,性能就会下降严重,只适用于几十M的内存。
Parallel(并行):
在串行基础上(在STW时,开启多个线程进行垃圾回收),增加多线程GC。PS+PO这种组合是JDK1.8默认的垃圾回收器。在多CPU的架构下,性能会比Serial高很多。
CMS(Concurrent Mark Sweep);并发标记清除
核心思想,就是将STW打散,让一部分GC线程与用户线程并发执行。
1、初始标记阶段:STW只标记出根对象直接引用的对象。
2、并发标记阶段:继续标记其他对象,与应用程序是并发执行的。
3、重新标记:STW对并发执行阶段的对象进行重新标记。
4、并发清除:并行,将产生的垃圾清除,清除过程中,应用程序又会产生新的垃圾,叫做浮动垃圾。这些浮动垃圾就要留到下一次GC过程中清除
G1 (Garbage First)垃圾优先:
他的内存模型是实际不分代,但是逻辑上是分代的。在内存模型中,对于堆内存就不再区分新生代与老年代,而是划分成一个一个的小内存块,叫做Region。每Region可以隶属于不同的年代。
GC分为四个阶段:
1、初始标记:标记出GC Root直接引用的对象(STW)
2、标记Region,通过RSet标记出上一个阶段的Region引用到Old区Region
3、并发标记阶段:跟CMS的步骤是差不多的,只是遍历范围不再是整个Old区,而只需要遍历第二步标记出来的Region。
4、重新标记:STW对并发执行阶段的对象进行重新标记。
5、垃圾清理:与CMS不同的是G1采用拷贝算法,直接将整个Region中对象拷贝到别一个Region。而这个阶段,G1只选择垃圾较多的Region来清理,并不是完全清理。
三色标记:三色标记是一种逻辑上的抽象。将每个内存对象分成三种颜色:黑色:表示自己和成员变量都已经标记完毕。灰色:自己标记完毕了,但是成员变量还没有完全标记完。白色:自己没有被标记。
CMS通过增量标记(increment update)的方式来解决漏标的问题,G1通过SATB(快照方式)来解决漏标的问题
六、如何进行JVM调优?JVM参数有哪些?怎么查看一个JAVA进程的JVM参数?谈谈你了解的JVM参数?
JVM调优主要就是通过定制JVM运行参数来提高JAVA应用程度的运行数据
JVM参数大致可以分成三类:
1、标准指令:- 开头,这些是所有HotSpot都支持的参数。可以用java -help打印出来。
2、非标准指令:-X 开头,这些指令通常是跟特定HotSpot版本对应的,可以用java -X打印出来。
3、不稳定参数:-XX 开头,这一类参数是跟特定HotSpot版本对应的,并且变化非常大。详细的文档资料非常少。在JDK1.8版本下,有几个常用的不稳定指令:
java -XX:+PrintCommanLineFlags :查看当前命令的不稳定指令。
java -XX:printFlagsInitial:查看所有不稳定指令的默认值
java -XX:printFlagsFinal:查看所有不稳定指令最终生效的实际值
七、MQ有什么用?有哪些具体的使用场景?
MQ:MessageQueue,消息队列。队列是一种FIFO先进先出的数据结构。消息由生产者发送到MQ进行排队,然后由消费者对消息进行处理。
MQ的作用主要有三个方面:
1、异步:异步能提高系统的响应速度和吞吐量(更新ES场景,注册短信,短信验证码)
2、解耦:服务之间进行解耦,可以减少服务之间的影响,提高系统的稳定性和可扩展性。另外,解耦之间可以实现数据分发。生产者发送一个消息后,可以由多个消费者来修理。(下订单操作)
3、削峰:以稳定的系统资源应对突发的流量冲击。(秒杀活动场景)
MQ的缺点:
1、系统可用性降低:一旦MQ宕机,整个业务就会产生影响。
2、系统复杂度提高:引入MQ之后,数据链路就会变得很复杂。如何保证消息不丢失?消息不会重复调用?
3、数据一致性:A系统发消息,需要B。C两个系统一同处理。如果B系统处理成功、C系统处理失败,这就会造成数据一致性的问题。
八、如何对MQ进行产品造型?
kafka:
优点:吞吐量非常大,性能非常好,集群高可用、
缺点:会丢数据,功能比较单一。
使用场景:日志分析、大数据采集
RabbitMQ:
优点:消息可靠性高,功能全面
缺点:吞吐量比较低,消息积累全严重影响性能。erlang语言不好定制。
使用场景:小规模场景。
RocketMQ:
优点:高吞吐、高性能、高可用、功能非常全面
缺点:开源版功能不如云上商业版,官方文档和周边生态还不够成熟,客户端只支持java
使用场景:几乎是全场景
九、如何保证消息不丢失?
可能丢失消息的位置:
1)、生产者发送消息不丢失
kafka: 消息发送+回调
RocketMQ:1、消息发送+回调。2、事务消息。
RabbitMQ:1消息发送+回调
2、手动事务:channel.txSelect()开启事务,channel.txCommit()提交事务,channel.txRollback()回滚事务。这种方式对channel是会阻塞的,造成吞吐量下降。
3、新版本的RabbitMQ提出了扩展(PUblisher Confirm),整个处理流程跟RocketMq的事务消息,基本是一样的。
2)、MQ主从消息同步不丢失
RocketMQ:1、普通集群中,同步同步、异步同步。异步同步效率更高,但是有丢消息的风险。同步同步就不会丢消息。
2、Dledger集群-两阶段提交:
RabbitMQ:普通集群:消息是分散存储的,节点之间不会主动进行消息同步,是有可能丢失消息的。
镜像集群:镜像集群会在节点之间主动进行数据同步,这样数据安全性得到提高
kafka:通常都是用在允许消息少量丢失的场景
3)、MQ消息存盘不丢失
RocketMQ:同步刷盘 异步刷盘:异步刷盘效率更高,但是有可能丢消息。同步刷盘消息安全性更高,但是效率会降低
RabbitMQ:将队列配置成持久化队列。新增的Quorum类型的队列,会采用Raft协议来进行消息同步
4)、MQ消费者消费不丢失
RocketMQ:使用默认的方式消费就行,不要采用异步方式
RabbitMQ:autoCommit->手动提交offset
kafka:手动提交offset
九、如何保证消息消费的幂等性?
其实就是消费者重复消费消息的问题
所有的MQ产品并没有提供主动解决幂等性的机制,需要由消费者自行控制
最好的方式就是自己带一个有业务标识的ID,来进行幂等判断,如OrderID
十、如何保证消息的顺序?
全局有序和局部有序:MQ只需要保证局部有序,不需要保证全局有序。
生产者把一组有序的消息放到同一个队列当中,而消费者一次消费整个队列当中的消息
RocketMQ中有完整的设计,但有RabbitMQ和Kafka当中,并没有完整的设计,需要自己进行设计。
RabbitMq:要保证目标exchange只对应一个队列,并且一个队列只对应一个消费者
Kafka:生产者通过定制partition分配规则,将消息分配到同一个partition。Topic只对应一个消费者
十一、如何保证消息的高效读写?
零拷贝:Kafka和RocketMQ都是通过零拷贝技术来优化文件读写的。
传统文件复制方式:需要对文件在内存中进行四次拷贝。
java当中对零拷贝进行了封装,Mmap方式通过MapperByteBuffer对象进行操作,而transfile通过FileChannel来进行操作。
Mmap适合比较小的文件,通宵文件大小不要超过2G。
transFile没有文件大小限制
RocketMq当中使用Mmap方式对他的文件进行读写,如commitLog.
在Kafka当中,他的index日志文件也是通过mmap的方式来读写的,在其他日志文件当中,并没有使用零拷贝的方式。
kafka使用transfile方式将硬盘数据加载到网卡
十二、使用MQ如何保证分布式事务的最终一致性?
MQ中要保证事务的最终一致性,就需要做到两点:
1、生产者要保证100%的消息投递。事务消息机制
2、消费者这一端需要保证幂等消费。唯一ID+业务自己实现幂等