1.内存泄露
内存泄漏两种情况:
在堆中申请的空间没有被释放(虚拟机gc可以解决)
对象已不在使用,但仍然在内存中保留着
Java中主要的内存泄漏:
静态集合类,容器中的对象在程序结束之前都不会被释放
数据库连接、网络连接、IO连接
监听器
变量不合理的作用域,成员变量在使用后依然存在。(解决:将变量设为局部变量)
单例造成内存泄漏
2.switch
注:在java中switch后的表达式的类型只能为以下几种:byte、short、char、int(在Java1.6中是这样),在java1.7后支持了对string的判断.long不可以,会出现精度丢失
3.LRU算法
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
1. 新数据插入到链表头部;
2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
3. 当链表满的时候,将链表尾部的数据丢弃。
可以使用LinkedHashMap、FIFO、HashMap+链表实现
4.内存溢出
1, java Heap 溢出
一般的异常信息:java.lang.OutOfMemoryError:Java heap space
java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。
可能原因:一个原因是真不够,另一个原因是程序中有死循环。或者说有可能是内存溢出也有可能是内存泄露,后者可能会导致前者,所以要先排除内存泄露的可能。
出现这种异常,一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
如果是内存泄漏,可进一步通过工具(如Jrockit等工具)查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象时通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。
如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。
2, 虚拟机栈和本地方法栈溢出
当栈深度超过虚拟机分配给线程的栈大小时,将抛出StackOverflowError异常(无限递归)
当再申请新的内存时,虚拟机分配给线程的内存大小中无法再分配新的内存,则抛出OutOfMemoryError异常(不断创建新线程)
这里需要注意当栈的大小越大可分配的线程数就越少。
3, 运行时常量池溢出(JDK6及之前)
异常信息:java.lang.OutOfMemoryError:PermGen space
如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。
4, 方法区溢出
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。
异常信息:java.lang.OutOfMemoryError:PermGen space
方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。
5.异常以及try-catch-finally
异常分为两类:Error和Exception,都继承自Throwable类
Error:错误也可以像Exception一样进行抛出和捕获,只是不推荐
Exception:
checked exception和runtime exception
检查异常:最常见有IO异常和SQL异常,编译器不会通过,会强制去捕获,可trycatch、可抛出
运行异常:NullPotinterException、ArrayIndexOutBoundsException、ArrayStoreException、ArithmeticException,这些异常即使有throw操作,编译器也会忽略。
多线程时,出现异常只是线程退出,程序不会结束
try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为以下三种情况:
情况一:如果finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
情况二:如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。
情况三:如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况:1)如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。2)如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。
6.java引用
Java四种引用:
1. 强引用
是指创建一个对象并把这个对象赋给一个引用变量。
比如:
Object object =new Object();
String str ="hello";
强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。
2. 软引用(SoftReference)
如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;
如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。
3. 虚引用,弱引用
无论内存是否充足,都会回收被弱引用关联的对象。
7.程序初始化顺序
变量大于块、静态大于非静态!
静态对象(变量)由于非静态对象(变量)初始化。其中静态对象(变量)只初始化一次,而非静态对象(变量)可能会初始化很多次
父类优先于子类进行初始化
按照成员变量的定义顺序进行初始化。即使变量定义散布于方法之中,他们依然在任何方法(包括构造函数)被调用前先初始化
8.自动转型
自动转型byte
byte、short、char在运算时会转换成int,结果也是int。所以short s1=1;s1=s1+1;会报错。应该为s1=(short)s1+1;
9.重写和重载
重写:
子类覆盖方法必须和父类有相同的函数名和参数
返回值必须相同
抛出异常必须必须比父类异常范围小
子类方法访问权限必须比父类大
重载:
重载通过不同的方法参数来区分的:不同的参数个数、不同的参数类型、不同的参数顺序
不能通过方法的访问权限、返回值类型和抛出的异常类型来进行重载
10.面向对象三大特征、五大原则
三大特征:
封装,将客观事物抽象成类,提供可供外部访问的操作,对内部数据进行了不同级别的保护。
继承:类继承和接口继承
多态:重写、运行时多态,重载、编译时多态
五大原则:
单一职责原则:一个类应该仅有一个引起他变化的原因
开放封闭原则:对扩展是开放的,对更改是封闭的
里氏变换原则:子类可以替换父类并且出现在父类能够出现的任何地方
依赖倒置原则:高层模块不要依赖底层模块
接口隔离原则:使用多个专门的接口比使用单个接口要好的多
11.线程池
Java通过Executors提供了四种线程池,这四种线程池都是直接或间接配置ThreadPoolExecutor的参数实现的:
1.可缓存线程池:
线程数无限制
有空闲线程则复用空闲线程,若无空闲线程则新建线程
一定程序减少频繁创建/销毁线程,减少系统开销
创建方法:ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
FixedThreadPool()
定长线程池:
可控制线程最大并发数(同时执行的线程数)
超出的线程会在队列中等待
创建方法:
//nThreads => 最大线程数即maximumPoolSize
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);
//threadFactory => 创建线程的方法,这就是我叫你别理他的那个星期六!你还看!
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
ScheduledThreadPool()
定时线程池:
支持定时及周期性任务执行。
创建方法:
//nThreads => 最大线程数即maximumPoolSize
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
源码:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
SingleThreadExecutor()
单线程化的线程池:
有且仅有一个工作线程执行任务
所有任务按照指定顺序执行,即遵循队列的入队出队规则
创建方法:
ExecutorService singleThreadPool = Executors.newSingleThreadPool();
源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
当一个任务被添加进线程池时:
线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
线程数量达到了corePools,则将任务移入队列等待
队列已满,新建线程(非核心线程)执行任务
队列已满,总线程数又达到了maximumPoolSize,就会由RejectedExecutionHandler抛出异常
12.访问网页的过程
输入地址->浏览器查找域名的 IP 地址->浏览器向 web 服务器发送一个 HTTP 请求->服务器的永久重定向响应->浏览器跟踪重定向地址->服务器处理请求->服务器返回一个 HTTP 响应->浏览器显示 HTML->浏览器发送请求获取嵌入在 HTML 中的资源->浏览器发送异步请求
DNS查找过程如下:
浏览器会缓存DNS记录一段时间,但是操作系统并没有告诉浏览器储存DNS记录的时间,这样不同浏览器会储存个自固定的一个时间(2分钟到30分钟不等)。如果在浏览器缓存里没有找到需要的记录,浏览器会做一个系统调用(windows里是gethostbyname),这样便可获得系统缓存中的记录。接着,前面的查询请求发向路由器,它一般会有自己的DNS缓存。接下来要检查的就是ISP缓存DNS的服务器。在这一般都能找到相应的缓存记录。ISP的DNS服务器从跟域名服务器开始进行递归搜索,从.com顶级域名服务器到example的域名服务器。一般DNS服务器的缓存中会有.com域名服务器中的域名,所以到顶级服务器的匹配过程不是那么必要了。
HTTP请求的建立过程
建立TCP连接:在HTTP工作开始之前,Web浏览器首先要通过网络与Web服务器建立连接,该连接是通过TCP来完成的,该协议与IP协议共同构建Internet,即著名的TCP/IP协议族,因此Internet又被称作是TCP/IP网络。HTTP是比TCP更高层次的应用层协议,根据规则,只有低层协议建立之后才能,才能进行更层协议的连接,因此,首先要建立TCP连接,一般TCP连接的端口号是80。在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;
第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包
第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。
完成三次握手,主机A与主机B开始传送数据。
一旦建立了TCP连接,Web浏览器就会向Web服务器发送请求命令。
浏览器发送其请求命令之后,还要以头信息的形式向Web服务器发送一些别的信息,之后浏览器发送了一空白行来通知服务器,它已经结束了该头信息的发送。
13SpringMVC
自定义注解自己实现SpringMVC:DispatcherServlet--->HttpServlet
1.init():scannerPackage--foundBeanInstance--SpringIOC---HanglerMapping
2.service():doPost(),doGet().Path--method--controller--反射执行
3.destroy().
以下组件通常使用框架提供实现:
1.DisPatcherServlet:前端控制器(不需要程序员开发)
用户请求到达前端控制器,它相当于MVC模式中的C(Controller),DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性。
作用:作为接受请求,响应结果,相当于转发器,中央处理器,减少其他组件之间的耦合度。
2.HandlerMapping:处理器映射器(不需要程序员开发)
HandlerMapping负责根据用户请求找到Handler(即:处理器),SpringMVC提供了不同的映射器实现实现不同的映射方式,例如:配置文件方式、实现接口方式、注解方式等。
作用:根据请求的Url 查找Handler
3.HandLer:处理器(需要程序员开发)
Handler是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下,Handler对具体的用户请求进行处理。
由于Handler设计到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。
注意:编写Handler时按照HandlerAdpter的要求去做,这样才可以去正确执行Handler。
4.HandlerAdapter:处理器适配器
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
作用:按照特定的规则(HandlerAdapter要求的规则)去执行Handler
5.ViewResolver:视图解析器(不需要程序员开发)ModelAndView逻辑视图和模型数据(逻辑视图解析成真实视图,模型数据进行render)
ViewResolver负责将处理结果生成View视图,ViewResolver首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面的展示给用户。SpringMVC框架提供了很多View视图类型,包括:JSTLView、freemarkerView、pdfView等等.
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)。
6.View视图(需要程序员开发 jsp)
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf)
一般情况下需要通过页面标签或者页面模板技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
【流程图说明】
具体步骤:
首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
DispatcherServlet——>HandlerMapping,HandlerMapping 将会把请求映射为 HandlerExecutionChain 对象(包含一个 Handler 处理器(页面控制器)对象、多个 HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个 ModelAndView 对象(包含模型数据、逻辑视图名);
ModelAndView 的逻辑视图名——> ViewResolver, ViewResolver 将把逻辑视图名解析为具体的 View,通过这种策略模式,很容易更换其他视图技术;
View——>渲染,View 会根据传进来的 Model 模型数据进行渲染,此处的 Model 实际是一个 Map 数据结构,因此很容易支持其他视图技术;
返回控制权给 DispatcherServlet,由 DispatcherServlet 返回响应给用户,到此一个流程结束。
14 Sevlet
Servlet生命周期(单例多线程):
servlet:servlet是一种运行服务器端的java应用程序,具有独立于平台和协议的特性,并且可以动态的生成web页面,它工作在客户端请求与服务器响应的中间层。最早支持 Servlet 技术的是 JavaSoft 的 Java Web Server。此后,一些其它的基于 Java 的 Web Server 开始支持标准的 Servlet API。Servlet 的主要功能在于交互式地浏览和修改数据,生成动态 Web 内容。这个过程为:
1) 客户端发送请求至服务器端;
2) 服务器将请求信息发送至 Servlet;
3) Servlet 生成响应内容并将其传给服务器。响应内容动态生成,通常取决于客户端的请求;
4) 服务器将响应返回给客户端。
在 Web 应用程序中,一个 Servlet 在一个时刻可能被多个用户同时访问。这时 Web 容器将为每个用户创建一个线程来执行 Servlet。如果 Servlet 不涉及共享资源的问题,不必关心多线程问题。但如果 Servlet 需要共享资源,需要保证 Servlet 是线程安全的。
最新版本3.1,为了简化开发流程,Servlet 3.0 引入了注解(annotation),这使得 web 部署描述符 web.xml 不再是必须的选择。
Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:
加载,加载Servlet类
创建,通过构造函数创建一个Servlet实例
Servlet 通过调用init ()方法进行初始化。
Servlet 调用service()方法来处理客户端的请求。
Servlet 通过调用destroy()方法终止(结束)。
最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
init() 方法
init 方法被设计成只调用一次。它在第一次创建 Servlet 时被调用,在后续每次用户请求时不再调用。因此,它是用于一次性初始化,就像 Applet 的 init 方法一样。
service() 方法
service() 方法是执行实际任务的主要方法。Servlet 容器(即 Web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。
destroy() 方法
destroy() 方法只会被调用一次,在 Servlet 生命周期结束时被调用。destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。
在调用 destroy() 方法之后,servlet 对象被标记为垃圾回收。
Servlet的调用过程
1.客户端通过发送请求给Tomcat,Tomcat发送客户端的请求页面给客户端。
2.用户对请求页面进行相关操作后将页面提交给Tomcat,Tomcat将其封装成一个HttpRequest对象,然后对请求进行处理,。
3.Tomcat截获请求,根据action属性值查询xml文件中对应的servlet-name,再根据servlet-name查询到对应的java类(如果是第一次,Tomcat则会将servlet编译成java类文件,所以如果servlet有很多的话第一次运行的时候程序会比较慢)。
4.Tomcat实例化查询到的java类,注意该类只实例化一次。
5.调用java类对象的service()方法(如果不对service()方法进行重写则根据提交的方式来决定执行doPost()方法还是doGet()方法)。
6.通过request对象取得客户端传过来的数据,对数据进行处理后通过response对象将处理结果写回客户端。
15 Object方法
Object是所有类的父类,任何类都默认继承Object。
clone:保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。浅复制
equals:在Object中与==是一样的,子类一般需要重写该方法
hashCode:该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到
getClass:final方法,获得运行时类型
wait:使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生:
1.其他线程调用了该对象的notify方法
2.其他线程调用了该对象的notifyAll方法
3.其他线程调用了interrupt中断该线程
4.时间间隔到了
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常
notify:唤醒在该对象上等待的某个线程
notifyAll:唤醒在该对象上等待的所有线程
toString:转换成字符串,一般子类都有重写,否则打印句柄
16 Intercepter和filter、Interceptor
filter:filter是一个可以复用的代码片段,可以用来转换HTTP请求、响应和头信息。Filter不像Servlet,它不能产生一个请求或者响应,它只是修改对某一资源的请求,或者修改从某一的响应。Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的服务器端程序,主要的用途是过滤字符编码、做一些业务逻辑判断等。其工作原理是,只要你在web.xml文件配置好要拦截的客户端请求,它都会帮你拦截到请求,此时你就可以对请求或响应(Request、Response)统一设置编码,简化操作;同时还可进行逻辑判断,如用户是否已经登陆、有没有权限访问该页面等等工作。它是随你的web应用启动而启动的,只初始化一次,以后就可以拦截相关请求,只有当你的web应用停止或重新部署的时候才销毁。Filter可认为是Servlet的一种“变种”,它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理,是个典型的处理链。它与Servlet的区别在于:它不能直接向用户生成响应。完整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
Filter有如下几个用处。
在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest。
根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据。
在HttpServletResponse到达客户端之前,拦截HttpServletResponse。
根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。
Filter有如下几个种类。
用户授权的Filter:Filter负责检查用户请求,根据请求过滤用户非法请求。
日志Filter:详细记录某些特殊的用户请求。
负责解码的Filter:包括对非标准编码的请求解码。
能改变XML内容的XSLT Filter等。
Filter可负责拦截多个请求或响应;一个请求或响应也可被多个请求拦截。
创建一个Filter只需两个步骤:
建Filter处理类;
web.xml文件中配置Filter。
listener:监听器,从字面上可以看出listener主要用来监听只用。通过listener可以监听web服务器中某一个执行动作,并根据其要求作出相应的响应。通俗的语言说就是在application,session,request三个对象创建消亡或者往其中添加修改删除属性时自动执行代码的功能组件。比如spring 的总监听器 会在服务器启动的时候实例化我们配置的bean对象 、 hibernate 的 session 的监听器会监听session的活动和生命周期,负责创建,关闭session等活动。
Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。主要作用是: 做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等等。
interceptor:是在面向切面编程的,就是在你的service或者一个方法,前调用一个方法,或者在方法后调用一个方法,是基于JAVA的反射机制。比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。
servlet、filter、listener是配置到web.xml中(web.xml 的加载顺序是:context-param -> listener -> filter -> servlet),interceptor不配置到web.xml中,struts的拦截器配置到struts.xml中。spring的拦截器配置到spring.xml中。
二、生命周期:
1、servlet:一般继承HttpServlet(一般的,通用Servlet由javax.servlet.GenericServlet实现Servlet接口。程序设计人员可以通过使用或继承这个类来实现通用Servlet应用。javax.servlet.http.HttpServlet实现了专门用于响应HTTP请求的Servlet,提供了响应对应HTTP标准请求的doGet()、doPost()等方法),web.xml配置servlet时如果加上load-on-start(在servlet的配置当中,1的含义是:标记容器是否在启动的时候就加载这个servlet。当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。正数的值越小,启动该servlet的优先级越高。)为1时候,Web应用启动时候加载Servlet。当servlet被部署在应用服务器中(应用服务器中用于管理Java组件的部分被抽象成为容器)以后,由容器控制servlet的生命周期。除非特殊指定,否则在容器启动的时候,servlet是不会被加载的,servlet只会在第一次请求的时候被加载和实例化。servlet一旦被加载,一般不会从容器中删除,直至应用服务器关闭或重新启动。但当容器做内存回收动作时,servlet有可能被删除。也正是因为这个原因,第一次访问servlet所用的时间要大大多于以后访问所用的时间。servlet在服务器的运行生命周期为,在第一次请求(或其实体被内存垃圾回收后再被访问)时被加载并执行一次初始化方法,跟着执行正式运行方法,之后会被常驻并每次被请求时直接执行正式运行方法,直到服务器关闭或被清理时执行一次销毁方法后实体销毁。Java服务器页面(JSP)是HttpServlet的扩展。由于HttpServlet大多是用来响应HTTP请求,并返回Web页面(例如HTML、XML),所以不可避免地,在编写servlet时会涉及大量的HTML内容,这给servlet的书写效率和可读性带来很大障碍,JSP便是在这个基础上产生的。其功能是使用HTML的书写格式,在适当的地方加入Java代码片断,将程序员从复杂的HTML中解放出来,更专注于servlet本身的内容。JSP在首次被访问的时候被应用服务器转换为servlet,在以后的运行中,容器直接调用这个servlet,而不再访问JSP页面。JSP的实质仍然是servlet。
(1)、装入:启动服务器时加载Servlet的实例;
(2)、初始化:web服务器启动时或web服务器接收到请求时,或者两者之间的某个时刻启动。初始化工作有init()方法负责执行完成;
(3)、调用:从第一次到以后的多次访问,都是只调用doGet()或doPost()方法;
(4)、销毁:停止服务器时调用destroy()方法,销毁实例。
2、filter:(必须实现javax.Servlet.Filter接口,并且必须定义以下三个方法:init(),destory(),doFilter(),空实现也行)
(1)、启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;
(2)、每一次请求时都只调用方法doFilter()进行处理;
(3)、停止服务器时调用destroy()方法,销毁实例。
3、listener:Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。
web.xml 的加载顺序是:context- param -> listener -> filter -> servlet
4、interceptor:以struts的拦截器为例,加载了struts.xml以后,初始化相应拦截器。当action请求来时调用intercept方法,服务器停止销毁interceptor。
三、职责
1、servlet:
创建并返回一个包含基于客户请求性质的动态内容的完整的html页面;
创建可嵌入到现有的html页面中的一部分html页面(html片段);
读取客户端发来的隐藏数据;
读取客户端发来的显示数据;
与其他服务器资源(包括数据库和java的应用程序)进行通信;
通过状态代码和响应头向客户端发送隐藏数据。
2、filter:
filter能够在一个请求到达servlet之前预处理用户请求,也可以在离开servlet时处理http响应:
在执行servlet之前,首先执行filter程序,并为之做一些预处理工作;
根据程序需要修改请求和响应;
在servlet被调用之后截获servlet的执行
3、listener:职责如概念。
servlet2.4规范中提供了8个listener接口,可以将其分为三类,分别如下:
第一类:与servletContext有关的listner接口。包括:ServletContextListener、ServletContextAttributeListener
第二类:与HttpSession有关的Listner接口。包括:HttpSessionListner、HttpSessionAttributeListener、HttpSessionBindingListener、 HttpSessionActivationListener;
第三类:与ServletRequest有关的Listener接口,包括:ServletRequestListner、ServletRequestAttributeListener
4、interceptor:与过滤器十分相似,通过层层拦截,处理用户的请求和响应。
四、几个区别:
1,servlet 流程是短的,url传来之后,就对其进行处理,之后返回或转向到某一自己指定的页面。它主要用来在 业务处理之前进行控制.
2,filter 流程是线性的, url传来之后,检查之后,可保持原来的流程继续向下执行,被下一个filter, servlet接收等,而servlet 处理之后,不会继续向下传递。filter功能可用来保持流程继续按照原来的方式进行下去,或者主导流程,而servlet的功能主要用来主导流程。
filter可用来进行字符编码的过滤,检测用户是否登陆的过滤,禁止页面缓存等
3, servlet,filter都是针对url之类的,而listener是针对对象的操作的,如session的创建,session.setAttribute的发生,在这样的事件发生时做一些事情。
可用来进行:Spring整合Struts,为Struts的action注入属性,web应用定时任务的实现,在线人数的统计等
4,interceptor 拦截器,类似于filter,不过在struts.xml中配置,不是在web.xml,并且不是针对URL的,而是针对action,当页面提交action时,进行过滤操作,相当于struts1.x提供的plug-in机制,可以看作,前者是struts1.x自带的filter,而interceptor 是struts2 提供的filter.
与filter不同点:(1)不在web.xml中配置,而是在struts.xml中完成配置,与action在一起
( 2 ) 可由action自己指定用哪个interceptor 来在接收之前做事
5,struts2中的过滤器和拦截器的区别与联系:
(1)、拦截器是基于java反射机制的,而过滤器是基于函数回调的。
(2)、过滤器依赖与servlet容器,而拦截器不依赖与servlet容器。
(3)、拦截器只能对Action请求起作用,而过滤器则可以对几乎所有请求起作用。
(4)、拦截器可以访问Action上下文、值栈里的对象,而过滤器不能。
(5)、在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次。
17 .HashMap && ConcurrentHashMap
HashMap:先说HashMap,HashMap是线程不安全的,在并发环境下,可能会形成环状链表(扩容时可能造成),导致get操作时,cpu空转,所以,在并发环境中使用HashMap是非常危险的。
HashTable: HashTable和HashMap的实现原理几乎一样,差别无非是1.HashTable不允许key和value为null;2.HashTable是线程安全的。但是HashTable线程安全的策略实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。
HashTable性能差主要是由于所有操作需要竞争同一把锁,而如果容器中有多把锁,每一把锁锁一段数据,这样在多线程访问时不同段的数据时,就不会存在锁竞争了,这样便可以有效地提高并发效率。这就是ConcurrentHashMap所采用的"分段锁"思想。
HashMap Put操作:
在JDK1.7版本中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成,如下图所示:
Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样.
Segment的大小ssize默认为16,HashEntry最小的容量为2
JDK1.8的实现
JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本
ConcurrentHashMap中有一个segments[]数组:
final Segment[] segments;
Segment如下,继承了ReetrantLock:
static final class Segment extends ReentrantLock implementsSerializable {
transient volatile HashEntry[]table;}
segment就是一个HashMap的结构
static final class HashEntry {
final inthash;
finalKkey;
volatileVvalue;
volatileHashEntrynext;}
18 Redis和Memcached的异同。
可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS;
只支持简单的key/value数据结构,不像Redis可以支持丰富的数据类型。
无法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据全部丢失。
无法进行数据同步,不能将MC中的数据迁移到其他MC实例中。
Memcached内存分配采用Slab Allocation机制管理内存,value大小分布差异较大时会造成内存利用率降低, 并引发低利用率时依然出现踢出等问题。需要用户注重value设计。
支持多种数据结构,如string、 list、hash、set、zset等;
支持持久化操作,可以进行aof及rdb数据持久化到磁盘,从而进行数据备份或数据恢复等操作,较好的防止数据丢失的手段;
支持通过Replication进行数据复制,通过master-slave机制,可以实时进行数据的同步复制来实现HA;
单线程请求,所有命令串行执行,并发情况下不需要考虑数据一致性问题,但性能受限于CPU性能,故单实例CPU最高才可能达到5-6wQPS每秒;
支持pub/sub消息订阅机制,可以用来进行消息订阅与通知;
Redis在string类型上会消耗较多内存,可以使用hash表压缩存储以降低内存耗用。
19Redis作为分布式缓存可能会存在哪些问题,怎么解决?
缓存穿透预防及优化:缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中;缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义;解决方法:缓存空对象和布隆过滤器拦截;
缓存雪崩问题优化:由于缓存层承载着大量请求,有效的保护了存储层,但是如果缓存层由于某些原因整体不能提供服务,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况;预防和解决缓存雪崩问题,可以从以下三个方面进行着手:保证缓存层服务高可用性、依赖隔离组件为后端限流并降级、提前演练。
缓存热点key重建优化:开发人员使用缓存和过期时间的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但如果热点Key和重建缓存耗时两个问题同时出现,可能就会对应用造成致命的危害;解决方法:互斥锁(只允许一个线程重建缓存)、永远不过期(唯一不足的就是重构缓存期间会出现数据不一致的情况)。
20.Mysql
主键和索引的区别:
惟一地标识一行。
作为一个可以被外键有效引用的对象。
索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。
主键索引和唯一索引的区别:主键一定会创建一个唯一索引,但是有唯一索引的列不一定是主键;主键不允许为空值,唯一索引列允许空值;一个表只能有一个主键,但是可以有多个唯一索引主键可以被其他表引用为外键,唯一索引列不可以;主键是一种约束,而唯一索引是一种索引,是表的冗余数据结构,两者有本质的差别。
MyISAM和InnoDB索引结构有很大差异,这里以InnoDB为例,InnoDB的叶节点存储的是数据的行,而除了主键之外的列索引存储的是主键key,也就是说在查询的时候需要二次查询,先通过列索引找到主键,再通过主键索引找到row。
MySql的事务隔离级别有哪几种?
隔离级别用于表述并发事务之间的相互干扰程度,其基于锁机制进行并发控制。
可序列化(Serializable):事务一个接一个的执行,完全相互独立;实现可序列化要求在选定对象上的读锁和写锁保持直到事务结束后才能释放;在SELECT的查询中使用一个WHERE子句来描述一个范围时应该获得一个“范围锁”。
可重复度(Repeatable Read):事务A读取数据之后,对涉及的数据加锁,不允许其他事务进行修改,由于其他事务会插入新的数据,因此会产生幻读;对选定对象的读锁和写锁一直保持到事务结束,但不要求“范围锁”,因此可能会发生幻读;可重复读是MySQL的默认事务隔离级别。
读取已提交(Read Committed):只能看到其他事务已经提交的数据,避免了脏读,但存在不可重复读、幻读;DBMS需要对选定对象的写锁(write locks)一直保持到事务结束,但是读锁(read locks)在SELECT操作完成后马上释放,且不要求“范围锁”。
读取未提交(Read Uncommitted):可以看到其他事务没有提交的数据,出现脏读、不可重复读、幻读;
[PS补充]
不可重复读的重点是修改:同样的条件,读取过的数据,再次读取出来发现值不一样了。
幻读的重点在于新增或者删除:同样的条件,第1次和第2次读出来的记录数不一样。
MySql的MVVC有什么作用?
多版本并发控制(MVCC),来实现MySQL上的多事务并发访问时,隔离级别控制;
数据版本:并发事务执行时,同一行数据有多个版本;
事务版本:每个事务都有一个事务版本;
版本有序:版本是通过时间来标识的;
通过MVCC实现的效果是同一时刻、同一张表、多个并发事务,看到的数据是不同的。
MVCC本质使用了copy-on-write,为每个数据保留多份snapshot,不同snapshot之间,使用指针连接成链表;update操作,能看到的snapshot是受限的,是链表上小于等于当前事务版本的最大版本(读取已提交:离当前事务最近的已提交版本);
21.如何设计高性能、高并发、高可用
系统架构三个利器:RPC服务组件、消息中间件(交互异步化、流量削峰)、配置管理(灰度发布、降级);
无状态:接口层最重要的就是无状态,将有状态的数据剥离到数据库或缓存中;
如何改善延时:找关键路径(“28原则”)、空间换时间,如各级缓存;时间换空间,如传输压缩,解决网络传输的瓶颈;多核并行,减少锁竞争;更适合的算法和数据结构;通过性能测试和监控找出瓶颈;减少系统调用和上下文切换;
如何提高吞吐量:复制、扩容、异步化、缓存;
如何保障稳定性:提高可用性、分组和隔离、限流、降级、监控和故障切换;
理解高可用系统:要做到数据不丢,就必需要持久化;要做到服务高可用,就必需要有备用(复本),无论是应用结点还是数据结点;要做到复制,就会有数据一致性的问题;我们不可能做到100%的高可用,也就是说,我们能做到几个9个的SLA;