目录
6.对象创建过程(类加载机制,双亲委派模型及作用,对象头的内容,从对象头的角度讲对象锁是怎么实现的)?
1.负载均衡算法?
随机算法,加权轮询,一致性hash,最小活跃数算法
-
随机算法:三种,不带权重的适合各服务器性能相似的场景;带权重的有两种实现,一种是将服务器ip按照权重比例放倒一个list里面随机抽取,另一种是按权重划分区间(将每个区间分配给各个服务器);
-
轮询算法:三种,不带权重的简单轮询算法;带权重的有两种:一种是按照权重比例将ip到数组中轮询(缺点是局部不够均衡),另一种加加权轮询算法是平滑加权轮询算法。
-
平滑加权轮询算法:每个服务器对应两个权重,分别为weight和currentWeight。其中weight是固定的,currentWeight会动态调整,初始值为0;
-
当有新的请求进来时,遍历服务器列表,让它的currentWeight加上weight(初始化后就一直不变)得到自身权重,遍历完成后,找到最大的currentWeight;
-
并将最大的currentWeight减去权重总和,然后返回相应的服务器即可。
-
-
一致性hash算法:
服务器集群接收到一次请求调用时,可以根据请求的信息,比如客户端的ip地址,或请求路径与参数等信息进行哈希,可以得出一个哈希值,特点是对于相同的ip地址,或请求路径和请求参数哈希出来的值是一样,只要能再增加一个算法,能够把这个哈希值映射成一个服务端ip的地址,就可以使相同的请求落到同一服务器上。如果有节点宕机,该算法会不均衡,需要加入虚拟节点(不存在,只是用来分配给其他节点)来让hash环更加均衡。
-
最小活跃数算法
活跃调用数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求。此时应优先将请求分配给该服务提供者。在具体实现中,每个服务提供者对应一个活跃数。初始情况下,所有服务提供者活跃数均为0。每收到一个请求,活跃数加1,完成请求后则将活跃数减1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求、这就是最小活跃数负载均衡算法的基本思想。
图片内容参考:负载均衡算法 - 掘金
2.G1垃圾回收器的原理和工作流程?
【介绍】G1(Garbage First)是一款并发的垃圾回收器,在JDK9中被JVM设置为默认的垃圾回收器。G1采用分区算法,基于Region的内存布局方式,对整个堆内存进行局部回收。既能回收新生代,也能回收老年代。G1垃圾回收器的目标是在期望的停顿时间内,尽可能的提升系统的吞吐量。
【原理】G1依然遵循分代收集理论,但是G1不再坚持固定大小、固定数量的分代区域划分,而是将整个内存区域划分为若干个大小相等的Region,每个Region都能扮演Eden、Survivor、Old区。新生代和老年代的内存在物理上不再连续,而是逻辑上处于连续。在 G1 中,新增了一个 H 区的概念,如果一个对象的大小超过了一个 Region 的 50%,那么该对象就会被直接存放进 H 区。如果一个 Region 无法存放下对象,那么就会采用连续的多个 Region 来存放该超大对象。
G1可以通过参数设置最大停顿时间,在系统运行过程中,G1会手机每个Region的回收耗时、垃圾占比等各个可测量的信息,然后计算回收每个Region带来的收益大小(可回收的内存+回收耗时),通过维护一个优先级列表,然后在设置的最大停顿时间内,回收哪些能带来最大收益的Region。
虽然 G1 为我们提供了最大停顿时间这个参数,但是我们也不能异想天开的认为,这个参数设置得越小越好。如果设置得太小,那么会因为每次 GC 可以停顿的时间太少,导致每次 GC 只能回收极少的 Region,如果此时内存的分配速度大于 Region 回收的速度,那么在系统初期,可能会因为还有空闲内存可以支撑一段时间,但是时间一长,就会导致空闲内存越来越少,最终触发 Full GC,从而导致系统停顿时间更长。
【使用】每个Region的大小可以通过参数【-XX:G1HeapRegionSize】设置,取值范围为1MB-32MB,且为2的整数次幂。通常情况下,G1会将堆内存划分为2048个Region,如果我们指定堆内存的大小为4G,那么每个Region的大小为2MB。
【工作流程】
-
初始标记:仅仅只是标记出 GC Roots 直接关联的对象(此时当前 Region 中的记忆集也会被当做是 GC Roots),并且还会修改 TAMS 指针,让下一阶段用户线程并发执行时,能够正确的在可用的 Region 中分配新对象。这一步会造成 STW,但是由于只标记和 GC Roots 直接相连的对象,所以暂停时间很短,具体暂停多长时间,和 GC Roots 的数量有关。另外由于该阶段是借用进行 Minor GC 时同步完成的,因此不会额外造成停顿。
-
并发标记:从上一步标记出的对象出发,遍历整个对象图,这一步耗时较长,但是由于是和用户线程并发执行,因此不会造成 STW。
-
最终标记:由于在并发标记阶段,垃圾回收线程和用户线程并发执行,因此在这一过程中,可能会由于用户线程改变了对象的引用关系,造成对象”消失“,因此还需要重新处理 SATB(原始快照)记录下在并发阶段有引用关系改动的对象,这一过程就是在最终标记阶段完成的,会造成 STW,否则如果用户线程还一直进行,就会不停地造成对象引用关系的改变,我们就得不停的处理 SATB 记录。虽然会造成 STW,但毕竟 SATB 记录的引用改变的对象不会特别多,因此耗时比并发标记阶段的耗时会少很多。在这一步中,如果发现当前 Region 中的所有对象都是垃圾对象,那么就会立即对当前 Region 进行回收。
-
筛选回收:负责更新 Region 的统计数据,根据每个 Region 的回收价值和成本进行排序,然后根据用户期望停顿的时间内来指定回收计划,可以选择多个 Region 构成回收集,然后采用复制算法,将 Region 中存活的对象复制到空闲的 Region 中,从而回收 Region。
内容图片来自:JVM系列之垃圾回收器(中篇)——G1的运行原理以及调优思路 - 掘金,《深入理解Java虚拟机》
3.Java为什么又有编译器又有解释器?
java是先将.java文件编译成了.class字节码文件(使用了编译器),然后再被JVM逐行解释成机器可执行的二进制机器码(使用了解释器)
4.TCP和UDP的区别,TCP的滑动窗口?
【区别】
-
TCP是面向连接的;UDP是面向无连接的。
-
TCP提供可靠的服务,通过TCP连接传送的数据,无差错,不丢失,不重复且按序到达;UDP尽最大努力交互,不保证可靠交付。
-
UDP具有良好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
-
每一条TCP连接只能是点对点的;UDP支持一对一,一对多和多对多的交互通信。
-
TCP对系统资源要求较多,UDP对系统资源要求较少。
【滑动窗口】
TCP依赖滑动窗口进行流量控制,窗口是自适应的,影响窗口的两个因素:网络延迟和传输速率,滑动窗口的大小与延时成正比,与传输速率也成正比。在给定的网络环境下,延时可以认为是固定的,因此滑动窗口仅与传输速率有关,当传输实时数据时,因为数据流通量比较固定,所以这时TCP上的滑动窗口会处于一个不大不小的固定值,这个值大小恰好保证当前生产的数据实时传输到对方,当出现网络丢包时,按TCP协议(快速恢复),滑动窗口将减少到原来的一半,因此速率立刻减半,此时发送速率将小于数据生产速率,一些数据将滞留在发送端,然后滑动窗口将不断增大,直到积累的数据全部发送完毕。上述过程即为典型的TCP流量抖动过程,对于实时传输影响很大,可能形成较大的突发时延,从用户感观角度来说,就是有时比较流畅,但有时卡(“抖一下”,并且比较严重),因此实时传输通常不使用TCP。
5.IO多路复用,重点介绍epoll?
【定义】IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞应用程序,交出cpu。多路是指网络连接,复用指的是同一个线程。
【实现方式】select、poll、epoll
select的缺点:
-
单个进程所打开的FD是有限制的,通过FD_SETSIZE设置,默认1024
-
每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
-
对socket扫描时是线性扫描,采用轮询的方法,效率较低(高并发时)
poll的缺点同select,只是没有fd数量限制,其他基本一样
epoll的缺点:只能工作在linux下;优点:fd首次调用epoll_ctl拷贝,每次调用epoll_wait不拷贝。
【epoll LT 与 ET模式的区别】
epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。
-
LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作。
-
ET模式下,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读完,或者遇到EAGAIN错误。
6.对象创建过程(类加载机制,双亲委派模型及作用,对象头的内容,从对象头的角度讲对象锁是怎么实现的)?
【类加载过程】
-
检查类是否已经被加载
如果没有被加载,会先进行类的加载过程(加载、链接、初始化)三个阶段
-
为对象分配内存空间
对象的大小类加载完成时就确定下来了,为对象分配内存有两种方式:
-
指针碰撞:通过一个指向已占空间区域和空白未使用的空间区域的相邻的边界处的指针,给一个对象分配内存空间,就将该指针向空白区域移动相应大小空间。该分配方式要求内存是地址连续的,且虚拟机带有内存压缩机制,即在对象释放空间时后也要形成连续地址空间。问题:多线程下会出现指针不一致问题,虚拟机采用了循环CAS操作保证内存的正确划分。
-
本地线程分配缓冲(TLAB):为了解决第一种分配方式的不足而创建的方式,多线程分配内存时,虚拟机为每个线程分配了不同的空间,这样每个线程在分配内存时只是在自己的空间中操作,从而避免了上述问题,不需要同步。当然,当线程自己的空间用完了才需要需申请空间,这时候需要进行同步锁定。是否启用TLAB需要通过 -XX:+/-UseTLAB参数来设定。
-
-
为对象的字段赋默认值
分配完内存后,需要对对象的字段进行零值初始化(赋默认值),对象头除外。零值初始化意思就是对对象的字段赋0值,或者null值,这也就解释了为什么这些字段在不需要进程初始化时候就能直接使用。
-
设置对象头
对这个将要创建出来的对象,进行信息标记,包括是否为新生代/老年代,对象的哈希码,元数据信息,这些标记存放在对象头信息中。
-
执行实例的初始化方法
Init方法包含成员变量、构造代码块的初始化,按照声明的顺序执行。
-
执行构造方法
执行对象的构造方法。至此,对象创建成功。
ps:上述为无父类的对象创建过程。对于有父类的对象创建过程,还需满足如下条件:
-
先加载父类;再加载本类;
-
先执行父类的实例的初始化方法init(成员变量、构造代码块),父类的构造方法;执行本类的实例的初始化方法init(成员变量、构造代码块),本类的构造方法。
【对象】
由对象头、实例数据、对其填充三部分组成
-
对象头:Java的对象头由以下三部分组成:MarkWord、指向类的指针、数组长度(只有数组对象才有)
MarkWord包含:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)
-
实例数据:属性和属性值
-
JVM要求Java对象是8字节的整数倍,后面的几个字节用于补齐至8个字节的整数倍。
7.JVM调优?
8.SQL中on和where的区别?
on不会对数据进行过滤,比如A left join B on C,将会查询出A中所有的数据并将B中的满足C条件的数据填充到A中,而where会根据条件C进行数据过滤。
9.客户端拔掉网线后,是否会影响TCP的连接状态?
存在两种情况:
-
此时正在传输数据:传输数据时如果传输失败将会进行重传,如果在指定重传次数的时间内网络恢复,则无影响;如果重传次数超过指定次数后,或者超过了最大超时时间,网络没有恢复,则将会断开TCP。
-
此时处于空闲状态:如果没有开启了TCP keepalive机制,在客户端拔掉网线之后,连接会一直保存;如果开启了TCP keepalive机制,会通过探测报文探测对端是否处于正常工作状态,如果处于,则将保活时间设置为初始值,反之则断开连接。(在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔等)
10.Java的反射机制?
【是什么】机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键,反射让Java成为一个准动态语言。缺点就是增加了不安全性,比如private访问权限修饰符的功能被破坏。
【获取Class类】
三种方法:1.知道具体的类,直接通过类的class获取 2.知道一个类的实例,通过实例的getClass()获取 3.知道一个类的全名,通过Class类的静态方法forName()获取,可能抛出classNotFoundException异常。
public class Student {
private int id;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public static void main(String[] args) throws Exception{
//一、正射调用过程
Student student = new Student();
student.setId(1);
System.out.println("正射调用过程Student id:" + student.getId());
//二、反射调用过程
Class clz = Class.forName("com.justin.java.lang.Student");
Constructor studentConstructor = clz.getConstructor();
Object studentObj = studentConstructor.newInstance();
Method setIdMethod = clz.getMethod("setId", int.class);
setIdMethod.invoke(studentObj, 2);
Method getIdMethod = clz.getMethod("getId");
System.out.println("反射调用过程Student id:" + getIdMethod.invoke(studentObj));
}
}