面试题汇总(一)

126 阅读11分钟

Java引用类型

JVM 系列(5)吊打面试官:说一下 Java 的四种引用类型 # 我赌你不懂系列:Java的引用类型都有哪几种

Java主要有四种引用类型,用来管理Java对象的生命周期,Java引用类型一般分为强引用FinalReference软引用SoftReference弱引用weakReference虚引用PhantomReference

使用引用对象

  • 1. 创建引用对象: 直接通过构造器创建引用对象,并且直接在构造器中传递关联的实际对象和引用队列。引用队列可以为空,但虚引用必须关联引用队列,否则没有意义;
  • 2. 获取实际对象: 在实际对象被垃圾收集器回收之前,通过 Reference#get() 可以获取实际对象,在实际对象被回收之后 get() 将返回 null,而虚引用调用 get() 方法永远是返回 null;
  • 3. 解除关联关系: 调用 Reference#clear() 可以提前解除关联关系。

四种引用介绍

  • 强引用 :强引用指向的对象不会被垃圾收集器回收;Object obj = new Object();其中obj就是new Object()的强引用,只要这个对象有强引用存在,它就不会被回收。
  • 软引用: 软引用指向的对象在内存充足时起到类似强引用的效果,但在内存不足时还是会被垃圾收集器回收。SoftReference的实例保存一个Java对象的软引用,该软引用的存在不影响垃圾回收线程对该Java对象的回收。(欺软怕硬)
    SoftReference<byte[]> soft = new SoftReference<>(new byte[1024*1024*10]);
    soft.get();  //  可以得到原始对象
    
  • 弱引用:弱引用指向的对象无论在内存是否充足的时候,都会被垃圾收集器回收;
  • 虚引用:虚引用是无法通过get方法来获取对象的,一个虚引用对象被回收时会被放在一个ReferenceQueue队列中,意思就是虚引用回收时会给出一个信号放在队列中。

线程池

线程池参数

public ThreadPoolExecutor(
    int corePoolSize,  // 核心线程数
    int maximumPoolSize,  // 最大线程数
    long keepAliveTime,  // 空闲线程的存活时间
    TimeUnit unit,  // 存活时间的单位
    BlockingQueue<Runnable> workQueue,  // 阻塞队列
    ThreadFactory threadFactory,  // 创建线程的工厂,可指定优先级
    RejectedExecutionHandler handler){  // 拒绝策略       
    ...
}

常见线程池

Executors类是一个工厂类,提供一些工厂方法帮助我们快速建立一个线程池。主要包括三个方法:newSingleThreadExecutor、newFixedThreadPool、newCachedThreadPool

  • newSingleThreadExecutor:设置核心线程数和最大线程数都为1
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
  • newFixedThreadPool:设置核心线程数和最大线程数为自定义的固定值
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}
  • newCachedThreadPool,核心线程数为0,但是最大线程数为Integer的最大值。适合生存期短的异步任务
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

BlockingQueue:在任意时刻只有一个线程可以进行take或者put操作,并且BlockingQueue提供了超时return null的机制

SynchronousQueue:一个特殊的队列,内部没有数据的存储空间,队列不能peek,因为不存在元素,任何入队的线程都会阻塞,直到有线程来出队,也就是这个队列是一组操作,入队和出队要一起离开,出队也是一样,必须等入队,必须成对执行,否则会阻塞

ArrayBlockingQueue:由数组支持的有界队列

LinkedBlockingQueue:由链接节点支持的可选有界队列

image.png

为什么不建议使用Exectors创建线程

以上三种方式创建的线程可能会导致OOM

  • LinkedBlockingQueue 无界队列:可以一直加任务
  • Integer.MAX_VALUE最大核心线程数:可以一直加线程

拒绝策略

拒绝策略是一个接口,只有一个rejectedExecution方法,包括四个实现类:

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

image.png

  • AbortPolicy:拒绝(啥也不做)并抛出异常。A handler for rejected tasks that throws a {@code RejectedExecutionException}.
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
    
  • DiscardPolicy:拒绝(啥也不做)但不抛出异常。A handler for rejected tasks that silently discards the rejected task.
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}
    
  • DiscardOldestPolicy:弹出队列中最早的任务,然后把当前任务加入到等待队列。A handler for rejected tasks that discards the oldest unhandled request and then retries {@code execute}, unless the executor is shut down, in which case the task is discarded.
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
    
  • CallerRunsPolicy:直接在调用者线程中运行。A handler for rejected tasks that runs the rejected task directly in the calling thread of the {@code execute} method, unless the executor has been shut down, in which case the task is discarded.
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
    

工作线程异常处理

当线程池中的某个线程出现异常之后,会从workers集合中删除这个线程

Spring中Bean的生命周期

  • 收集BeanDefinition
  • 实例化:给 Bean 分配内存空间
    • InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation(实例化之前执行,返回的对象如果不为null,则会替换掉原本的bean)
    • 实例化(需要推断构造函数,默认使用空参构造)
    • InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation 实例化之后执行,如果返回 false 会跳过依赖注入阶段
  • 属性注入:@Autowired @Resource @Value
    • InstantiationAwareBeanPostProcessor#postProcessProperties 依赖注入阶段执行,如@Autowired, @Value, @Resource
  • 初始化
    • Aware接口
    • InstantiationAwareBeanPostProcessor#postProcessBeforeInitialization 初始化之后执行 这里返回的对象会替换掉原有的bean,如代理增强
    • @PostConstruct
    • init-method
    • InstantiationAwareBeanPostProcessor#postProcessAfterInitialization 初始化之后执行
  • 使用
  • 销毁
    • DestructionAwareBeanPostProcessor#postProcessBeforeDestruction 销毁之前执行
    • @PreDestroy 销毁之前执行
    • destory-method 销毁之前执行

进程之间如何设置共享内存

  1. 创建共享内存区,通过shmget实现。在物理内存中开辟一块共享内存区。
  2. 把这块共享内存区挂接映射到两个进程的地址空间上,通过shmat实现。
  3. 完成通信之后,撤销内存映射关系,通过shmdt进行脱离。
  4. 删除共享内存区,通过shmctl实现。

熔断和限流

熔断:QPS、异常数量、异常比例

限流方法:计数器固定窗口算法、计数器滑动窗口算法、漏斗算法、令牌桶

操作系统中断的处理流程

MySQL排查慢查询

  1. 通过show variables like '%slow_query_log%'查看慢查询日志,找到慢查询的时长以及日志存放路径,如果没有开启慢查询可以通过set global slow_query_log='ON';开启慢查询日志 image.png
  2. 通过explain分析表现为慢查询的sql语句
  3. 检查是不是因为没有走索引导致的慢,如果是这个原因可以通过设置适当的索引,如果是因为优化器的选择了直接扫全表,可以通过force index(index_name)强制走索引

explain的type字段

image.png

type 表示 MySQL 在表中找到所需行的方式, 又称"访问类型",主要包括ALL, index, range, ref, eq_ref, const, system, NULL (从左到右, 性能从差到好)

  • ALL:全表扫描
  • index:全索引扫描
  • range:索引范围扫描, 常见于 '<', '<=', '>', '>=', 'between' 等操作符
  • ref:使用非唯一性索引或者唯一索引的前缀扫描, 返回匹配某个单独值的记录行.
  • eq_ref:类似ref, 区别就在使用的索引是唯一索引. 在联表查询中使用 primary key 或者 unique key 作为关联条件.
  • const/system: 当 MySQL 对查询某部分进行优化, 并转换为一个常量时, 使用这些类型访问. 如将主键置于 where 列表中, MySQL 就能将该查询转换为一个常量, system 是 const 类型的特例, 当查询的表只有一行的情况下使用 system.
  • NULL:MySQL 不用访问表或者索引就直接能到结果,比如select 1;

索引失效

模型数空运最快

  1. 模:代表模糊查询。like的模糊查询以%开头,索引失效。
  2. 型:代表数据类型。类型错误,如字段类型为varchar,where条件用number,索引也会失效。
  3. 数:代表函数。对索引的字段使用内部函数,索引也会失效。这种情况下应该建立基于函数的索引。
  4. 空:是Null的意思。索引不存储空值,如果不限制索引列是not null,数据库会认为索引列有可能存在空值,所以不会按照索引进行计算。
  5. 运:代表运算。对索引列进行加、减、乘、除等运算,会导致索引失效。
  6. 最:代表最左原则。在复合索引中索引列的顺序至关重要。如果不是按照索引的最左列开始查找,则无法使用索引。
  7. 快:全表扫描更快的意思。如果数据库预计使用全表扫描要比使用索引快,则不使用索引。

SQL执行顺序

  1. FROM子句组装数据(包括通过ON进行连接);
  2. WHERE子句进行条件筛选;
  3. GROUP BY分组 ;
  4. 使用聚集函数进行计算;
  5. HAVING筛选分组;
  6. 计算所有的表达式;
  7. SELECT 的字段;
  8. ORDER BY排序;
  9. LIMIT筛选。

原码、补码和反码

整数的原码、补码和反码都一样,只是负数有所不同。原码的负数是通过符号位来标识的,反码的负数除了用原位标识之外,还将其他位置的数值取反了。补码则是相当于反码+1。

计算机底层存储都是以补码的形式存储的,因为它的效率更高。

  • 原码 :最高位是符号位,0代表正数,1代表负数,非符号位为该数字绝对值的二进制.
  • 反码:正数的反码与原码一致,负数的反码是对原码按位取反,只是最高位(符号位)不变。
  • 补码:正数的补码与原码一致,负数的补码是对原码按位取反加1,符号位不变。

比如byte -1,它的原码为10000001,反码为11111110,补码为11111111

再比如,如下的程序输出为-106,因为150默认是int类型,包含四个字节,转成byte之后会取第四个字节,变为10010110,它是补码的形式,减一得到反码10010101,转换为原码为11101010,代表-106。

byte b = (byte) 150;
System.out.println(Integer.toBinaryString(150));
System.out.println(b);

单例模式懒汉模式

public class LazySingleton {
    private LazySingleton(){}
    private volatile static LazySingleton lazySingleton;
    public static LazySingleton getInstance(){
        if(lazySingleton == null){  // 防止每次都加锁
            // 多个线程会到达此处,但是只有一个线程能够加锁,如果没有第二个null判断,会导致下一个线程获取锁后再进行实例化操作
            synchronized (LazySingleton.class){
                if(lazySingleton==null){  // 防止多个线程依次获取锁之后被实例化多次
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }
}

AtomicInteger

AtomicInteger借助了Unsafe类进行CAS操作。AtomicInteger内部存在一个int类型的value属性,用来保存当前的值,在类初始化时会获取value属性相对于对象起始位置的偏移量,便于底层进行处理。在set值的时候

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;  // value的偏移量
    private volatile int value;  // 存储的当前值

    static {
        try {
                //获取value字段相对AtomicInteger对象的"起始地址"的偏移量
                valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    ...
}
public final native boolean compareAndSwapInt(
            Object object,  // 需要变更的对象(对这个对象中的int属性进行cas)
            long offset,  // 在对象中的偏移量
            int expect,  // 期望值(更新之前的值)
            int update);  // 更新值

LockSupport.park和LockSupport.unpark

7P9PFBSC5.jpg

Blocker是用来标识当前线程在等待的对象,即记录线程被阻塞时被谁阻塞的,用于线程监控和分析工具来定位,它在park之前先通过setBlocker()记录阻塞线程的发起者object,当线程锁被释放后再次清除记录;

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);  // 一个本地方法
}
public static void park(Object blocker) {  // 在Thread类中存在一个parkBlocker属性用来给LockSupport调用
    // 如果有许可,消耗许可并马上返回
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);  // 表示永远挂起,不是计时挂起
    setBlocker(t, null);
}
public static void park() {  // 不需要记录当前因为什么对象park时可以使用该方法
    UNSAFE.park(false, 0L);
}

Spring中Bean的创建过程

所有的ApplicationContext(如AnnotationConfigApplicationContext) 继承了 GenericApplicationContext,GenericApplicationContext 中持有一个 DefaultListableBeanFactory,DefaultListableBeanFactory 继承了 AbstractAutowireCapableBeanFactory,AbstractAutowireCapableBeanFactory中有doCreateBean方法

image.png AbstractAutowireCapableBeanFactory

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
    }

    if (instanceWrapper == null) {
        instanceWrapper = this.createBeanInstance(beanName, mbd, args);
    }

    Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
        mbd.resolvedTargetType = beanType;
    }

    synchronized(mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            try {
                this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            } catch (Throwable var17) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", var17);
            }

            mbd.postProcessed = true;
        }
    }

    boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
    if (earlySingletonExposure) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
        }

        this.addSingletonFactory(beanName, () -> {
            return this.getEarlyBeanReference(beanName, mbd, bean);
        });
    }

    Object exposedObject = bean;

    try {
        this.populateBean(beanName, mbd, instanceWrapper);
        exposedObject = this.initializeBean(beanName, exposedObject, mbd);
    } catch (Throwable var18) {
        if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
            throw (BeanCreationException)var18;
        }

        throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
    }

    if (earlySingletonExposure) {
        Object earlySingletonReference = this.getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            } else if (!this.allowRawInjectionDespiteWrapping && this.hasDependentBean(beanName)) {
                String[] dependentBeans = this.getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet(dependentBeans.length);
                String[] var12 = dependentBeans;
                int var13 = dependentBeans.length;

                for(int var14 = 0; var14 < var13; ++var14) {
                    String dependentBean = var12[var14];
                    if (!this.removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }

                if (!actualDependentBeans.isEmpty()) {
                    throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }

    try {
        this.registerDisposableBeanIfNecessary(beanName, bean, mbd);
        return exposedObject;
    } catch (BeanDefinitionValidationException var16) {
        throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", var16);
    }
}