Java面试临时抱佛脚必备系列(含答案)

版权声明:如笔记。一点一滴,伴我成长。可转载 https://blog.csdn.net/Butterfly_resting/article/details/89735792

其他面试题类型汇总:
Java校招极大几率出的面试题(含答案)----汇总
几率大的网络安全面试题(含答案)
几率大的多线程面试题(含答案)
几率大的源码底层原理,杂食面试题(含答案)
几率大的Redis面试题(含答案)
几率大的linux命令面试题(含答案)
几率大的杂乱+操作系统面试题(含答案)
几率大的SSM框架面试题(含答案)
几率大的数据库(MySQL)面试题(含答案)
几率大的JVM面试题(含答案)
几率大的现场手撕算法面试题(含答案)
临时抱佛脚必备系列(含答案)

几率大的网络安全什么是SQL注入攻击

攻击者在HTTP请求中注入恶意的SQL代码,服务器使用参数构建数据库SQL命令时,恶意SQL被一起构造,并在数据库中执行。如何防范SQL注入攻击
Web端进行有效性检验并限制字符串的长度,服务端使用预编译PrepareStatement取代拼接SQL字符串,并再进行有效性检查防止攻击者绕过WEB段请求,过滤参数中特殊的符号比如单引号双引号。

什么是三次握手四次挥手?tcp为什么要三次握手?

为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据。
在这里插入图片描述
四次挥手:

  1. 客户端先发送FIN,进入FIN_WAIT1状态
  2. 服务端收到FIN,发送ACK,进入CLOSE_WAIT状态,客户端收到这个ACK,进入FIN_WAIT2状态
  3. 服务端发送FIN,进入LAST_ACK状态
  4. 客户端收到FIN,发送ACK,进入TIME_WAIT状态,服务端收到ACK,进入CLOSE状态

第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不 会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。
第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。
第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。
在这里插入图片描述

(很重要)几率大的JVM

JVM调优
对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数。
1)监控GC的状态,使用各种JVM工具,查看当前日志,并且分析当前堆内存快照和gc日志,根据实际的情况看是否需要优化。
2)通过JMX的MBean或者Java的jmap生成当前的Heap信息,并使用Visual VM或者Eclipse自带的Mat分析dump文件
3)如果参数设置合理,没有超时日志,GC频率GC耗时都不高则没有GC优化的必要,如果GC时间超过1秒或者频繁GC,则必须优化
4)调整GC类型和内存分配,使用1台和多台机器进行测试,进行性能的对比。再做修改,最后通过不断的试验和试错,分析并找到最合适的参数

JVM内存区域的划分:

程序计数器,
每个线程都有它自己的程序计数器,并且任何时间一个线程都只有一个方法在执行。程序计数器会存储当前线程正在执行的 Java 方法的 JVM 指令地址;如果是在执行本地方法,则是未指定值(undefined)。(唯一不会抛出OutOfMemoryError)
第二,Java 虚拟机栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的 Java 方法调用。JVM 直接对 Java 栈的操作只有两个,就是对栈帧的压栈和出栈。栈帧中存储着局部变量表、操作数栈、动态链接、方法正常退出或者异常退出的定义等。
**第三,堆,**它是 Java 内存管理的核心区域,用来放置 Java 对象实例,几乎所有创建的 Java 对象实例都是被直接分配在堆上。堆被所有的线程共享。
**第四,方法区。**这也是所有线程共享的一块内存区域,用于存储所谓的元(Meta)数据,例如类结构信息,以及对应的运行时常量池、字段、方法代码等。Oracle JDK 8 中将永久代移除,同时增加了元数据区。
**第五,运行时常量池,**这是方法区的一部分。Java 的常量池可以存放各种常量信息。
第六,本地方法栈。它和 Java 虚拟机栈是非常相似的,支持对本地方法的调用,也是每个线程都会创建一个。

G1垃圾收集器:

G1 GC 这是一种兼顾吞吐量和停顿时间的 GC 实现,是 Oracle JDK 9 以后的默认 GC 选项。G1 可以直观的设定停顿时间的目标。G1 GC 仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个 region。Region 之间是复制算法,但整体上实际可看作是标记 - 整理(Mark-Compact)算法。
JVM 会尽量划分 2048 个左右、同等大小的 region。数值是在 1M 到 32M 字节之间的一个 2 的幂值数,
G1 也会根据堆大小自动进行调整,也可以手动调整。在 G1 实现中,一部分 region 是作为 Eden,一部分作为 Survivor,Old region,G1 会将超过 region 50% 大小的对象归类为 Humongous 对象。
在新生代,G1 采用的是并行的复制算法(所以同样会发生 Stop-The-World 的暂停。)
老年代使用的mixed gc,会回收整个新生代,还会回收一部分的old region
1.YoungGC年轻代收集
在分配一般对象(非巨型对象)时,当所有eden region使用达到最大阀值并且无法申请足够内存时,会触发一次YoungGC。每次younggc会回收所有Eden以及Survivor区,并且将存活对象复制到Old区以及另一部分的Survivor区。
2.mixed gc
当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即mixed gc,该算法并不是一个old gc,除了回收整个young region,还会回收一部分的old region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。

缺点:region 大小和大对象很难保证一致,这会导致空间的浪费。

MySQL常见的存储引擎InnoDB、MyISAM的区别?

1)事务:MyISAM不支持,InnoDB支持
2)锁级别: MyISAM 表级锁,InnoDB 行级锁
MySQL表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。
InnoDB行锁是通过给索引项加锁来实现的,即只有通过索引条件检索数据,InnoDB才使用行级锁,否则将使用表锁!
3)MyISAM存储表的总行数;InnoDB不存储总行数;
4)MyISAM采用非聚集索引,B+树叶子存储指向数据文件的指针。InnoDB主键索引采用聚集索引,B+树叶子存储数据
5)适用场景:
MyISAM适合: 插入不频繁,查询非常频繁, 没有事务。
InnoDB适合: 要求事务; 表更新和查询都相当的频繁

线程的状态有哪些 :

状态:在 Java 5 以后,线程状态被明确定义在其公共内部枚举类型 java.lang.Thread.State:
新建(NEW)new,表示线程被创建出来还没真正启动的状态
就绪,运行(RUNNABLE)runnable,表示该线程已经在 JVM 中执行,当然由于执行需要计算资源,它可能是正在运行,也可能还在等待系统分配给它 CPU 片段,在就绪队列里面排队。
阻塞(BLOCKED)blocked,阻塞表示线程在等待 Monitor lock。
等待(WAITING)waiting,表示正在等待其他线程采取某些操作。
计时等待(TIMED_WAIT)timed_wait:调用的是存在超时条件的方法进入等待状态,比如 wait 或 join 等方法的指定超时版本,
终止(TERMINATED)terminated,不管是意外退出还是正常执行结束,线程已经完成使命,终止运行,也有人把这个状态叫作死亡。
在这里插入图片描述

扫描二维码关注公众号,回复: 6081976 查看本文章

ThreadLocal的底层原理

ThreadLocal,该类提供了线程局部 (thread-local) 变量,ThreadLocal会为每个线程创建变量的副本,线程之间互不影响,这样就不存在线程安全问题。在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals用来存储实际的变量副本容器, 键值为当前ThreadLocal变量,value为变量副本。
1)初始时,在Thread的threadLocals为空,调用ThreadLocal变量调用get()方法或者set()方法,就会对threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals中。
2)然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

voliate 的实现原理

volatile可以保证线程可见性和禁止指令重排序,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。加入volatile关键字时,汇编后会多出一个lock前缀指令。lock前缀指令其实就相当于一个内存屏障。
Java 并发类库提供的线程池有哪几种?
Executors 目前提供了 5 种不同的线程池创建配置:
1)newCachedThreadPool():用来处理大量短时间工作任务的线程池。当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;其内部使用 SynchronousQueue 作为工作队列。
2)newFixedThreadPool(int nThreads),重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。
3)newSingleThreadExecutor(),它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态
4)newSingleThreadScheduledExecutor() 和 newScheduledThreadPool(int corePoolSize),创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。
5)newWorkStealingPool(int parallelism),Java 8 才加入这个创建方法,并行地处理任务,不保证处理顺序。
6)ThreadPoolExecutor():是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装。
线程池需要要的一些核心参数。
corePoolSize:基本线程数量
maximumPoolSize:最大线程数量。
keepAliveTime:空闲线程的存活时间 当实际线程数量超过corePoolSize时,若线程空闲的时间超过该值,就会被停止。
timeUnit:keepAliveTime的单位
runnableTaskQueue:任务队列 这是一个存放任务的阻塞队列,可以有如下几种选择:
ArrayBlockingQueue 由数组实现的阻塞队列,FIFO。
LinkedBlockingQueue 由链表实现的阻塞队列,FIFO。 fixedThreadPool使用的阻塞队列就是它。 它是一个无界队列。
SynchronousQueue 没有存储空间的阻塞队列,任务提交给它之后必须要交给一条工作线程处理;如果当前没有空闲的工作线程,则立即创建一条新的工作线程。 cachedThreadPool用的阻塞队列就是它。 它是一个无界队列。
PriorityBlockingQueue 优先权阻塞队列。

handler:饱和策略 当实际线程数达到maximumPoolSize,并且阻塞队列已满时,就会调用饱和策略。
AbortPolicy 默认。直接抛异常。 CallerRunsPolicy 只用调用者所在的线程执行任务。 DiscardOldestPolicy 丢弃任务队列中最久的任务。 DiscardPolicy 丢弃当前任务。

synchronized和ReentrantLock有什么区别呢?

1)synchronized 是 Java 内建的同步机制,它提供了互斥的语义和可见性,当一个线程已经获取当前锁时,其他试图获取的线程只能等待或者阻塞在那里。
ReentrantLock,通常翻译为再入锁,是 Java 5 提供的锁实现,语义和 synchronized 基本相同。但是ReentrantLock 提供了轮训、超时、中断,设置为公平锁等很多实用的方法,使用起来比较灵活。
2)ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
3)ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。

可重入锁。ReentrantLock和synchronized都是可重入锁。
可中断锁。synchronized是不可中断锁,而ReentrantLock则提供了中断功能。
公平锁与非公平锁。synchronized是非公平锁,而ReentrantLock的默认实现是非公平锁,但是也可以设置为公平锁。

ReentrantLock 底层实现

ReentrantLock实现了Lock接口,是AQS的一种。加锁和解锁都需要显式写出,注意结束操作记得unlock释放锁。它内部自定义了同步器Sync,这个又实现了AQS,同时又实现了AOS,而后者就提供了一种互斥锁持有的方式。其实就是每次获取锁的时候,看下当前维护的那个线程和当前请求的线程是否一样,一样就可重入了。它还提供了获取共享锁和互斥锁的方式,都是基于CAS对state操作而言的。

spring bean 的生命周期

1.Spring 容器根据配置中的 bean 定义中实例化 bean。
2. Spring 使用依赖注入填充所有属性,如 bean 中所定义的配置。
3. 如果 bean 实现 BeanNameAware 接口,则工厂通过传递 bean 的 ID 来调用 setBeanName()。
4. 如果 bean 实现 BeanFactoryAware 接口,工厂通过传递自身的实例来调用 setBeanFactory()。
5. 如果存在与 bean 关联的任何 BeanPostProcessors,则调用 postProcessBeforeInitialization() 方法。
6. 如果为 bean 指定了 init 方法( 的 init-method 属性),那么将调用它。
7. 最后,如果存在与 bean 关联的任何 BeanPostProcessors,则将调用 postProcessAfterInitialization() 方法。
8. 如果 bean 实现 DisposableBean 接口,当 spring 容器关闭时,会调用 destory()。
9. 如果为 bean 指定了 destroy 方法( 的 destroy-method 属性),那么将调用它。

IOC底层实现原理

IoC,控制反转 ,是一种设计思想,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。 是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方。它是通过反射机制+工厂模式实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在Map中的类属性注入到类中。
DI—Dependency Injection,即“依赖注入”由容器动态的将某个依赖关系注入到组件之中。

AOP底层实现原理

面向方面编程,利用一种称为“横切”的技术,剖解开封装的对象内部。将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来。便于减少系统的重复代码,降低模块间的耦合度。实现AOP的技术,主要分为两大类:一是采用动态代理技术,Spring默认使用Jdk动态代理,如果目标类不是接口选择cglib动态代理,二是采用静态织入的方式。

Spring MVC 运行流程

1). 用户发请求–>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制。
2).DispatcherServlet–>HandlerMapping,HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器,多个HandlerInterceptor拦截器)。
3).DispatcherServlet–>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器。
4).HandlerAdapter–>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理,并返回一个ModelAndView对象(包含模型数据,逻辑视图名)
5).ModelAndView的逻辑视图名–>ViewResolver,ViewResoler将把逻辑视图名解析为具体的View。
6).View–>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构
7).返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户。
在这里插入图片描述

HashMap底层原理

HashMap实现Map接口,非线程安全的,区别于ConcurrentHashMap。允许使用null值和null键,不保证映射的顺序.底层数据结构是一个“数组+链表+红黑树“
put():

  1. 根据key计算得到key.hash = (h = k.hashCode()) ^ (h >>> 16);
  2. 根据key.hash计算得到桶数组的索引index = key.hash & (table.length - 1),这样就找到该key的存放位置了:
    ① 如果该位置没有数据,用该数据新生成一个节点保存新数据,返回null;
    ② 如果该位置有数据是一个红黑树,那么执行相应的插入 / 更新操作
    ③ 如果该位置有数据是一个链表,分两种情况一是该链表没有这个节点,另一个是该链表上有这个节点,注意这里判断的依据是key.hash是否一样: 如果该链表没有这个节点,那么采用尾插法新增节点保存新数据,返回null; 如果该链表已经有这个节点了,那么找到該节点并更新新数据,返回老数据。
    3.更新size和阈值
    扩容机制
    扩容时机:
    1)当链表长度大于8的时候并且数组的长度小于64时优先进行扩容
    2) 当元素个数大于阈值时,进行扩容。
    怎么扩容:
    调用resize()方法方法,
    1.首先进行异常情况的判断,如是否需要初始化,二是若当前容量>最大值则不扩容,
    2.然后根据新容量(是就容量的2倍、)新建数组,将旧数组上的数据(键值对)转移到新的数组中,这里包括:(遍历旧数组的每个元素,重新计算每个数据在数组中的存放位置。(原位置或者原位置+旧容量),将旧数组上的每个数据逐个转移到新数组中,这里采用的是尾插法。)
    3.新数组table引用到HashMap的table属性上
    4.最后重新设置扩容阙值,此时哈希表table=扩容后(2倍)&转移了旧数据的新table

ConcurrentHashMap的底层原理

ConcurrentHashMap的JDK8已经摒弃了Segment段锁的概念,由于在于JDK8的锁粒度更细,理想情况下talbe数组元素的大小就是其支持并发的最大个数,数据结构采用volatile修饰的table数组+单向链表+红黑树的结构,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap。
get()

  1. 计算hash值,定位到该table索引位置,如果是首节点符合就返回
  2. 如果遇到扩容的时候,会调用标志正在扩容节点ForwardingNode的find方法,查找该节点,匹配就返回
  3. 以上都不符合的话,就往下遍历节点,匹配就返回,否则最后就返回null
    put(): 对当前的table进行无条件自循环直到put成功
  4. 如果没有初始化就先调用initTable()方法来进行初始化过程
  5. 如果没有hash冲突就直接CAS插入
  6. 如果还在进行扩容操作就先进行扩容(ForwardingNode的hash值判断)
  7. 如果存在hash冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入,
  8. 最后一个如果该链表的数量大于阈值8,就要先转换成黑红树的结构,break再一次进入循环(树化)
  9. 如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容。
    扩容机制:
    扩容时机
    1) 当链表长度大于8的时候并且数组的长度小于64时优先进行扩容
    2) 当数组元素个数大于阈值时,会触发transfer方法,重新调整节点的位置,进行扩容。
    怎么扩容
    先来看一下单线程是如何完成的:
    它的大体思想就是遍历、复制的过程。首先根据运算得到需要遍历的次数i,然后利用tabAt方法获得i位置的元素:
    如果这个位置为空,就在原table中的i位置放入forwardNode节点,这个也是触发并发扩容的关键点;
    如果这个位置是Node节点(fh>=0),如果它是一个链表的头节点,就构造一个反序链表,把他们分别放在nextTable的i和i+n的位置上
    如果这个位置是TreeBin节点(fh<0),也做一个反序处理,并且判断是否需要untreefi,把处理的结果分别放在nextTable的i和i+n的位置上
    遍历过所有的节点以后就完成了复制工作,这时让nextTable作为新的table,并且更新sizeCtl为新容量的0.75倍 ,完成扩容。
    多线程是如何完成的:
    如果遍历到的节点是forward节点,就向后继续遍历,再加上给节点上锁的机制,就完成了多线程的控制。多线程遍历节点,处理了一个节点,就把对应点的值set为forward,另一个线程看到forward,就向后遍历。这样交叉就完成了复制工作。
    size()
    1) JDK 8 推荐使用mappingCount 方法(另外的叫size方法),因为这个方法的返回值是 long 类型,不会因为 size 方法是 int 类型限制最大值
    2)在没有并发的情况下,使用一个名为 baseCount 的volatile 变量就足够了,当并发的时候,CAS 修改 baseCount 失败后,就会使用 会创建一个CounterCell对象,通常对象的 volatile value 属性是 1。在计算 size 的时候,会将 baseCount 和 CounterCell 数组中的元素的 value 累加,得到总的大小,但这个数字仍旧可能是不准确的。
    3) 还有一个需要注意的地方就是,这个 CounterCell 类使用了 @sun.misc.Contended 注解标识,这个注解是防止伪共享的。是 1.8 新增的。使用时,需要加上 -XX:-RestrictContended 参数。
    size()/mappingCount()–>sumCount(){使用了baseCount变量和CounterCell数组},在put的时候调用了 addCount()方法。

NIO

在 Java 1.4 中引入了 NIO 框架(java.nio 包),提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序。核心是 同步非阻塞,解决传统IO的阻塞问题。操作对象是Buffer。 其实NIO的核心是IO线程池, NIO中的IO多路复用调用系统级别的select和poll模型,由系统进行监控IO状态。
底层实现是:
服务端和客户端各自维护一个管理通道的对象selector,该对象能检测一个或多个通道 (channel) 上的事件。在selector中添加一个自己感兴趣的事件,通过双向通道的channer进行消息的传递,channer另一端就会开启线程轮询seletor,如果有自己感兴趣的事件,则处理这些事件,如果没有感兴趣的事件到达,一直阻塞直到感兴趣的事件到达为止。

猜你喜欢

转载自blog.csdn.net/Butterfly_resting/article/details/89735792
今日推荐