二面
三面
在 JVM 描述的规范中,除了 程序计数器 以外,虚拟机内存的其他几个运行时区域都有 OutOfMemoryError 异常的可能。列举具体场景:
比如,不断地创建对象,并且保证 GC Roots 到对象之间有 可达路径 来避免垃圾回收机制清除对象,那么在对象数量达到最大堆的容量限制后就会产生内存溢出异常。
比如 往 ArrayList 里不停地 add 元素。
HotSpot 虚拟机不区分虚拟机栈 和 本地方法栈,而 栈空间无法继续分配时,到底是 内存太小,还是已使用的栈空间太大,其本质都是同一件事情的两种描述而已。
实验证明,在单个线程下,无论是 由于栈帧太大 还是 虚拟机栈容量太小,当内存无法分配时,虚拟机抛出的都是 StackOverflowError 异常。
对于 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);
}
判断过期的代码比较多,这里不全部罗列了,大致包含以下几个点:
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配置文件,加上
这样的话 Tomcat 每隔一段时间自动检测 Java 文件是否修改,然后重启。
(ps:只适合在开发阶段使用,项目上线后不用频繁重启tomcat。)
想要 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() 方法去处理请求。
doGet( ) 或者 doPost( ) 方法是由 Service( ) 来调用的,我们怎么不重写 Service( ) 方法呢?
那是因为 HttpServlet 继承了GenericServlet ,而GenericServlet 实现了Servlet 接口。在其中的一个类中,它的 Service( ) 其实就已经解析了 来自于 Http 请求头的内容,根据请求行的 method 来决定调用 doGet( ) 或者 doPost( ) 方法,同时把 HttpServletRequest 和 HttpServletResponse 以参数的形式传递给 doGet( ) 或者doPost( ),来进行我们的业务处理。
如果重写 Service( ) 方法,那么里面的很多工作可能就需要我们完成,其实这并不需要,我们只需要重写 doGet( ) 或者 doPost( ) 方法就可以了。
反向代理:
代替真实服务器接收网络请求,然后将请求转发到真实服务器。
反向代理的作用:
隐藏真实服务器,使真实服务器只能通过内网访问,保护了真实服务器不被攻击。配置负载均衡,减轻单台真实服务器的压力。配置主备服务器,保持服务稳定运行。
可以在服务器上部署 Nginx,让它监听 80 端口,它会根据 域名的不同 指向 不同的服务器,也可以根据 负载的不同 指向不同服务器。
优点:简单、易于扩展、应用广泛、环境成熟
缺点:明文传输-不安全、无身份认证、无完整性校验。
“明文”意思就是协议里的报文(准确地说是header部分)不使用二进制数据,而是用简单可阅读的文本形式。
JDK 1.8 HotSpot 里的 字符串常量池 是在堆中的,基础类型的包装类的缓存是用数组存储的,而 字符串常量池是用 Hashtable 缓存的,Hashtable 是数组,数组中的每个元素是一个链表,不过 Hashtable (不是ne个 Hashtable )是不可以动态扩容的,但是它会在发现散列不均匀的时候会进行 rehash。
证明 字符串常量池是在堆中:
String类有intern() 方法,这个方法的作用是:
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
以 中序遍历 【左,根,右】 为例:
SYN 泛洪攻击是一种比较常用的 DoS 方式之一。通过发送大量伪造的 TCP 连接请求,使被攻击主机资源耗尽(通常是CPU满负荷或者内存不足) 的攻击方式。
TCP 连接需要完成三次握手。正常情况下客户端首先向服务端发送 SYN 报文,随后服务端以 SYN+ACK 报文到达客户端,最后客户端向服务端发送 ACK 报文完成三次握手。
而 SYN 泛洪攻击则是客户端向服务器发送 SYN 报文之后就不再响应服务器回应的报文。由于服务器在处理 TCP 请求时,会在协议栈留一块缓冲区来存储握手的过程,当然如果超过一定的时间内没有接收到客户端的报文,本次连接在协议栈中存储的数据将会被丢弃。攻击者如果利用这段时间发送大量的连接请求,全部挂起在半连接状态。这样将不断消耗服务器资源,直到拒绝服务。
回环屏障 CycleBarrier 中是有条件队列的。
回环屏障 CycleBarrier 的 await() 方法,内部调用了 doWait() 方法,当一个线程 调用了 dowait() 方法后,首先会获取 独占锁 Lock,比方说 创建 CycleBarrier 时传递的参数是 10 ,那么后面 9 个调用线程会被阻塞。然后当前获取到 锁 的 线程 会对计数器 count 递减,递减后 count=index=9,这时 index != 0,如果没有设置超时时间,当前线程会被放到 条件变量 trip 的条件阻塞队列,当前线程会被挂起并释放获取的 Lock 锁;如果设置了超时时间,当前线程也会被放入条件变量的条件队列 并 释放锁,不同的是当前线程会在指定事件超时后被自动激活。
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;
}
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;
}
}
swap 会被利用到的时候通常是 物理内存不足的情况。
关系型数据库是依据关系模型来创建的数据库。
所谓关系模型就是 “一对一、一对多、多对多”等关系模型,关系模型就是指二维表格模型,因而一个关系型数据库就是由二维表及其之间的联系组成的一个数据组织。
非关系型(如:redis,mangoDB)模型有:
name:liming