java知识点-面试常问

  1. CPU指令乱序执行(不准确,具体自个百度)

    cpu乱序执行是为了提高程序执行效率,因为cup的执行速度大概是内存的100倍,假如CPU发起一个指令去内存中读数据,但等数据回来CPU要干等99个时钟周期然后才能执行第二条指令。
    所以在前后两条指令没有依赖关系的情况下,b可能会在a的前面执行。
    
  2. 单例模式

意思是一个类在应用程序中只存在一个实例化对象。

单例模式的好处:
单例和多例的区别就是单例通过getBean获取到的都是同一个bean对象,而多例是get一个创建一个,单例的好处就是省去了这些个无用对象的垃圾回收与创建,他要执行getBean的时候第一时间会判断这个对象是否为null,如果有直接使用,还省去了创建对象、分配内存空间的时间和内存空间。
单例坏处:
如果是有状态的话在并发环境下线程不安全。

饿汉式:
	类被加载到内存即实例化一个对象,无论将来用到与否。
	(不用就实例化may浪费空间)
懒汉式:
	初次new对象的时候实例化一个对象,之后请求都返回该实例。
DCL:double check lock
	单单使用饿汉或懒汉会因instance==null不是原子性操作会new多个实例化对象,所以使用双重检查。

dcl:=>
private static volatile Person INSTANCE;
if(instance==null){
	synchronized(Person.class){
		if(instance==null){
			INSTANCE=new Person();
		}
	}
}
return instance

1.为什么需要第一个判断=>
若没有第一个判断,当有1000个线程过来,虽然只有一个线程会去new这个对象,但1000个线程都会参与锁的竞争,效率太低。
加上if判断,1000个线程一个new完了,其他的只需要判断一下就行,返回该实例,不用参与锁竞争了,效率就提高了。

2.为什么加volatile=>
防止指令重排,new 对象会分为三步:
1.在堆内存中开辟一块空间,赋予默认值(int->0)
2.赋初始值
3.栈内存的引用指向堆内存地址
假如2 3指令发生重排,则引用指向的就是一个半初始化的对象,对象不为null,但始终为0(没被初始化),这样单例创造出的实例对象在内存中一直存在这么一个半实例化的对象。
  1. java引用类型
1. 强引用类型
	执行system.gc,不会被垃圾回收,直到把这个对象设置为null,无引用指向他时才被回收。
2. 软引用
	执行system.gc,内存够用不会被回收,不够则回收。
3. 弱引用
	执行gc就回收
4. 虚引用
	

4.进程线程

桌面app->双击:app资源存在于硬盘,双击后就被加载进内存中,这个app.exe进程就启动了,进程是资源调度的最小单位。

app资源被加载进内存就首先启动一个main线程,main线程中可能会调用N多个线程,线程是进程的最小单位。

线程执行中,可能有N多个,他们会被CPU调度执行,这个调度分配由操作系统进行的,CPU不会让他的这些个孩子饿着,也不会紧着一个执行,所以会被轮询等模式进行调度,A线程抢夺资源被CPU执行,执行一定时间换B执行A就被CPU缓存起来,B加载并执行后再被缓存起来,A被释放出来继续执行,这一个线程切换到另一个线程叫上下文切换,上下文切换是需要时间的,所以不是CPU越多越好。
  1. cas(CompareAndSwap)
假如主内存中有一个变量 a=0 ,此时有N个线程需要用到这个变量,在线程执行时就去拿到并做处理,比如执行a+1,然后会写回主内存中.
但是问题来了,若A线程拿到后a=0后并做了a+1操作还没写回去时发现B线程已经对a进行操作并写回了,B修改后的值a=10.
按正常逻辑,a执行a+1,应该写回10+1=11的,所以A并不会把0+1=1的这个值写回主内存中,而是会拿这个1和主内存中的10比较是否相同,不相同会把主内存的值拿到并执行a+1操作并写回,若发现主内存中值又被改变了那就再拿,直到两者一样,写回成功。(CompareAndSwap)

ABA问题
但是第二个问题来了,若A执行a+1后,去主内存中比较发现两者是一样的,而其实主内存中的值是经过B修改后(10)->C修改后(0)产生的,也就是经过了0->10->0的过程。
解决ABA问题:加版本号。

cas原子性问题
当A线程会写回进主内存时会比较if(a==0){执行写回},但是if判断是非原子性操作,因为有可能在刚比较成功还没写回时,其他线程把它变成8了。
解决原子性问题:加lock锁。

  1. 什么情况需要序列化?
当数据需要在网络上进行传输的时候需要进行序列化,序列化是指把对象转成二进制流的过程,因为数据在网络上传输仅支持二进制、二进制在网络上传输就是高低电平0v或5v,二进制数据通过数模转换把数字信号转成电信号,电信号到达目标主机在通过数模转换变成二进制文件,然后通过反序列化,把二进制文件变成对象,这就是序列化和反序列化的过程。
然后数据库进行持久化io操作时也需要序列化,链接数据库就类似网络传输,因为数据库也是一个服务器,与数据库连接也需要类似三次握手这样的操作,所以与数据库相连需要耗费时间和资源,因为要创建连接就需要创建连接对象,频繁的创建和断开需要创建时间和对象垃圾回收,所以引出数据库连接池,当有线程要与数据库连接进行io操作时,就从线程池中取,使用完毕后释放资源再归还到连接池中,这么做可以极大提高程序执行效率。

1 需要序列化的类一般会实现serializable接口
2 被static和transient标记的变量不会被序列化
3 serialVersionId=1L这个东西不写的话jvm会默认给他生成一个,他是为了防止在反序列化过程文件被恶意篡改。
  1. 四次挥手

    1. client端要断开连接时会给server端发送一个fin信号
    2. server端收到后会立刻发送一个ack确认收到的信号
    3. 当server端不在有数据需要传输了就会给client端发送fin信号
    4. client收到fin信号后会给server端发送一个ack信号,不会直接断开连接,而是会过一段时间才会,这个时间是tcp协议规定好的。
    
    说一下为何需要第四次握手
    IP协议是不可靠的,可能发生丢包的情况,最后这个ack信号丢失,server
    端没有收到client的最后一个ack信号就会想是不是发生了丢包的情况client
    端没收到fin信号?然后就会再发送一个信号请求断开连接。
    
    而client端等n秒再断开链接的原因就是防止最后一个信号丢失。因为如果
    丢了,server端就会在这个等待的这个时间内再次发送请求断开连接的信
    号。
    
  2. 两次握手行不行

假设两次握手client和server就建立连接:
client发送一个连接的请求,但是网络拥堵,这个请求堵在网络上了,过了一段时间client
发现server没有回应就会认为这个包丢失了,于是就会再次发送一个请求,这个请求到达
后server端发送一个ack表确认连接,根据假设此时双方就可以发送信息进行通信了。
但是双方建立连接后第一个请求到达server端了,server端一看,又发送一个请求,于是就
发送ack表要建立连接,此时client收到这个ack信息就会想,我不需要与你建立连接,就把
他当垃圾给扔了,但根据假设,双方已经建立了链接,server端就会一直等client给他传输
数据。浪费资源。
  1. jvm
栈:线程私有、先进后出
程序为每一个线程创建一个栈,所以栈是线程私有的,这个线程里的每一个方法的调用就
对应着栈里的一个栈帧,栈帧就是每一个方法的独立空间,栈帧存放对应方法的局部变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UyzM6ziB-1617273763148)(C:\Users\23981\AppData\Roaming\Typora\typora-user-images\image-20201206172656076.png)]

  1. string、stringbuilder、stringbuffer
线程安全的意思是当多个线程访问同一资源时,这个对象不需要进行加锁和同步处理,输出的结果依然是正确的。

stringbuilder多线程访问同一资源是线程不安全的。
但是
public void add{
	StringBuilder sb = new StringBuilder();
	sb.append("***");
}
这个方法在程序运行中是线程安全的,不用考虑加锁问题
是因为每一个线程访问此方法,这个stringbuilder对象都会在每个栈帧中被new出来,而这个stringbuilder对象是属于本线程的,每个线程创建的这个sb对象存在堆中不同的位置,不存在多线程共享一个资源。
  1. 自动装箱拆箱
Integer it1 = new Integer("1");
Integer it2 = new Integer("1");
println(it1==it2) -> false
因为两者是引用类型,所以比较的是地址值,只是两者的值正好相同而已

Integer it = new Integer("1");
int a = 1;
println(a==it) -> true
一个引用类型一个基本类型,引用类型会自动拆箱与a相比较,此时比的是数值是否相等,所以是true;
只要是包装类和基本类型比都是数值比较。

Integer a = 1;=> Integer.valueOf("1")
Integer b = 1; => Integer.valueOf("1")
println(a==b) -> true
a和b都在[-128,127]之间,所以直接在缓存中拿,没有new,所以相等

Integer a = 129;=> new Integer("129")
Integer b = 129; => new Integer("129")
println(a==b) -> false
对于integer不在[-128,127]之间的都用new创建
  1. ArrayList和linkedlist
ArrayList和linkedlist都是有有序的、值是可重复。

ArrayList因为存储是连续的空间,所以可以根据索引直接定位到具体的元素,所以查找元素会快,插入删除需要数据迁移,所以速度慢。数据迁移是指当要往数组中间插一个元素时,后面的元素都往后移动一位,新元素再插入。

linkedlist因为存储不连续,所以无法通过索引直接定位,要查找一个元素时只能从头往后遍历,所以查找速度慢,插入删除时只需要前后的指针变一下即可,所以插入删除快。

但是

快慢都不是绝对的。
比如我要查找b这个元素时,ArrayList无法通过索引定位,所以只能和linkedlist一样从头往后遍历,所以两者速度差不多。
假如要插入或删除一个元素在两者的末尾,那ArrayList也不用数据迁移了,而linkedlist是双向链表实现的,头指向尾,尾指向头,所以删除和插入时不会从头遍历到末尾,发现没有元素时再进行插入删除操作,而是直接就删除了,所以两者速度也差不多一样。
  1. 运行时异常和非运行时异常
运行时异常是不可查的
1. 空指针
2. 数组越界
3. 类型转换异常
4. 算数运算异常(1/0)

非运行时异常是可预料到的可能发生的错误
1. ioException
2. SQLException
3. noSuchMethodException
4. fileNotFoundException
  1. 创建线程的方式有几种
1. 继承thread
2. 实现runable
3. 实现callable(有返回值有泛型)

本质上来说只有一种就是通过继承thread来实现的
因为实现runable接口并重写里面的run方法并不能直接调用start来开启线程,最终执行还是通过new thread来开启。
比如:class MyThread implement Runnable{override run()}
开启:new Thread(new MyThread()).start();

实现runnable接口重写run方法相当于创建了一个可执行任务,执行的话需要new一个线程来开启一个线程来执行这个任务。
  1. 线程的六种状态
1. 新建<-new Thread();
2. 就绪<-thread.start();
3. 阻塞  没有获取到锁,其他线程被执行,本线程则进入阻塞状态
	当锁被释放,重新进入就绪状态
4. waiting  当调用wait()或join()时进入此状态
	当join的线程结束或调用notify,进入就绪状态,等CPU调度
5. timed waiting  当调用wait(time)或sleep(time)进入此状态
	休眠结束或调用notify可使该线程进入就绪状态,等cpu的调度。
6. terminaled 程序结束、终止
  1. threadLocal是个what
https://baijiahao.baidu.com/s?id=1653790035315010634&wfr=spider&for=pc
  1. 类加载器、机制
类加载器有三种:
1. 根类加载器
2. 扩展类加载器
3. 系统类加载器(第三方jar和自己开发的jar包)

类加载器听着挺高大上挺玄乎,其实就是一个个的类组合成的一个jar包,
双亲委派机制就是我调用一个类的时候,首先并不是到自己写的那个jar包里去调用这个方法接口,而是直接扔给它的父类扩展类加载器,扩展类加载器拿到后又抛给他的父类,到跟类加载器(jar包)里面找有没有同名的类,如果有就调用根类加载器里的这个同包同名的方法,若没有则看扩展类加载器里面有没有,在没有的话才会去调用自个写的那个包下的方法。
这么做的好处是:
1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。
  1. jsp和servlet的区别
JSP是在HTML代码里写JAVA代码,框架是HTML;而Servlet是在JAVA代码中写HTML代码,本身是个JAVA类。
两者的侧重点不同,jsp主要方便数据展示,servlet主要方便页面跳转,请求转发,类似spring中的controller层。
  1. 转发和重定向的区别
https://www.cnblogs.com/kingofjava/p/10761679.html
  1. synchronized和lock的区别
synchronized有jvm实现,遇到异常会释放锁,并不会出现死锁的情况
lock一般会搭配unlock方法,若是忘加了会出现死锁,一般unlock加在finally里面。
  1. ArrayList和StringBuilder线程不安全点在哪
ArrayList初始容量是10,StringBuilder初始容量是16
ArrayList和StringBuilder在多线程情况下不安全是在扩容的时候发生
ArrayList:
	a、b线程在ArrayList容量在9的时候判断还是否有空位加入一个元素,此时ab均发现还有空位就都不会执行扩容,此时a抢到CPU资源执行add操作,执行完毕后b再进行add的时候会拿现容量值加一,然后插入到这个位置,但是此时容量等于10已经满了,就会插入到索引为11的位置,所以就报数组越界异常。
StringBuilder:
	在剩一个字节的时候,ab线程均要执行字符串相加一个字节的操作,ab均会执行count+length(现容量加要添加的字节长度)判断容量够用,所以a先执行append操作,b在执行的时候就抛出数组越界异常。

  1. spring事务传播特性
  2. pagehelper分页原理
https://www.bilibili.com/video/BV1k74118721?p=57
  1. 说说你对Redis的了解

  2. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vpZCCzlP-1617273777552)(C:\Users\23981\AppData\Roaming\Typora\typora-user-images\image-20201215232713024.png)]

redis属于非关系型数据库,常用的数据类型有五种,不常用的用三种,五种常用类型有
string、hash、set、zset、list。
redis常用作数据库缓存,主要存储不经常操作的数据,读是主要的,比如热点新闻。它的
存在是因为若有大量的访问要请求数据库,会导致数据库的访问压力过大、满负荷运转会
直接导致数据库宕机,重要的是数据库资源存在于硬盘,硬盘数据读取也叫io,磁盘转动
磁头水平移动进行数据定位,读取数据相对来说很慢。
而redis存在于内存当中,论运行速度来讲,硬盘<内存<cpu,redis的读写速度都是超快
的,所以引入redis来作为中间件做数据缓存,当请求来时,首先查看缓存中是否用要请求
的数据资源,有则直接取出并返回,没有在查数据库。
但是redis存在于内存当中,假若停电或服务器宕机,那么存在于内存中的数据就会全部丢
失,所以redis提供了两种持久化机制,aof和rdb,默认是rdb,两种的区别是,其中一种是
每隔多少秒有n个数据被改变则进行一次持久化,另一种是采用追加append的方式,只要
有请求操作数据库(增删改),就会追加进持久化文件,因为他是不管这些操作有用没用,
比如我插入a再删除a,这两条SQL都会被追加进aof文件,所以他会导致这个文件一直增
大,会占很多空间,这两种方法各有各的好处。
因为有中间的redis做缓存所以数据库的压力会变小,但是一些热点数据做缓存预热存储在
Redis中会设置过期时间,所以当某个热点数据在某个时间点过期,而大量请求就会绕过缓
存直接请求数据库,会导致数据库因为缓存击穿直接宕机,解决方法是设置热点数据不过
期。
假设大量请求来请求缓存中没有的值,又直接打到数据库,这叫缓存穿透,解决方式是当
请求打到数据库中没有的值时,返回一个null并写入缓存中。
假若大量热点数据在同一时间失效,请求又打到数据库,这叫缓存雪崩。

但是若有人利用大量的不存在的IP来访问数据库中不存在的值时,按照缓存穿透的解决方
式把null返回并写入内存中,那么内存很快就会被这个null给占据大量空间,是不合理的。
所以此时为解决这个问题,可以在查询缓存之前引入布隆过滤器。
布隆过滤器的原理很简单,假若数据库中有十条数据,查询数据的时候是根据id来查询          
的,我们可以把这些个ID提前存入布隆过滤器中,布隆过滤器是通过n个hash函数加比特
数组实现的,数组中只存0或1,存入过程是拿到一个id分别做n个hash函数,就是求余,在
对应位置记上1,十个id均做这样的处理。之后若有请求携带不存在的id来攻击数据库时,
经过布隆过滤器会在误差范围内阻止其访问数据库。
  1. 什么是tcp链接
tcp:面向连接、安全可靠
tcp属于端对端传输,要想建立连接必须经过三次握手,三次握手就是客户端可服务器必须
经过三次通信才能确定双方接收和发送信息无误。第一次客户端发送syn证明不了什么,但
当服务器收到并回复syn+ack,客户端知道服务器发送信息没问题,然后客户端再次回复
ack后,服务器才知道客户端也可以进行数据传输了。

建立完三次握手后,客户端发送数据包会经过osi七层参考模型,在传输层进行数据分割,
ip层加上本机ip地址和目标主机IP地址,经链路层加上局域网的MAC地址,当数据包到达
路由器,路由会去掉外层的MAC地址并根据目标IP地址确定下一跳给哪个路由转发,确定
后就会在数据包上加上下一个路由的MAC地址,然后逐跳转发,路由本身自带缓冲区消息
队列,当链路拥堵,路由器就会扔掉后面发来的数据包并且不会给发送端反馈,数据包丢
失由传输层的TCP协议进行重传,数据包被拆分发送途中经由的路由链路并不是固定的,
链路上的拥塞情况也不确定,可能会造成a先于b发送但晚到于b的情况,当数据包到达目
标主机后会经由TCP协议进行包重组。
  1. vpn
VPN的翻墙的实现原理就是数据包背着数据包发送传输。
比如我想要访问Google并且我在香港有一台服务器,我发送一个数据包(IPA-S)到香港,但是这个数据包里还携带着一个数据包(ipX),里面这个数据包是访问Google的请求,当我发送的这个数据包到达香港服务器时,里面那个包会被拆解出来,再次请求会发送到国外Google服务器,响应回来的时候同样先到香港再封装再返回。这就是翻墙。
  1. 子网掩码存在的意义

  1. bio效率低下的点在哪
SocketServer s = new SocketServer();
while(true){
	Socket s = s.accept();
	s.getInputStream().read();
	s.getOutputStreamwrite();
}
并发情况下阻塞的点:
1. accept 阻塞一直等到一个client端连接过来,才继续往下走。
2. read socket建立,但是client只连不写,server就read不进来,就	 会阻塞。
3. write server写,client不接收,write就会阻塞。
  1. IO
io指的是input和output操作,参考对象可以是磁盘也可以是内存,当以磁盘作为参考,把数据从磁盘读到内存当中就叫做output,反之就是input。io也叫io流,有字节、字符输出输入流,为了读取写入操作速度更快会引入buffer作为缓冲区。
而通常有io操作的时候都会有序列化操作,因为io是在链路上传输的是二进制,所以就需要序列化操作,序列化可以通过jdk自带的serialize接口或者hission,而序列号类一般都会有一个versionid,balabala作用。
当ab两个线程要进行读取文件的时候,当a执行到read(buffer)这个操作时,cpu会向磁盘发送一个读取请求,因为CPU运行速度远高于磁盘io,这个读取的时间不固定,假设文件比较大,他读取的时间假设是十秒,线程就不可能一直阻塞到读取操作执行完毕,因为CPU的资源是很宝贵的,并且就绪队列还有嗷嗷多的线程在等待,所以当给磁盘发起这个读请求的时候,该线程就会被中断被放进阻塞队列中,并记录a的状态以便下次执行接着进行,但磁盘往内存中读的操作还在继续。a被阻塞,b就会被从就绪队列唤醒开始执行,因为CPU不可能让其他的孩子都饿着,所以会分配时间片,让每个线程执行相同的时间,b的时间片执行完毕后,操作系统会保存b的执行状态,并寄存器指向c线程,这个线程切换就叫上下文切换,上下文切换是需要cpu记录并保存线程状态,所以是挺耗CPU资源的一件事。
当a读取完毕后就会发起一个中断指令,CPU收到后就会把a线程从阻塞队列中拿出放到就绪队列中,等待下一个时间片,就会继续执行。
线程的六种状态:新建、就绪、执行、阻塞、长阻塞、结束。
处理器映射器
分为两种
BeanNameUrlHandlerMapping:处理通过xml配置的控制器(<bean name-"/xxx")>
RequestMappingHandlerMapping:处理通过注解的控制器(@RequestMapping)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值