为什么 Java 中不支持多继承?
原因
如果 Java 支持多继承的话,会出现下面的问题:
类 D 因为多继承,继承了 B 和 C ,B 和 C 同时继承了 A ,那么导致了这样的问题:
D 中会出现两个从 A 继承下来的属性或者方法,那么在调用的时候就会出现歧义。所以在 Java 中放弃使用多继承,避免了这样的问题。
什么是索引,索引的优缺点是什么
索引:一种为了提升数据的检索效率的数据结构
常见的索引数据结构
哈希表
二叉树
B+ 树
索引的优缺点
优点:使得查询效率有很大的提升
缺点:索引本身就是占用空间的,在数据量比较大的情况下创建以及删除索引花费的时间是比较长的
说一下多线程
线程和进程之间的关系
操作系统中运行着各个程序,这些程序可以看做是一个个的进程,从粒度上来说,还有一个比进程粒度更小的,那就是线程。
多线程出现的原因
由于计算机的发展,CPU 的核心数量在不断的变多,为了使得计算机的性能得到最大的发挥,所以创造出了使用多线程。多个程序同时高并发或者高并行的执行,使得计算机可以快速的处理程序。
保证线程安全
为什么出现线程的安全问题
共享的变量在多线程同时操作的时候,由于线程的交替执行,导致了线程安全的问题。
线程安全的问题怎么处理?
1、原子类创造出来的原子变量保证了线程安全
java.util.concurrent.atomic 包,遵循比较与替换原则,
2、volatile 保证了某一个变量是线程安全的
保证变量的可见性以及有序性
3、加锁实现线程安全
synchornized 隐式的加锁,Monitor 对象加锁
Lock 接口 显示的声明锁
4、并发工具
Semaphore : 信号量,控制同时访问特定资源的线程数量
CountDownLatch : 允许一个或者多个线程等待其他的线程完成操作
Cyclicbarrier : 让一组线程达到一个屏障是阻塞,直到最后一个到达屏障的时候,屏障才会打开
ThreadLocal 在线程本地存储变量
死锁以及死锁产生的条件
什么是死锁
由于多个线程互相争抢资源从而导致的互相等待,这种现象叫做死锁。
死锁的形成条件
1、资源在同一时间中只能被一个线程占用
2、线程占用了一个资源。并且尝试获取新的资源
3、线程之间形成了环路等待,举个例子:线程 A 占有资源 1 想要获取资源 2 ,线程 B 占有资源 2 想要获取资源 1 ,从而会形成环路等待
怎么解决死锁
谈谈对于 MVC 模式的理解
在程序开发的过程中,为了使得程序开发效率高,程序运行效率高,程序的维护成本降低。所以在开发 Web 应用程序的时候,将一个程序看为三层,也就是 Model View Controller 。
Controller 负责视图和模型之间的交互(直白的说就是把前端发送的请求分配给合适的方法去处理);Model 负责和数据库的交互,获取到需要的数据;View 负责 Model 层数据的展示。
MVC 是一种设计模式,SpringMVC 是一种基于 MVC 设计模式设计的框架。
可以理解为:MVC 是一种思想,Spring MVC 是这种思想的具体实现
说一说对于 IO 多路复用的理解
什么是 IO 的多路复用
使用一个线程同时做到监听多个文件描述符的从而提升性能的技术。
什么是文件描述符(file description FD)
文件描述符在形式上是一个非负整数,是一个索引值,索引指向内核为每一个进程所维护的该进程打开文件的记录表。
内核维护的一张表,表中记录了每个进程打开的相关文件,不同的进程在内核中有不同的记录表。
当程序打开或者创建一个新文件的时候,内核会给进程返回一个文件描述符.
Linux 怎么实现的 IO 多路复用?
下面从建立网络连接的角度说明 IO 多路复用的原理
所谓的 IO 多路复用,就是使用同一个线程同时监听多个文件描述符,一旦监测到了某个文件描述符有我们关心的事件发生(也就是做好了文件读写的准备),那么就通知程序做出相关的操作。
select poll epoll 三种系统调用
linux 使用了 select poll epoll 三种系统调用实现了 IO 的多路复用。这三种不同的系统调用。简单来说就是用户态先将所有的文件描述符传递到内核,内核返回产生了事件的文件描述符,在用户态处理这个产生了事件的文件描述符所表示的文件。
select 系统调用
将文件描述符拷贝到内核态中,文件描述存储在数组中,内核通过遍历检查是否有事件产生,将有事件产生的文件描述符进行标记,标记成为可读或者可写的状态。然后再将文件描述符集合从内核拷贝到用户态中,遍历检查找到可读或者可写的文件,然后进行处理。
缺点
1、需要进行两次文件描述符的拷贝以及遍历,整体的效率是比较低下的。
2、文件描述符的大小是有限制的默认的最大值是 1024 ,存储在 bitsmap 这个数据结构中
poll 系统调用
poll 本质上和 select 是一样的,只不过 poll 将文件描述符存储在链表中,打破了 select 1024 的限制。
优点
文件描述符存储的限制打破了 1024
缺点
需要将文件描述符进行复制以及拷贝,使得效率有很大的下降
epoll
1、用户态使用 epoll_ctl 函数将需要监视的文件描述符放置到内核中的红黑树中
2、当用户调用 epoll_wait 的时候,只会返回有事件发生的文件描述符。epoll 使用了事件动机制,内核中维护了一个链表记录就绪事件,当某个事件发生时,通过回调函数,内核会将该时间放入到就绪事件链表中。
边缘触发和水平触发
两者的区别就是内核传递给用户态的数据的次数,边缘触发就是只会传递一次,水平触发可 能传递多次
JVM 什么时候触发 GC ,如何减少 FullGC 的次数?
触发 Minor GC
当 Enen 和一块 survior 中的内存空间使用完之后,会将 Eden 区域和 Survior 区域中的存活对象拷贝到另外一个 Survior 区域中。
触发 FullGC 条件
1、开发人员调用 System.gc 的时候,系统建议执行 Full GC 但是不是一定会执行的
2、老年代空间不足的时候
3、从 Enen From Survivor 到 to Survivor 区域的时候,老年代无法存储大对象,可以直接进行一次 Full GC
减少 FullGC 的办法
1、增加老年代的空间
2、减少新生代的空间
3、禁止使用 System.gc() 方法
4、排查代码中没有用的大对象
JVM 如何确定对象是可以回收的?
1、引用计数法
给对象上面增加一个引用计数器,每次有一个引用引用到这个对象的时候,计数器的数量加 1 ;引用失效的时候,计数器的数量减少 1 即可。
但是存在一个问题就是: 因为闭环的相互依赖,从而导致的内存泄漏问题,本来没有被使用的多个对象,由于互相的依赖,导致了内存控件不能被回收。
2、可达性分析算法
JVM 中设置了一个 GC Roots , 判断一个对象是不是应该回收,看该对象到 GC Roots 对象之间有没有引用链的存在。
常见的 GC Roots 对象
1、虚拟机栈,也就是栈帧中引用的对象
2、Java 类里面 引用类型的静态变量
3、被 Synchornized 持有的对象(锁对象)
…
对象怎么直接到老年代
在对象头上,会标记对象存活的年龄,经过了多次 Minor GC 之后还能存活下来的对象,一般设置的默认存活年龄是 15 。之后这个大龄对象会直接进入老年代。
为什么老年代不嫩使用标记复制算法?
因为老年代中的对象存活的时间都是比较长的,一旦使用了标记复制算法的话,那么会造成大量的对象的复制,使得整个系统运行的效率会下降。
新生代为什么分为 Enen 和 Survivor 区域?
首先是为了更好的 GC ,分为不同的区域,存活的对象移动到一个区域,需要清理的对象移动到另外一个区域,然后将另外一个区域无差别清理。
分为这个区域是为了更好的进行 标记 - 复制 算法的实现。标记复制算法的思想就是:将有用的对象放到另外一块区域中,然后将没有用的另外一块区域中的所有对象都清理掉。
因为在新生代创建出来的对象大部分的存活时间比较短。所以两块区域一半一半是有问题的,对于内存是一定的浪费,所以使用了 Endn 和 Survivor 区域的划分是 8 : 1;
为什么使用两块 Survivor 区域?
为了减少内存中的碎片化
只有一块区域的时候,Endn 以及 Survior 区域满了,那么里面没有用的对象都会被清理掉。
等到第二次垃圾回收的时候,那么 Survivor 区域中,有的地方被回收了,有的地方没有被回收,那么就会造成内存空间的碎片化。
如果存在两块 Survivor 区域,每次都保证一块区域中是空的,将所有存活的对象移动到这个空的 Survivor 区域中,其余的区域都被清空,这样一来就解决了内存空间碎片化的问题。
什么是文件描述符?
在人和电脑交互的时候,我们寻找一个文件是根据文件名字来找的。到了操作系统的层面,这样做肯定效率是比较低下的,并且拿着一个字符串到处找显然也是不合适的。
所以在操作系统层面,用户态以及系统态之间的文件的 open read write 系统调用通过一种文件描述符来对文件进行标识。在此引出啦文件标识符的含义:文件描述符是一个非负整数,本质上是一个索引值。
当用户态想要打开一个文件,只能委托到内核态,因为自身没有操作文件的权限。在内核态将文件打开之后,会返回给用户态一个文件标识符,之后想要对什么文件进行操作,两者之间就使用这个标识符彼此标识文件,彼此凭借着这个文件标识符(非负整数)精确的找到一个文件。
什么是 AOP
这是一种编程思想,就是大佬们想出来的一种写代码的思想,就是在不改变原来的代码的基础上,对原来的代码进行功能增强。
这种思想的组成概念有下面几个(为了支撑这种思想的实现,起了一些花里胡哨的名字支撑):
连接点:所有的方法都可以作为连接点,不管是需要增强的方法还是不需要功能增强的方法都可以作为连接点
切入点:范围比连接点小一些,也就是需要进行功能增强的方法,有了方法不需要增强,所以只是一个连接点。
通知:对原来方法中代码增强的部分。也就是需要增加的功能的代码。
通知类:增强代码所在的类叫做通知类(花里胡哨的起名字)。
切面:连接增强代码以及需要增强的方法的纽带。也就是连接通知和切入点的纽带