面经打怪升级系列【一】
作者:互联网
1. 自我介绍
2. 具体介绍一下最主要的项目,以及承担的工作,技术难点?
3. 你们使用 Redis 具体的使用场景?
1. 热点数据的缓存
由于redis访问速度快、支持的数据类型比较丰富,所以redis很适合用来存储热点数据,另外结合expire,我们可以设置过期时间然后再进行缓存更新操作,这个功能最为常见,我们几乎所有的项目都有所运用。
2. 高并发的读写
将数据库中的库存数据同步到 redis ,业务逻辑直接在redis 中操作数据,在某个时间点将缓存数据同步到数据库
3. 数据共享
将用户的登录信息存储在redis 中,其他模块或工程从redis 中直接获取
4. 分布式锁(为了保证多台服务器在执行某一段代码时保证只有一台服务器执行)
5. 自增长编号的生成
4. 热点数据放缓存的场景?
- 热点数据,频繁查询,放到redis中,降低数据库的压力
- 查询耗时的数据,每次查询耗费时间的请求,每次查询数据库会影响系统的响应速度
5. 技术选型为什么选择 Mybatis,他解决了什么问题?
1. 什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
2. 原生JDBC 的问题?
-
SQL夹在Java代码块里,耦合度高导致硬编码内伤
-
维护不易且实际开发需求中sql是有变化,频繁修改的情况多见
3. Hibernate和JPA 问题?
-
长、难复杂SQL,对于Hibernate而言处理也不容易
-
内部自动生产的SQL,不容易做特殊优化
-
基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。导致数据库性能下降
4. MyBatis 优势?
-
sql语句与代码分离,存放于xml配置文件中,便于维护
-
用逻辑标签控制动态SQL的拼接
-
查询的结果集与java对象自动映射
-
编写原生SQL,比较灵活
6. Mybatis 是怎么做到防止 sql 注入的?预编译动作是在什么环节发生的?
#{}是经过预编译的,是安全的;
${}是未经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入
【底层实现原理】MyBatis是如何做到SQL预编译的呢?其实在框架底层,是JDBC中的PreparedStatement类在起作用,PreparedStatement是我们很熟悉的Statement的子类,它的对象包含了编译好的SQL语句。这种“准备好”的方式不仅能提高安全性,而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。
在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题。
7. Spring 的 AOP 讲一下?
「AOP」:面向切面编程,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性
8. 线程都有哪些状态,是怎么转换的?
-
「新建状态(New)」:新创建了一个线程对象。
-
「就绪状态(Runnable)」:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“「可运行线程池」”中,变得可运行,只「等待获取CPU的使用权」。「即在就绪状态的进程除****CPU之外,其它的运行所需资源都已全部获得。」
-
**运行状态(Running):**就绪状态的线程获取了CPU,执行程序代码。
-
**阻塞状态(Blocked):**阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
「阻塞的情况分三种:」
(1)、「等待阻塞」:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“**等待池”**中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,
(2)、「同步阻塞」:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入**“锁池”**中。
(3)、「其他阻塞」:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
-
**死亡状态(Dead):**线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
9. 抽象方法可以加 static 修饰吗?可以加 synchronized 吗?可以加 native 吗?
-
不可以加 static 修饰
用static声明方法表明这个方法在不生成类的实例时可直接被类调用,而abstract方法不能被调用,两者矛盾。
-
不可以加 native 修饰
native 暗示这些方法是有实现体的,只不过这些实现体是非java 的,但是abstract却显然的指明这些方法无实现体。
-
不可以加synchronized 修饰
用synchronized的前提是该方法可以被直接调用,抽象方法的抽象类不可以被实例化,自然也无法被调用
10. JVM 类加载的过程?
JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化
-
「加载」:将字节码文件加载到内存中,将其存放到运行时数据区的方法区内,并在堆区中创建一个该类的Class对象,Class对象中封装了方法区内的数据结构,并提供了访问方法区数据结构的接口。字节码可以存在于本地磁盘、网络上、zip或jar等归档包、数据库等地方
-
「验证」:校验字节码文件的格式,确保加载的类的正确性
-
「准备」:给类的静态变量分配内存,并初始化默认值,比如int类型就初始化为0,boolean类型就初始化为false等,而非初始化为代码中显式赋予的值;
-
「解析」:把类中的符号引用转换为直接引用
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
符号引用:就是一组符号来描述目标,可以是任何字面量。
直接引用:就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
-
「初始化」:为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化
11. java 里 GC 的机制,怎么判断一个对象是可回收的?
-
「引用计数」
每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题
-
「可达性分析」
从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象
12. 什么情况下会发生内存泄漏?
-
「static字段引起的内存泄漏」
大量使用static字段会潜在的导致内存泄漏,在Java中,静态字段通常拥有与整个应用程序相匹配的生命周期。
-
「静态集合类引起内存泄漏」
这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放
-
「当集合里面的对象属性被修改后,再调用remove()方法时不起作用」
public static void main(String[] args) { Set<Person> set = new HashSet<Person>(); Person p1 = new Person("唐僧","pwd1",25); Person p2 = new Person("孙悟空","pwd2",26); Person p3 = new Person("猪八戒","pwd3",27); set.add(p1); set.add(p2); set.add(p3); System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素! p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变 set.remove(p3); //此时remove不掉,造成内存泄漏 set.add(p3); //重新添加,居然添加成功 System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素! for (Person person : set) { System.out.println(person); } }
-
「各种连接未关闭」
一般都会在try里面连接,在finally里面释放连接
13. 两个对象 Hashcode 一致的时候,equals 会相等吗?
-
两个对象的equals() 相等,hashCode()一定相等。
-
两个对象的hashCode()相等,equals()不一定相等
「equals()」:比较的是非基本类型的数据的引用地址(即内存地址)是否相同,但是对于重写equals方法的类型,比较的是对象内容是否相同。「hashCode()」:计算对象实例的哈希码,用于返回字符串的哈希码。其中哈希码使用散列表(也叫哈希表)存储的。但是因为哈希表存储的Hash散列值时可能存在有冲突的情况,因此,两个对象的equals()可能不同,虽然概率很低。
因此在比较两个对象是否相等的时候,单单比较两个对象的hashCode是否相等是不可以的。
14. 解释一下什么是 Hashcode?
「Hash:」
一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。
这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
hashCode:散列码是由对象导出的一个整型值。散列码是没有规律的。类的hashCode()方法继承自Object类,因此每个对象都有一个默认的散列码,他的值为对象的存储地址(由对象的物理存储地址通过散列转换来的)
15. 往一个 Hash 表插入一个键值对的时候的过程是什么?
-
初始化table
-
计算hash值
-
插入或更新节点
-
扩容
16. 讲一下最熟悉的数据结构?
17. Docker 原理?为什么比虚拟机更轻?
-
「docker 是什么?」
Docker是一个开源的应用容器引擎,可以轻松的让开发者打包任何应用以及依赖包到一个轻量级的、可移植的、自给自足的容器中。然后发布到任何流行的Linux机器上,也可以实现虚拟化。开发者把编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机)、bare metal、OpenStack 集群和其他的基础应用平台。容器是完全使用沙箱机制,相互之间不会有任何接口
-
「为什么Docker比虚拟机快?」
-
docker有着比虚拟机更少的抽象层。由亍docker不需要Hypervisor实现硬件资源虚拟化,运行在docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上docker将会在效率上有明显优势。
-
docker利用的是宿主机的内核,而不需要Guest OS。因此,当新建一个容器时,docker不需要和虚拟机一样重新加载一个操作系统内核。因而避免引寻、加载操作系统内核返个比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载Guest OS,返个新建过程是分钟级别的。而docker由于直接利用宿主机的操作系统,则省略了整个过程,因此新建一个docker容器只需要几秒钟。
-
18. 虚拟机的隔离和 docker 容器的隔离有什么区别?
-
docker属于进程之间的隔离,虚拟机可实现系统级别隔离;
-
docker 容器 借助 Linux 内核技术Namespace来做到隔离的,Docker 技术由于 还是一个普通的进程,所以隔离不是很彻底,还是共用宿主机的内核,在隔离级别和安全性上没有虚拟机高,这也是它的一个劣势。
-
容器其实就是Linux下一个特殊的进程;
-
Docker容器通过namespace实现进程隔离;通过cgroups实现资源限制;
-
Docker镜像(rootfs)是一个操作系统的所有文件和目录而不包括内核,Docker镜像是共享宿主机的内核的;
19. 为不同的微服务提供统一的网关应该怎么做?
-
「路由」
微服务服务注册和发现,服务在应用内部被管理而通过网关路由定位,调用不同的服务
-
「负载均衡」
-
「聚合」
将多个服务的多个请求结果聚合,合并返回
-
「鉴权」
通过网关鉴权,更加高效的访问每一个服务,而不用每个都执行鉴权
-
「断路限流黑白名单」
-
在网关层面实现断路器, 即超过了指定的阈值,API网关就会停止发送数据到那些失败的模块。这样可以防止服务之间的调用延时造成的雪崩,又有足够的时间来分析日志,修复问题
-
可以对流量进行限制,实现限流的能力,缓解下层服务的压力
-
可以通过黑白名单设置,直接禁止某些请求到达下层应用,防止恶意访问等情况
-
-
「监控日志」
监控各个应用的基础指标,记录聚合业务日志,标记异常堆栈,方便追踪排查
-
「灰度发布」
通过网关的流量控制,可以实现新旧版本同时"服役",通过实际流量检验服务情况,并实现服务的平滑升级。
编程·技术·经验
欢迎扫码关注
1024快乐编程
标签:对象,虚拟机,面经,升级,打怪,线程,SQL,docker,方法 来源: https://blog.csdn.net/qq_34909297/article/details/115607499