文章目录
整理
- 讲一下项目
- 对WebSocket了解多少
- 浏览器要向一个客户端WebSocket连接,需要经过哪些协议和报文
- 对 Tomcat 了解多吗
- 了解 Tomcat 任务部署吗
- Tomcat有没有配置过请求日志 什么log???
- 更新JSP后不用重启Tomcat就能生效,是怎么实现的
- Linux进程与线程 和 Java 中 的进程与线程有什么区别
- 讲一下HashMap
- 红黑树,要知道是左节点还是右节点是要比较大小的,是用什么比较大小的?
- 为什么红黑树的log2n?
- 面向对象有哪些基本特性?
- 常用哪些设计模式?哪些场景会用工厂方法?讲一下单例模式
- Spring IOC 的原理
- 那你讲讲怎么使用IOC 容器?
- 你怎么实现 IOC?除了反射
- 线程有哪些状态
- 线程池
- 往线程池提交任务,执行过程是怎么样的?
- synchronized 和 lock 有什么区别?
- JVM运行时数据区
- 什么时候会出现 OutOfMemery?有遇到过吗?
- 什么时候会出现 StackOverFlow?
- 字符串常量存在哪?
-了解哪些垃圾回收算法? - TCP是怎么保证可靠的?
- 断开连接为什么有四次?
- HTTP中哪些是长连接、哪些是短连接?
- HTTP 客户端怎么判断浏览器返回的响应是完整的?
- HTTP 的优点和缺点?
- HTTPS 协议了解吗?具体通信过程?
- 反向代理场景?
- NIO有用过吗?简单介绍一下。
- NIO 底层 Linux 用的 epoll 了解吗?
- NIO 那个多路复用器 selector 怎么做的?
- 哪些通道可以注册到多路复用器上?
- Linux 怎么看负载?负载很高怎么定位?
- Linux 里的内存分为哪几个区域?
- Linux 里的进程和线程有什么区别,进程除了共享线程的变量还共享什么吗?
- MySQL怎么定位慢查询,比如很慢的select ?
- InnoDB 和 MyISAM 的区别?
- InnoDB 的隔离级别有哪些?
- 项目里用了 Druid连接池 ,什么场景应该用连接池?
- MyBatis 里 Mapper 实现原理?用 Mapper 接口的实例就可以操作数据库,怎么实现的?
- 快排和堆排的区别,时间复杂度?
- 遍历二叉树有哪些常用方式?讲一下非递归的。
二面
- 如何判断一个数是不是 2 的 n 次方?
- 10亿个整数找最大的100个?
- 泛洪攻击是怎么回事?机制、原理?客户端请求,消耗的具体是什么资源?
- 讲一下 IO
- MySQL高可用模式?
- Linux 进程 和 Java 的进程有什么区别?
-怎么实现 比数大 的 最小的 2 的 n 次方 - 同步器中有无条件队列
- LinkedList 传入 Object 类的元素,执行过程是怎么样的?
- 状态码 300、301
- Servlet 生命周期
- 范式有什么用?
- 海量数据确认一个是否存在?
- 挥手可以三次吗?
三面
- 家在哪里?
- 介绍一下项目,架构是怎么样的?服务部署在哪里?运行环境在哪?为什么不做成一个远程访问呢?
- 你会部署一个服务吗?
- 怎么搭建服务器?
- Tomcat是多进程还是多线程?
Linux里进程和线程的区别? - 进程A里有两个线程a1和a2,线程a1 里面出现了一个除0的算术异常,a2会怎么样
- 一个进程最大能占有多大的内存空间
- 你们课程有学过操作系统吗???
- 怎么查看进程CPU情况
- IO swap 占用很高是什么意思
- 怎么查看服务器端口连接情况
- MySQL是一个什么数据库?有什么特点?什么是关系型数据库?非关系型数据库有哪些
- MySQL执行的比较慢,怎么去分析慢的问题?除了慢查询日志和explain还有什么?
- Buffer Pool 是干嘛的?
- 像8亿个用户的数据,怎么存?
- 临时表一般用来做什么的?
- 回滚是用来做什么用的?
- InnoDB一般用在什么场景?
- 除了InnoDB还有什么存储引擎?还有吗?还有吗?还有吗?
- 你的项目是B/S还是C/S?B/S 和 C/S有什么优缺点?你为什么说C/S比较安全?
- 为什么你要做后台?做后台其实压力挺大的,很多女生都去做前端或者测试,为什么你要做后台?
- 专业课排名怎么样?
- 写代码大概写多少行?有没有一万行?
一、OOM
在 JVM 描述的规范中,除了 程序计数器 以外,虚拟机内存的其他几个运行时区域都有 OutOfMemoryError 异常的可能。列举具体场景:
1、Java 堆溢出
比如,不断地创建对象,并且保证 GC Roots 到对象之间有 可达路径 来避免垃圾回收机制清除对象,那么在对象数量达到最大堆的容量限制后就会产生内存溢出异常。
比如 往 ArrayList 里不停地 add 元素。
2、栈溢出
HotSpot 虚拟机不区分虚拟机栈 和 本地方法栈,而 栈空间无法继续分配时,到底是 内存太小,还是已使用的栈空间太大,其本质都是同一件事情的两种描述而已。
实验证明,在单个线程下,无论是 由于栈帧太大 还是 虚拟机栈容量太小,当内存无法分配时,虚拟机抛出的都是 StackOverflowError 异常。
二、Tomcat 部分
1.为什么jsp修改了不需要重启Tomcat,而servlet类被修改了需要重启
对于 JSP,进行修改后,不需要重新部署,直接刷新页面即可看到更改。
因为JSP【Java Server Pages,Java服务器页面。JSP是一种基于文本的程序,其特点就是HTML和Java代码共同存在】第一次被访问时 被编译为HttpJspPage类(HttpServlet的一个子类)是由 Tomcat 服务器编译的,Tomcat 会监视 JSP 文件的改动,改动之后则重新编译成 .class 文件,替换掉 原来的 JSP 对应的 Class 文件,然后执行。而 Servlet 是由 IDE 编译的,需要把更新后的字节码文件重新加载到服务器中。
(补:关于静态资源的加载,JSP 也属于其一:
首先,在 E:\apache-tomcat-8.5.42\conf 下有个 web.xml 可以看到一段注解,Tomcat 中的 DefaultServlet 会先对所有的静态资源进行加载:
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
... ...
</servlet>
这个 DefaultServlet 是用于对静态资源进行相应处理的,一般资源请求,都会走 GET 方法,在 doGet 方法中有 serveResource () 方法,主要是会判断要请求的资源是否存在、文件是否可读,然后根据资源的类型,设置响应头的 content-type,判断文件的时间、设置超时时间等。)
整个应用内的资源是存放在 resources 这个变量里的。
在对 JSP 处理过程中,会判断文件是否生成、是否过期,来决定是否重新编译 JSP文件 生成 Servlet 类。这个一般是通过JspServletWrapper的 service 方法。
在该方法中有如下代码:
if (options.getDevelopment() || firstTime ) {//重点看这里
synchronized (this) {
firstTime = false;
// The following sets reload to true, if necessary
ctxt.compile();
}}
默认的情况下,这个 options 的 development 属性为true,所以会进到这个代码块中。
对应的 compile 方法,有如下代码段:
public void compile() {
createCompiler();
if (jspCompiler.isOutDated()) {//此处判断文件是否过期
if (isRemoved()) {
throw new FileNotFoundException(jspUri);
}
try {
jspCompiler.removeGeneratedFiles();
jspLoader = null; //注意这里
jspCompiler.compile();
jsw.setReload(true); //这里会设置reload标识
jsw.setCompilationException(null);
}
判断过期的代码比较多,这里不全部罗列了,大致包含以下几个点:
- 检查 JSP 文件生成的 Servlet 类对应的 class 文件,如果不存在,则认为过期
- 如果 class 文件修改时间和 JSP 的修改时间不一致,则认为过期
- 对于 JSP 中 include 的一些资源,如果有更新操作的,也会认为过期
从上面的代码看到,在判断文件过期之后,会执行这样几个操作: - 删除旧文件
- 编译并生成新文件
- 设置 reload 属性为 true
- 设置 JSPLoader 为 null
从操作的最后两点,是 JSP 文件能够修改立即生效的关键所在。
在 compile 操作之后,第二步就是获取具体的 servlet 类,这个方法的一些主要代码如下:
public Servlet getServlet() throws ServletException {
if (reload) {
synchronized (this) {
if (reload) {
destroy();
final Servlet servlet;
try {
InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(config);
servlet = (Servlet) instanceManager.newInstance(ctxt.getFQCN(), ctxt.getJspLoader());
} catch (Exception e) {}
theServlet = servlet;
reload = false;
}}}
return theServlet;
}
首先根据 reload 标识来判断是否要重新加载 Servlet类。而每次的 JSP 修改,都会导致 compile 时将此标识设置为 true,自然每次的修改都是要加载的。
我们注意到 instanceManager 的 newInstance 方法,会从 JspCompilationContext 这个类取 JSP 的 ClassLoader,这个取 ClassLoader的过程如下:
public ClassLoader getJspLoader() {
if( jspLoader == null ) {//看这里
jspLoader = new JasperLoader
(new URL[] {baseUrl}, //这里的baseUrl,就是应用的file:/D:/xxx/work/Catalina/localhost/test/
getClassLoader(),
rctxt.getPermissionCollection());
}
return jspLoader;
}
在获取 jspLoader 的,如果这个对象为null,就会新创建一个。这个 get 到的 jspLoader 会被用来执行 loadClass 的操作,即 JSP 对应的 Servlet 类是会被其进行加载的。
假设在请求应用的 index.jsp 页面,那初次请求时, instanceManager 对应的 classLoader 是一个,当修改 JSP 文件后再次请求时,又是使用的另一个 classLoader,所以新的内容修改被成功加载,而原来旧的内容,已经在 compile 阶段被清除了。
所以,每次修改后刷新页面,对相应的 JSP 页面的请求被JspServlet拦截,就会进行以上操作。
当然,还有一点不得不注意,这一些是依赖配置 options 的 development 的配置,这一配置默认是 true,可以关闭。如果是生产环境关闭了这一配置,那上面的一些修改重新编译,classLoader一类的也就不能生效了,因为如果关闭的话,除了第一次请求会编译 jsp 文件之外,其它时候不会进入compile,reload 也就一直是 false 了。
 如果想修改 Servlet 也不用重启的话,可以修改 Tomcat目录下,/conf/context.xml配置文件,加上<Context reloadable="true">
这样的话 Tomcat 每隔一段时间自动检测 Java 文件是否修改,然后重启。
(ps:只适合在开发阶段使用,项目上线后不用频繁重启tomcat。)
2.Servlet 生命周期
- init():
当该 servlet 第一次被请求时,Servlet 容器会调用 init() 方法,后续请求不会再被调用,可以利用该方法进行相应的初始化功能。 - service():
每当请求 Servlet 时,都会调用这个方法。 - destory():
每当销毁 Servlet 时,会调用这个方法,通常在关闭 Servlet 容器时会调用。
想要 Tomcat 一启动就加载 Servlet 除非在 web.xml 中 写:
<servlet>
<load-on-startup>0</load-on-startup>
... ...
</servlet>
0 代表优先级别,而且是 最高的优先级别,这样的话,Tomcat 首先把这些类加载并实例化,并会调用 init() 方法完成初始化过程。保存在自己的 Servlet 容器池中,以后如果有请求,直接从此容器池中取出来,处理相应的请求。
而如果是客户端输入请求,如 在URL 中输入http://localhost:8080/accp/reqrep
是先去 web.xml 中找到指定的 Servlet 类,加载并实例化,调用 init() 进行初始化,而且这是来自客户端的一个请求,所以会调用 doGet() 方法去处理请求。
2.1 为什么只覆写 doGet()、doPost() 方法,不覆写 service()
doGet( ) 或者 doPost( ) 方法是由 Service( ) 来调用的,我们怎么不重写 Service( ) 方法呢?
那是因为 HttpServlet 继承了GenericServlet ,而GenericServlet 实现了Servlet 接口。在其中的一个类中,它的 Service( ) 其实就已经解析了 来自于 Http 请求头的内容,根据请求行的 method 来决定调用 doGet( ) 或者 doPost( ) 方法,同时把 HttpServletRequest 和 HttpServletResponse 以参数的形式传递给 doGet( ) 或者doPost( ),来进行我们的业务处理。
如果重写 Service( ) 方法,那么里面的很多工作可能就需要我们完成,其实这并不需要,我们只需要重写 doGet( ) 或者 doPost( ) 方法就可以了。
三、反向代理
反向代理:
代替真实服务器接收网络请求,然后将请求转发到真实服务器。
反向代理的作用:
隐藏真实服务器,使真实服务器只能通过内网访问,保护了真实服务器不被攻击。配置负载均衡,减轻单台真实服务器的压力。配置主备服务器,保持服务稳定运行。
可以在服务器上部署 Nginx,让它监听 80 端口,它会根据 域名的不同 指向 不同的服务器,也可以根据 负载的不同 指向不同服务器。
四、HTTP 协议的优缺点
优点:简单、易于扩展、应用广泛、环境成熟
- 简单
基本的报文格式就是 “header+body” ,头部信息也是简单的文本格式,用的也都是常见的英文单词。“简单” 蕴含了进化和扩展的可能性,所谓“少即是多”,“把简单的系统变复杂”,要比“把复杂的系统变简单”容易得多。 - 易于扩展
HTTP 协议里的 请求方法、URI、状态码、原因短语、头字段等每一个核心组成要素都允许开发者任意定制、扩充或解释,给予了浏览器和服务器最大程度的信任和自由。
易于扩展 的特性还表现在 HTTP对 “可靠传输” 的定义上,它不限制具体的下层协议,不仅可以使用TCP、UNIX Domain Socket,还可以使用SSL/TLS,甚至是基于 UDP 的QUIC,下层可以随意变化,而上层的语义则始终保持稳定。 - 无状态
“无状态” 对于 HTTP 来说既是优点也是缺点。
好处:
因为服务器没有“记忆能力”,所以就不需要额外的资源来记录状态信息,不仅实现上会简单一些,而且还能减轻服务器的负担,能够把更多的 CPU 和 内存 用来对外提供服务。
而且,“无状态” 也表示服务器都是相同的,没有“状态”的差异,所以可以很容易地组成集群,让负载均衡把请求转发到任意一台服务器,不会因为状态不一致导致处理出错,使用“堆机器”的“笨办法”轻松实现高并发高可用。
坏处:
既然服务器没有“记忆能力”,它就无法支持需要连续多个步骤的“事务”操作。例如电商购物,首先要登录,然后添加购物车,再下单、结算、支付,这一系列操作都需要知道用户的身份才行,但“无状态”服务器是不知道这些请求是相互关联的,每次都得问一遍身份信息,不仅麻烦,而且还增加了不必要的数据传输量。
缺点:明文传输-不安全、无身份认证、无完整性校验。
“明文”意思就是协议里的报文(准确地说是header部分)不使用二进制数据,而是用简单可阅读的文本形式。
五、字符串常量池
JDK 1.8 HotSpot 里的 字符串常量池 是在堆中的,基础类型的包装类的缓存是用数组存储的,而 字符串常量池是用 Hashtable 缓存的,Hashtable 是数组,数组中的每个元素是一个链表,不过 Hashtable (不是ne个 Hashtable )是不可以动态扩容的,但是它会在发现散列不均匀的时候会进行 rehash。
证明 字符串常量池是在堆中:
String类有intern() 方法,这个方法的作用是:
- 如果字符串未在 Pool 中,那么就往 Pool 中增加一条记录,然后返回 Pool 中的引用。
- 如果已经在 Pool 中,直接返回 Pool 中的引用。
import java.nio.*;
public class test {
public static void main(String[] args) {
String str = "abc";
char[] array = {'a', 'b', 'c'};
String str2 = new String(array);
//使用intern()将str2字符串内容放入常量池
str2 = str2.intern();
//字符串常量和我们使用intern处理后的字符串是同一个
System.out.println(str == str2);
//接下来不停地intern
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < 20000000; i++) {
String temp = String.valueOf(i).intern();
list.add(temp);
}
}
}
输出结果:
true
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
at java.lang.Integer.toString(Integer.java:403)
at java.lang.String.valueOf(String.java:3099)
at test.main(test.java:18)
Process finished with exit code 1
六、二叉树的非递归遍历
以 中序遍历 【左,根,右】 为例:
- 需要一个栈 S , 指针 p 指向根节点
- 申请一个节点空间 q 存放栈定弹出的元素
while( P 非空 或 栈 S 非空时)
{如果 p 非空,根 进栈,然后 p 指向左子树。
如果 p 为空,则出栈,访问根节点,然后 p 指向右子树,}
七、SYN 泛洪攻击
SYN 泛洪攻击是一种比较常用的 DoS 方式之一。通过发送大量伪造的 TCP 连接请求,使被攻击主机资源耗尽(通常是CPU满负荷或者内存不足) 的攻击方式。
TCP 连接需要完成三次握手。正常情况下客户端首先向服务端发送 SYN 报文,随后服务端以 SYN+ACK 报文到达客户端,最后客户端向服务端发送 ACK 报文完成三次握手。
而 SYN 泛洪攻击则是客户端向服务器发送 SYN 报文之后就不再响应服务器回应的报文。由于服务器在处理 TCP 请求时,会在协议栈留一块缓冲区来存储握手的过程,当然如果超过一定的时间内没有接收到客户端的报文,本次连接在协议栈中存储的数据将会被丢弃。攻击者如果利用这段时间发送大量的连接请求,全部挂起在半连接状态。这样将不断消耗服务器资源,直到拒绝服务。
- 如何防范 SYN 攻击
最常用的一个手段就是优化主机系统设置。比如降低SYN timeout时间,使得主机尽快 释放半连接的占用 或者 采用 SYN cookie 设置,如果短时间内收到了某个 IP 的重复 SYN 请求,我们就认为受到了攻击。我们合理的采用防火墙设置等外部网络也可以进行拦截。
八、同步器中的条件队列
回环屏障 CycleBarrier 中是有条件队列的。
回环屏障 CycleBarrier 的 await() 方法,内部调用了 doWait() 方法,当一个线程 调用了 dowait() 方法后,首先会获取 独占锁 Lock,比方说 创建 CycleBarrier 时传递的参数是 10 ,那么后面 9 个调用线程会被阻塞。然后当前获取到 锁 的 线程 会对计数器 count 递减,递减后 count=index=9,这时 index != 0,如果没有设置超时时间,当前线程会被放到 条件变量 trip 的条件阻塞队列,当前线程会被挂起并释放获取的 Lock 锁;如果设置了超时时间,当前线程也会被放入条件变量的条件队列 并 释放锁,不同的是当前线程会在指定事件超时后被自动激活。
九、Linkedlist 源码分析
- indexOf(Object o):
方法没什么特别的,就是从头到尾遍历。
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
- get(index):
返回指定下标所在的元素,会先比较 index 与 size/2 的大小,如果大于,就会从 size 所在的节点由后向前遍历。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
十、Linux 内存交换分区 Swap
swap 会被利用到的时候通常是 物理内存不足的情况。
十一、关系型数据库 与 非关系型数据库
关系型数据库是依据关系模型来创建的数据库。
所谓关系模型就是 “一对一、一对多、多对多”等关系模型,关系模型就是指二维表格模型,因而一个关系型数据库就是由二维表及其之间的联系组成的一个数据组织。
非关系型(如:redis,mangoDB)模型有:
- 列模型:
存储的数据是一列列的。关系型数据库以一行作为一个记录,列模型数据库以一列为一个记录。(这种模型,数据即索引,IO很快,主要是一些分布式数据库)
键值对模型:
存储的数据是一个个“键值对”,比如:
name:liming
那么name这个键里面存的值就是limingimage
- 文档类模型:
以一个个文档来存储数据,有点类似“键值对”。