Java面试总结(九)

redis的持久化方式

RDB、AOF、混合。

RDB 持久化

Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用,快照持久化是 Redis 默认采用的持久化方式。

AOF 持久化

开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 server.aof_buf 中,然后再根据 appendfsync 配置来决定何时将其同步到硬盘中的 AOF 文件。
AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。

在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:

appendfsync always    #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec  #每秒钟同步一次,显式地将多个写命令同步到硬盘
appendfsync no        #让操作系统决定何时进行同步

混合

由于 RDB 和 AOF 各有优势,于是,Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。

redis的单线程怎么理解

一开始文件事件处理器以单线程方式运行,通过IO多路复用技术监听多个套接字(select、poll、epoll),Redis4.0以后针对大键值对的删除引入了多线程,执行这些命令都会使用主线程之外的线程异步执行,Redis6.0之后使用多线程提高网络IO读写性能,但命令执行仍是单线程顺序执行,不用担心线程安全问题。

Redis6.0 之前为什么不使用多线程

  1. 单线程编程容易并且更容易维护;
  2. Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
  3. 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。

JVM的内存区域有哪些块

线程私有:程序计数器、Java虚拟机栈、本地方法栈;
线程共享:堆和方法区(JDK1.8之前实现为永久代,JDK1.8后为元空间(使用直接内存))。

JDK 1.8 之前:
在这里插入图片描述
JDK 1.8 之后:
在这里插入图片描述

请问JVM中new一个对象后是怎么进行分配内存的

内存分配的两种方式

指针碰撞
  • 使用场合:堆内存规整(即没有内存碎片)的情况下。
  • 实现原理:将用过的内存都整合到一边,没有用过的内存放到另一边,中间有一个分界指针,当需要为新对象分配内存空间时,只需要将分界指针向没有用过的内存一侧移动对象内存大小位置即可。
空闲列表
  • 使用场合:堆内存不规整的情况下。
  • 实现原理:虚拟机会维护一个列表,该列表记录了哪些内存是可用的,当需要为新对象分配内存空间时,只需要在列表中找一块足够大小的内存分配给对象实例,然后更新列表记录。

选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器垃圾采用的垃圾收集算法,垃圾收集相关内容我会在后续文章详细介绍。

AOP的原理,IOC的原理

Spring IoC

Ioc(Inversion of control:即控制反转)是一种设计思想,而不是一种具体的技术实现。IoC的思想就是将原本在程序中手动创建对象的控制权交给Spring框架来管理。

不过, IoC 并非 Spring 特有,在其他语言中也有应用。

为什么叫控制反转
  • 控制: 指的是对象创建(实例化、管理)的权力;、

  • 反转: 指的是将控制权交给外部环境(Spring框架、IoC容器)。

在这里插入图片描述

将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。

在实际项目中一个 Service 类可能依赖了很多其他的类,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。

在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。

Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。

IoC解决了什么问题

IoC 的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢?

  • 对象之间的耦合度降低或者说依赖程度降低;

  • 资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。

什么是依赖注入(Dependency Injection)

Dependency Injection。它是 spring 框架核心 IoC 的具体实现。
我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。IoC 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。
那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。
简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

依赖注入的原理

所谓依赖注入,即在运行期由容器将依赖关系注入到组件之中。
讲的通俗点,就是在运行期,由Spring根据配置文件,将其他对象的引用通过组件的提供的setter方法进行设定。
Spring 大量引入了Java 的Reflection机制,通过动态调用的方式避免硬编码方式的约束,并在此基础上建立了其核心组件BeanFactory,以此作为其依赖注入机制的实现基础。
代码理解:

Class cls = Class.forName("net.xiaxin.beans.User");  
Method mtd = cls.getMethod("setName",new Class[]{String.class});  
Object obj = (Object)cls.newInstance();  
mtd.invoke(obj,new Object[]{"Erica"});  
return obj;
Object obj = Class.forName("net.xiaxin.beans.User").newInstance();  
BeanWrapper bw = new BeanWrapperImpl(obj);  
bw.setPropertyValue("name", "Erica");  
System.out.println("User name=>"+bw.getPropertyValue("name"));  
数据类型

能注入的数据类型:有三类

  • 基本类型和String;
  • 其他bean类型(在配置文件中或者注解配置过的bean);
  • 复杂类型/集合类型。

Spring AOP

AOP:Aspect oriented programming 面向切面编程,AOP 是 OOP(面向对象编程)的一种延续。

下面我们先看一个 OOP 的例子。

例如:现有三个类,Horse、Pig、Dog,这三个类中都有 eat 和 run 两个方法。

通过 OOP 思想中的继承,我们可以提取出一个 Animal 的父类,然后将 eat 和 run 方法放入父类中,Horse、Pig、Dog通过继承Animal类即可自动获得 eat() 和 run() 方法。这样将会少些很多重复的代码。

在这里插入图片描述
OOP 编程思想可以解决大部分的代码重复问题。但是有一些问题是处理不了的。比如在父类 Animal 中的多个方法的相同位置出现了重复的代码,OOP 就解决不了。

/**
 * 动物父类
 */
public class Animal {

    /** 身高 */
    private String height;

    /** 体重 */
    private double weight;

    public void eat() {
        // 性能监控代码
        long start = System.currentTimeMillis();

        // 业务逻辑代码
        System.out.println("I can eat...");

        // 性能监控代码
        System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
    }

    public void run() {
        // 性能监控代码
        long start = System.currentTimeMillis();

        // 业务逻辑代码
        System.out.println("I can run...");

        // 性能监控代码
        System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
    }
}

这部分重复的代码,一般统称为 横切逻辑代码。
在这里插入图片描述

横切逻辑代码存在的问题
  • 代码重复问题
  • 横切逻辑代码和业务代码混杂在一起,代码臃肿,不变维护

AOP 另辟蹊径,提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离

在这里插入图片描述

代码拆分比较容易,难的是如何在不改变原有业务逻辑的情况下,悄无声息的将横向逻辑代码应用到原有的业务逻辑中,达到和原来一样的效果。

AOP 为什么叫面向切面编程

切 :指的是横切逻辑,原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑

面 :横切逻辑代码往往要影响的是很多个方法,每个方法如同一个点,多个点构成一个面。这里有一个面的概念。

AOP 解决了什么问题

在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。

谈谈自己对于 AOP 的了解

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

动态代理有哪些,并讲一下区别

JDK动态代理和CGLIB动态代理都是Java中的动态代理技术,但它们的实现方式不同。

JDK动态代理是基于接口的代理,它要求被代理的类必须实现一个接口,代理类实现该接口并在代理类中调用被代理类的方法。JDK动态代理使用Java自带的反射机制实现,因此它的效率比较高。

CGLIB动态代理是基于继承的代理,它不要求被代理的类实现接口,代理类继承被代理类并重写被代理类的方法。CGLIB动态代理使用ASM框架实现,因此它的效率比JDK动态代理略低,但它可以代理没有实现接口的类。

总的来说,JDK动态代理适用于代理实现了接口的类,而CGLIB动态代理适用于代理没有实现接口的类。

JDK动态代理示例代码:

public interface UserService {

    void addUser();

    void updateUser(String str);

}


public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("添加用户");
    }

    @Override
    public void updateUser(String str) {
        System.out.println("更新用户信息:" + str);
    }
}

public class UserProxy implements InvocationHandler {

    private Object target;

    public UserProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("---------------------before---------------------");
        Object invoke = method.invoke(target, args);
        System.out.println("---------------------after---------------------");

        return invoke;
    }
}

public class TestProxy {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserProxy userProxy = new UserProxy(userService);
        UserService proxyInstance = (UserService) Proxy.newProxyInstance(userProxy.getClass().getClassLoader(), userService.getClass().getInterfaces(), userProxy);

        proxyInstance.addUser();
        proxyInstance.updateUser("张三");
    }
}

Output:

---------------------before---------------------
添加用户
---------------------after---------------------
---------------------before---------------------
更新用户信息:张三
---------------------after---------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路上阡陌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值