文章目录
一、说一下Java语言的优势
跨平台(java代码在JVM虚拟机上运行)、安全性(Java内置多种安全功能,内存管理、异常处理、访问控制等)、强类型检查(Java是一种静态类型语言,在编译的时候就会检查类型错误)、多线程支持(轻松创建并发程序)、面向对象编程(封装、继承、多态三大特性,便于组织和管理代码,易理解易扩展)、大型标准库(提供很多实用的类和方法)、强大的社区支持
二、了解Java内存模型吗?
首先,Java内存模型(JMM)是Java虚拟机(JVM)规范的一个重要组成部分,它确保在多线程环境下安全和高效的执行Java程序。Java内存模型定义了主内存和工作内存,主内存被线程共享,线程的工作内存相互隔离,以此来解决多线程同时访问内存而导致数据不一致的问题。(所有变量都存储在主内存中,但是每个线程都在自己的工作内存中存储了所有变量的副本。)
然后,Java内存模型的运行时数据区分为五个主要部分:堆、方法区、虚拟机栈、本地方法栈、程序计数器。
- 堆,是主内存也就是线程共享的区域,主要用来new对象的时候存放对象实例;
- 方法区,存储类的结构信息(字段、方法);
- 虚拟机栈,每一个线程都有一个虚拟机栈,用来存放局部变量、动态链接、方法出口等信息;
- 本地方法栈,与虚拟机栈类似,用来支持native方法(非Java实现的底层方法)的执行;
- 程序计数器,每一个线程都有一个程序计数器,用来记录线程执行的当前指令地址。
了解这些内存区域是优化内存使用和排查内存泄漏问题的基础。通过深入理解Java内存模型和运行时数据区,我们可以更有效地进行Java编程,尤其是在处理多线程和内存管理问题时。这些知识对于写出高性能、可靠和高效的Java代码是至关重要的。
三、说一下常用的集合
ArrayList、LinkedList、HashMap
ArrayList和LinkedList都是Java中常用的List的子类,它们的一些区别如下:
-
底层实现不同:ArrayList是基于动态数组实现的,在内存中的存储也是连续的地址空间,LinkedList是基于双向链表实现的,在内存中的存储是非连续的;
-
查询效率不同:ArrayList基于数组下标可以随机访问所以查询时间复杂度是O(1),LinkedList不支持随机访问,查询元素的时候需要从链表头节点进行遍历,所以查询的平均复杂度是O(n);
-
插入删除效率不同:对于ArrayList来说如果在数组的中间位置进行元素的插入或者删除时还需要额外的移动元素的操作所以效率会有所降低,对于LinkedList来说如果在链表中间位置进行元素的插入或者删除直接修改指针就可以实现,不需要移动数据,但是在插入前存在一个遍历链表的操作,所以二者各有优劣吧。
-
内存开销:ArrayList较小的内存开销,LinkedList的开销相对较大,因为链表的每一个节点还需要额外的空间存储前后节点的引用。
-
容量调整:ArrayList是基于数组实现的,所以在创建的时候容量就已经确定了,如果容量不足就需要对数据进行扩容,通常是当前容量的1.5倍,而且该扩容是通过新建数据,将原来的元素复制到新数组中;LinkedList基于链表,可以动态的添加和删除节点,不需要进行容量调整。
HashMap是Java中常用的键值对存储的数据结构,每个键映射一个值;内部使用哈希表来存储数据;它是非线程安全的;HashMap不保证数据的有序性,是无序的。
HashMap底层是基于数组来实现的,通过哈希函数来计算key对应的哈希值,然后在定位到数组的下标进行存储,这样可能会发生哈希冲突,使用链地址法来解决哈希冲突,所以在数组中,将元素存储为链表,随着链表节点的增多,导致查询效率降低,在JDK1.8对此做了优化,就是当数组的容量到达64且链表的长度超过8之后就将链表转化成红黑树,以提高查询效率。
注意:HashMap的初始容量和负载因子是两个非常重要的参数,需要根据实际情况合理设置。
四、了解ArrayList的扩容机制吗?
ArrayList是基于数组实现的,在创建的时候容量就已经确定了,就算我们没有指定大小,也会有一个默认的大小,JDK8默认大小是10,所以当容量不足时,需要采取一定的措施进行扩容,而这里的扩容其实是新建一个数组,然后把原来数组中的元素复制到新数组中,问题就是一次扩到多大呢?新的容量是原容量的1.5倍。
这种扩容策略是为了平衡存储空间和时间开销。频繁的扩容会导致时间开销增大,因为每次扩容都需要复制数组。另一方面,如果每次扩容都增加很多额外空间,那么存储空间可能会被浪费。所以,选择1.5倍的扩容策略是为了在这两者之间达到平衡。
五、HashMap是线程安全的吗?
HashMap是非线程安全的数据结构,在多线程环境下,如果有多个线程同时读写HashMap,而没有进行适当的同步控制,那么可能会导致数据的不一致。
Java提供了线程安全的代替方案:ConcurrentHashMap
,这是一个为并发访问设计的Map实现,它提供了更好的并发性能和更精细的锁粒度。
六、SpringBoot Starter启动流程
SpringBootApplication的启动流程主要分为两大部分,一是SpringBootApplication的实例化,二是SpringBootApplication.run()方法的执行。
SpringBootApplication的实例化阶段,在初始化模块中主要完成以下几件事:读取配置文件、检查项目类型、初始化构造器、创建应用监听器;
SpringBootApplication.run()方法执行阶段,执行方法表示应用正式启动,启动应用监听器、创建和配置环境、创建对应的应用上下文以及加载一些配置文件。
七、InnoDB存储引擎的索引数据结构
InnoDB存储引擎的索引数据结构是基于B+树的,B+树是在B树上继续优化得到的,它通过将索引和数据进行分离来提高查询效率,就是B+树的非叶子节点只存储索引,在叶子节点存储一行记录,这样可以使得更多的索引值存放在一页中,从而降低了树的高度,减少了IO次数,并且叶子节点之间通过双向指针进行连接,提高了范围查询的性能。
八、Redis常用的数据结构
String字符串类型,最简单的数据结构,可以包含字符串、整数或者浮点数,可以用来做缓存、计数器等;
哈希表,可以用来存储对象信息,对象信息中包含一系列字段和字段值;
List列表,一个有序集合,常见的使用场景就是实现队列、堆栈、时间线等;
Set集合,一个无序集合,且不含重复元素;
SortedSet有序集合,里面的元素都会关联一个浮点数类型的分数,集合中的元素就会根据这个分数进行排序;
九、OSI七层网络模型
应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
应用层:提供网络服务和用户接口;
表示层:数据格式的转换、数据加密
会话层:会话连接
传输层:负责端到端的通信和流控制,确保数据的完整性
网络层:网络寻址和路由
数据链路层:传输MAC帧,提供物理地址,错误检测和修正报文
物理层:物理媒介,传输比特流
十、tcp和udp的区别
tcp是面向连接的,提供可靠传输(确认机制、超时重传、流量控制、拥塞控制);udp是无连接的,不保证可靠传输(丢失、重复、乱序);
tcp以字节流进行传输,没有边界;udp以数据报的格式传输,每个数据报有明确的界限;
tcp传输速度相对较慢,因为提供可靠传输肯定有额外的开销,以及头部包含的额外控制信息也更多;udp刚好就相反,没有太多的额外控制以及信息,传输效率就相对较高;
tcp一般用于服务器和客户端这种一对一的通信模式,tcp可以一对一、一对多、多对多,广播和多播模式。
十一、操作系统内存管理
操作系统的内存管理是计算机系统资源管理的关键组成部分,其主要目的是为进程分配和管理内存空间,以实现有效的内存利用和保护。
连续内存分配:单一连续分配、固定分区分配、动态分区分配
非连续内存分配:分页、分段
虚拟内存分配:
十二、Spring AOP实现原理
Spring AOP实现原理基于两种主要技术:动态代理和静态代理。
静态代理,AspectJ,AspectJ是一个独立的AOP框架,但Spring AOP也可以与AspectJ集成;AspectJ使用编译时织入(Compile-time weaving)或加载时织入(Load-time weaving)来插入增强代码。
动态代理:JDK动态代理和CGLib动态代理
- JDK动态代理:只能为实现了接口的类生成代理。
java.lang.reflect.Proxy
类用于生成动态代理类。- 实现
InvocationHandler
接口,重写invoke()
方法,将增强的逻辑写在invoke()
方法中。 Proxy.newProxyInstance()
方法用于创建动态代理实例。
- CGLib动态代理:可以为没有实现接口的类生成代理。
- CGLIB通过继承目标类的方式生成代理类。
MethodInterceptor
接口类似于JDK动态代理的InvocationHandler
。Enhancer.create()
方法用于创建代理实例。
十三、Redis过期策略
定时过期、定期过期、惰性过期
定时过期:每一个键设置过期时间,当定时器到达设置的过期时间,键就会立即被删除;精确控制键的过期删除,对内存友好但是对CPU不是很友好;
惰性过期:只有在访问一个键时才会检查它是否过期,如果过期则返回错误(对于读操作)或者直接删除(对于写操作);对CPU比较友好,不需要额外的占用CPU区检查,但是对内存不太友好,不能及时删除过期键;
定期过期:定期随机检查一些键,删除已经过期的键,通过限制每次检查的键的数量和检查的频率,可以控制CPU的使用;比定时过期更节省CPU资源,比惰性过期更能及时释放内存。