LOADING

加载过慢请开启缓存 浏览器默认开启

2024得物一面(一)

类加载机制

Java源程序(.java文件)在经过编译器编译之后被转换成字节代码(.class 文件),类加载器将.class文件中的二进制数据读入到内存中,将其放在方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构

类的生命周期包括:加载、验证、准备、解析、初始化、使用、卸载7个阶段。其中加载、验证、准备、初始化、卸载5个阶段是按照这种顺序按部就班的开始,而解析阶段则不一定:某些情况下,可以在初始化之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定,其实就是多态),例如子类重写父类方法。

加载:通过一个类的全限定名来获取定义此类的二进制字节流。将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。
验证:分为四步:文件格式验证、元数据验证、字节码验证、符号引用验证。如果所引用的类经过反复验证,可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备:为类的静态变量分配内存,并初始化默认值,这些内存是在方法区中分配,需要注意以下几点:1.此处内存分配的变量仅包含类变量(static),而不包括实例变量,实例变量会随着对象实例化被分配在java堆中。2.这里默认值是数据类型的默认值(如0、0L、null、false),而不是代码中被显示的赋予的值。3.如果类字段的字段属性表中存在ConstatntValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。
解析:虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。
初始化:为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。初始化阶段是执行类构造器()方法的过程。

双亲委派机制:当一个类加载器收到了类加载的请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求都应该传送到启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时(它的搜索范围中没有找到所需的类),子加载器才会尝试自己去加载。

如何打破双亲委派机制

  1. 自定义类加载器,重写loadClass方法,通过创建一个自定义的classLoader,并将其父类加载器设置为null,这样就可以打破双亲委派机制,自己去加载类。
  2. 使用线程上下文加载器,线程上下文类加载器(Context ClassLoader)是从JDK1.2开始引入的,类Thread中的getContextClassLoader()与setContextClassLoader(ClassLoader cl)分别用来获取和设置上下文类加载器。如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器。Java应用运行时的初始线程的上下文类加载器是系统类加载器,在线程中运行的代码可以通过该类加载器来加载类与资源。

AQS

AQS(AbstractQueuedSynchronizer)是Java并发编程中的一个核心抽象类,它提供了一种基于队列机制的线程同步方法。它定义了一些基本的线程同步队列操作,比如入队(enqueue)、出队(dequeue)、获取队列头(getHead)和获取队列尾(getTail)等操作。

AQS常用于实现一些基于锁或者其他同步原语的工具类,比如ReentrantLock、Semaphore、CountDownLatch等。在使用这些工具类时,它们底层都是基于AQS实现的。

AQS提供了一种基于比较和交换的线程安全操作方式,它通过维护一个内部状态变量来标识资源的获取和释放状态。当一个线程尝试获取资源时,它会先比较自己的状态变量和队列头部的状态变量,如果该线程的状态变量大于队列头部的状态变量,那么该线程就可以获取资源。否则,该线程就需要入队等待。

G1垃圾回收器

一种基于区域的垃圾回收器。G1将堆内存分割成多个大小相等的独立区域(Region),每个区域都可以是Eden区、Survivor区或Old区。每个区域都有一个记忆集(Remembered Set),用于记录该区域引用了其他区域的对象。收集器能够对扮演不同角色的Region采用不同的策略去处理。G1收集器的运作过程大致分为以下几个步骤:

  1. 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
  2. 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
  3. 最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
  4. 筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的

垃圾回收算法

  1. 标记-清除算法:标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。标记清除算法会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。
  2. 复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当一块内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,空间浪费多了一倍。
  3. 标记-整理算法:标记-整理算法分为标记和整理两个阶段,首先标记出所有需要回收的对象,然后将所有存活的对象都向一端移动,然后清除端边界以外的内存。标记-整理算法不会产生内存碎片,但是需要移动对象,效率较低。
  4. 分代收集算法:分代收集算法根据对象的存活周期将内存分为新生代和老年代,新生代中的对象存活周期较短,老年代中的对象存活周期较长。新生代中的对象使用复制算法,老年代中的对象使用标记-整理算法。

老年代的空间担保机制

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败,如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那么这时也要改为进行一次Full GC。

MYSQL最左前缀

只会在查询条件与索引的顺序相互匹配时才会生效。

spring 依赖注入

Spring依赖注入(Dependency Injection,DI)是Spring框架的核心特性之一,它通过将对象的依赖关系交给容器来管理,而不是由对象自己创建依赖对象。

在Spring中,依赖注入有三种方式:

  1. 构造函数注入(Constructor Injection):通过构造函数将依赖对象传递给目标对象。这种方式可以保证目标对象在创建时就具备了所有必需的依赖对象。

  2. Setter方法注入(Setter Injection):通过setter方法将依赖对象注入到目标对象中。这种方式可以在目标对象创建后,通过setter方法动态地注入依赖对象。

  3. 接口注入(Interface Injection):通过接口定义注入方法,目标对象实现该接口并实现注入方法。这种方式相对较少使用。

依赖注入的好处包括:

  1. 降低了对象之间的耦合性,使得代码更加灵活、可维护和可测试。

  2. 提高了代码的可读性,通过依赖注入可以清晰地看到一个对象所依赖的其他对象。

  3. 方便了对象的创建和管理,由容器负责创建和注入依赖对象,减少了手动管理对象的工作量。

Spring框架提供了多种实现依赖注入的方式,包括基于XML配置、注解和Java配置等。无论使用哪种方式,Spring都会在容器启动时自动完成依赖注入的过程。

spring bean 的创建是采用了什么模式

Spring Bean的创建采用了设计模式中的工厂模式。具体来说,Spring IoC容器的Bean创建是通过工厂方法模式实现的。Spring IoC容器通过容器中配置的Bean定义信息(包括类名、属性、依赖关系等),动态地创建出各个Bean对象,并且通过依赖注入将这些对象组装起来。这个过程就是通过工厂方法模式实现的,具体来说就是通过容器中的getBean()方法来获取Bean对象。

动态代理如何实现

动态代理是一种在运行时创建代理对象的技术。它允许程序在运行时动态地生成代理对象,并自动地将方法调用转发给代理对象。动态代理通常用于跨语言调用、远程方法调用、事务处理、日志记录等场景。

实现动态代理的主要步骤如下:

  1. 定义接口:首先需要定义一个接口,该接口将作为代理对象的接口。代理对象将实现这个接口,以便客户端能够通过该接口来调用代理对象的方法。

  2. 实现代理类:实现一个代理类,该类需要实现上面定义的接口,并重写接口中的所有方法。在每个方法的实现中,需要调用实际的被代理对象的方法,并将调用结果返回给客户端。

  3. 创建被代理对象:在被代理对象的类中,需要实现一个方法,该方法将返回一个代理对象。这个方法通常被称为“getProxy”方法。

  4. 调用被代理对象的方法:通过调用被代理对象的“getProxy”方法,获取代理对象。然后通过代理对象的方法来调用被代理对象的方法。

下面是一个简单的Java示例代码:

// 定义接口
public interface MyInterface {
    public void doSomething();
}

// 实现代理类
public class MyProxy implements MyInterface {
    private MyInterface myInterface;
    
    public MyProxy(MyInterface myInterface) {
        this.myInterface = myInterface;
    }
    
    public void doSomething() {
        myInterface.doSomething();
    }
}

// 实现被代理类
public class MyObject implements MyInterface {
    public MyInterface getProxy() {
        return new MyProxy(this);
    }
    
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

// 调用被代理对象的方法
public class Main {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        MyInterface myInterface = myObject.getProxy();
        myInterface.doSomething();
    }
}

在上面的示例代码中,MyObject是被代理对象,MyInterface是接口,MyProxy是代理类。MyObject实现了MyInterface接口,并实现了getProxy方法,该方法返回一个MyProxy对象。MyProxy实现了MyInterface接口,并重写了doSomething方法。在doSomething方法的实现中,调用了实际的被代理对象的方法,并将调用结果返回给客户端。最后,在Main类中,通过调用MyObject的getProxy方法获取代理对象,并通过代理对象的doSomething方法来调用被代理对象的方法。

spring容器初始化的流程

1、ResourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件的资源;

2、BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中;

3、容器扫描BeanDefinitionRegistry中的BeanDefinition,使用Java的反射机制自动识别出Bean工厂后处理后器(实现BeanFactoryPostProcessor接口)的Bean,然后调用这些Bean工厂后处理器对BeanDefinitionRegistry中的BeanDefinition进行加工处理。主要完成以下两项工作:

1)对使用到占位符的元素标签进行解析,得到最终的配置值,这意味对一些半成品式的BeanDefinition对象进行加工处理并得到成品的BeanDefinition对象;
2)对BeanDefinitionRegistry中的BeanDefinition进行扫描,通过Java反射机制找出所有属性编辑器的Bean(实现java.beans.PropertyEditor接口的Bean),并自动将它们注册到Spring容器的属性编辑器注册表中(PropertyEditorRegistry);

4.Spring容器从BeanDefinitionRegistry中取出加工后的BeanDefinition,并调用InstantiationStrategy着手进行Bean实例化的工作;

5.在实例化Bean时,Spring容器使用BeanWrapper对Bean进行封装,BeanWrapper提供了很多以Java反射机制操作Bean的方法,它将结合该Bean的BeanDefinition以及容器中属性编辑器,完成Bean属性的设置工作;

6.利用容器中注册的Bean后处理器(实现BeanPostProcessor接口的Bean)对已经完成属性设置工作的Bean进行后续加工,直接装配出一个准备就绪的Bean。

负载均衡策略

负载均衡策略是指在分布式系统中,将请求分发到多个服务器上,以实现负载均衡的一种策略。常见的负载均衡策略包括以下几种:

  1. 轮询策略(Round Robin):按照请求的顺序依次将请求分发给每台服务器,循环往复。适用于服务器的性能相对均衡的情况。

  2. 最小连接数策略(Least Connections):将请求分发给当前连接数最少的服务器,以实现负载均衡。适用于服务器的性能差异较大的情况。

  3. 最少响应时间策略(Least Response Time):将请求分发给响应时间最短的服务器,以实现负载均衡。适用于服务器的响应时间差异较大的情况。

  4. IP哈希策略(IP Hash):根据请求的源IP地址进行哈希计算,将请求分发给对应的服务器。保证同一IP地址的请求始终被分发到同一台服务器上,适用于需要保持会话的情况。

  5. 加权轮询策略(Weighted Round Robin):根据服务器的权重值,将请求按照权重比例分发给各个服务器。适用于服务器性能差异较大的情况。

  6. 加权最小连接数策略(Weighted Least Connections):根据服务器的权重值和当前连接数,将请求分发给当前连接数最少且权重值较高的服务器。适用于服务器性能差异较大且需要保持会话的情况。

以上是常见的负载均衡策略,根据实际需求和系统情况选择合适的策略可以提高系统的性能和可靠性。

MVCC

MVCC(Multi-Version Concurrency Control)是一种并发控制机制,它通过保存数据在某个时间点的快照来实现事务的隔离性。在MVCC中,每个读操作会看到一个一致性的快照,而不是数据库中最新的数据。当系统中有大量的并发事务时,MVCC能够提高系统的性能。

redis的应用

Redis的应用范围很广,以下是一些主要的应用场景:

  1. 缓存数据:Redis用于缓存生命期较短的数据,能够大大降低数据库的压力。
  2. 排行榜应用:Redis提供的有序集合数据类型能够实现各种复杂的排行榜应用。
  3. 计数器:Redis可以用于实现计数器,例如电商网站商品的浏览量、视频网站视频的播放数等。
  4. 分布式会话:Redis可以用于实现分布式会话,解决单点故障的问题。
  5. 消息系统:Redis可以作为消息队列的中间件,解耦业务、削峰以及处理实时性低的业务。
本文作者:GWB
当前时间:2023-11-09 11:11:09
版权声明:本文由gwb原创,本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 国际许可协议。
转载请注明出处!