BootstrapClassLoader 启动类加载器
ExtensionClassLoader 扩展类加载器
这个加载器由sun.misc.Launcher
$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
ApplicationClassLoader 应用程序类加载器 (也称 系统类加载器)
由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回
值,所以一般也称它为系统类加载器。 它负责加载用户类路径(ClassPath)上所指定的类
库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一
般情况下这个就是程序中默认的类加载器
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载
好处
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。 例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。 相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。 如果读者
有兴趣的话,可以尝试去编写一个与rt.jar类库中已有类重名的Java类,将会发现可以正常编
译,但永远无法被加载运行
设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
主要用来查看Java线程的调用堆栈的,可以用来分析线程问题(如死锁)
用于监控虚拟机各种运行状态信息的命令行工具。他可以显示本地或远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GUI图形的服务器上,它是运行期定位虚拟机性能问题的首选工具。
动态代理
cglib
Cglib代理是功能最为强大的一种代理方式,因为其不仅解决了静态代理需要创建多个代理类的问题,还解决了jdk代理需要被代理对象实现某个接口的问题。
对于需要代理的类,如果能为其创建一个子类,并且在子类中编写相关的代理逻辑,因为“子类 instanceof 父类”,因而在进行调用时直接调用子类对象的实例,也可以达到代理的效果。Cglib代理的原理实际上是动态生成被代理类的子类字节码,由于其字节码都是按照jvm编译后的class文件的规范编写的,因而其可以被jvm正常加载并运行。这也就是Cglib代理为什么不需要为每个被代理类编写代理逻辑的原因。这里需要注意的是,根据Cglib实现原理,由于其是通过创建子类字节码的形式来实现代理的,如果被代理类的方法被声明final类型,那么Cglib代理是无法正常工作的,因为final类型方法不能被重写。
/**
*/
public class Suject {
public Suject(){}
public void request() {
System.out.println("update without implement any interface.");
}
}
/**
*/
public class SafetyCheckCallback implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before safety check.");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("after safety check.");
return result;
}
}
public class Client {
@Test
public void testCglibProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Suject.class);
enhancer.setCallback(new SafetyCheckCallback());
Suject proxy = (Suject) enhancer.create();
proxy.request();
}
}
jdk
jdk代理解决了静态代理需要为每个业务接口创建一个代理类的问题,虽然使用反射创建代理对象效率比静态代理稍低,但其在现代高速jvm中也是可以接受的,在Spring的AOP代理中默认就是使用的jdk代理实现的。
这里jdk代理的限制也是比较明显的,即其需要被代理的对象必须实现一个接口。
public class SafetyInvocationHandler implements InvocationHandler {
private Object target;
public SafetyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before safety check.");
Object result = method.invoke(target, args);
System.out.println("after safety check.");
return result;
}
}
public class Client {
@Test
public void testDynamicProxy() {
ISubject subject = new SubjectImpl();
ISubject proxySubject = (ISubject) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{ISubject.class}, new SafetyInvocationHandler(subject));
proxySubject.request();
}
}
静态代理
要为每个业务接口创建一个代理类
public interface ISubject {
void request();
}
public class SubjectImpl implements ISubject {
@Override
public void request() {
System.out.println("request SubjectImpl.");
}
}
public class SubjectProxy implements ISubject {
private ISubject target;
public SubjectProxy(ISubject target) {
this.target = target;
}
@Override
public void request() {
System.out.println("before safety check.");
target.request();
System.out.println("after safety check.");
}
}
public class Client {
@Test
public void testStaticProxy() {
ISubject subject = new SubjectImpl();
ISubject proxy = new SubjectProxy(subject);
proxy.request();
}
}
IOC
优势
重要模块
core
Core包是框架的最基础部分,并提供依赖注入(Dependency Injection)管理Bean容器功能。
context
aop
expression
orm
mvc
dao
ApplicationContext
public class App { } xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byName">
public static void main(String[] args) {
// 用我们的配置文件来启动一个 ApplicationContext
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
System.out.println("context 启动成功");
// 从 context 中取出我们的 Bean,而不是用 new MessageServiceImpl() 这种方式
MessageService messageService = context.getBean(MessageService.class);
// 这句将输出: hello world
System.out.println(messageService.getMessage());
}
@Transactional
bean
生命周期
Spring没有对bean的多线程安全问题做出任何保证与措施。
对于每个bean的线程安全问题,根本原因是每个bean自身的设计。不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了。
singleton
默认的scope,每个scope为singleton的bean都会被定义为一个单例对象,该对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。
prototype
bean被定义为在每次注入时都会创建一个新的对象。
request
bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。
session
bean被定义为在一个session的生命周期内创建一个单例对象
application
bean被定义为在ServletContext的生命周期中复用一个单例对象。
websocket
bean被定义为在websocket的生命周期中复用一个单例对象。
创建流程
初始化bean的时候执行,可以针对某个具体的bean在xml进行配置
初始化bean的时候执行,可以针对某个具体的bean在xml进行配置
核心注解 @SpringBootApplication
在SimpleDateFormat转换日期是通过Calendar对象来操作的,SimpleDateFormat继承DateFormat类,DateFormat类中维护一个Calendar对象.
在parse方法的最后,会调用CalendarBuilder的establish方法,入参就是SimpleDateFormat维护的Calendar实例,在establish方法中会调用calendar的clear方法
我们要考虑一种会发生内存泄漏的情况,如果ThreadLocal被设置为null后,而且没有任何强引用指向它,根据垃圾回收的可达性分析算法,ThreadLocal将会被回收。这样一来,ThreadLocalMap中就会含有key为null的Entry,而且ThreadLocalMap是在Thread中的,只要线程迟迟不结束,这些无法访问到的value会形成内存泄漏。为了解决这个问题,ThreadLocalMap中的getEntry()、set()和remove()函数都会清理key为null的Entry,以下面的getEntry()函数的源码为例。
/**
Get the entry associated with key. This method
itself handles only the fast path: a direct hit of existing
key. It otherwise relays to getEntryAfterMiss. This is
designed to maximize performance for direct hits, in part
by making this method readily inlinable.
@param key the thread local object
@return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
/**
Version of getEntry method for use when key is not found in
its direct hash slot.
@param key the thread local object
@param i the table index for key’s hash code
@param e the entry at table[i]
@return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 清理key为null的Entry
while (e != null) {
ThreadLocal> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性
实例方法isInterrupted()只会判断线程是否已中断,即判断中断标志位。而静态方法会清楚中断标志位。
优雅地响应中断甚至怎样抛出InterruptedException类比较高深,与C、C++语言不同,Java没有提供一种安全直接的方法来直接停止某个线程,而是提供了一个种叫做中断协议的东西,开放给程序员自己去实现线程怎么终止,Java语言的这种中断机制是一种协议机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程或方法自己处理中断。这好比是家里的父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则完全取决于自己
线程可以调用中断自己的方法interrupt()方法,但是中断也是有条件的,在线程调用interrupt()方法的时候虚拟机会在此线程上标记一个标志(这个中断标志只是一个布尔类型的变量),代表这个线程可能被中断,在后面的中断操作也是根据这个中断标志执行的。可以说interrupt()方法是一种友好的方法,总是和虚拟机商量着来。如果一个线程处于了阻塞状态(如线程调用了sleep()、join()、wait()、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法(sleep()、join()、wait()及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
每个线程都有一个与线程是否已中断的相关联的 Boolean 属性,用于表示线程的中断状态(interrupted status)。中断状态初始时为 false;当一个线程A通过调用 threadB.interrupt() 中断线程B时,会出现以下两种情况之一。如果那个线程B在执行一个低级可中断阻塞方法,例如 Thread.sleep()、 Thread.join() 或 Object.wait(),那么它将取消阻塞并抛出 InterruptedException。否则, interrupt() 只是设置线程B的中断状态。 在被中断线程B中运行的代码以后可以轮询中断状态,看看它是否被请求停止正在做的事情。中断状态可以通过 Thread.isInterrupted() 来读取,并且可以通过一个名为 Thread.interrupted() 的操作读取和清除。
可响应中断线程
线程可以调用中断自己的方法interrupt()方法,但是中断也是有条件的,在线程调用interrupt()方法的时候虚拟机会在此线程上标记一个标志(这个中断标志只是一个布尔类型的变量),代表这个线程可能被中断,在后面的中断操作也是根据这个中断标志执行的。可以说interrupt()方法是一种友好的方法,总是和虚拟机商量着来。如果一个线程处于了阻塞状态(如线程调用了sleep()、join()、wait()、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法(sleep()、join()、wait()及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
每个线程都有一个与线程是否已中断的相关联的 Boolean 属性,用于表示线程的中断状态(interrupted status)。中断状态初始时为 false;当一个线程A通过调用 threadB.interrupt() 中断线程B时,会出现以下两种情况之一。如果那个线程B在执行一个低级可中断阻塞方法,例如 Thread.sleep()、 Thread.join() 或 Object.wait(),那么它将取消阻塞并抛出 InterruptedException。否则, interrupt() 只是设置线程B的中断状态。 在被中断线程B中运行的代码以后可以轮询中断状态,看看它是否被请求停止正在做的事情。中断状态可以通过 Thread.isInterrupted() 来读取,并且可以通过一个名为 Thread.interrupted() 的操作读取和清除。
wait
wait()释放CPU执行权,也释放同步锁,使得其他线程可以使用同步控制块或者方法。
sleep
sleep()释放CPU执行权,但不释放同步锁;
join
不可响应中断线程
Error
程序无法处理的错误,如OutOfMemoryError、ThreadDeath等。
这些异常发生时, Java虚拟机(JVM)一般会选择线程终止。
Exception
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。
受检异常
RuntimeException
非受检异常
非运行时异常
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。
从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。
标记清除算法
复制算法
标记整理算法
分代收集算法
serial
新生代收集器,必须暂停其他所有的工作线程,直到它收集结束
parnew (Serial收集器的多线程版本)
Serial收集器的多线程版本
parallel scavenge
serial old
parallel old
cms
G1
java.lang.OutOfMemoryError: PermGen space
JVM管理两种类型的内存,堆和非堆。堆是给开发人员用的上面说的就是,是在JVM启动时创建;非堆是留给JVM自己用的,用来存放类的信息的。它和堆不同,运行期内GC不会释放空间。如果web app用了大量的第三方jar或者应用有太多的class文件而恰好MaxPermSize设置较小,超出了也会导致这块内存的占用过多造成溢出,或者tomcat热部署时侯不会清理前面加载的环境,只会将context更改为新部署的,非堆存的内容就会越来越多。
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的应用中有很CLASS的话,就很可能出现PermGen space错误,这种错误常见在web服务器对JSP进行pre compile的时候。如果你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。
一个最佳的配置例子:(经过本人验证,自从用此配置之后,再未出现过tomcat死掉的情况)
set JAVA_OPTS=-Xms800m -Xmx800m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m
只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。
可能的原因
改进
StackOverFlowError
原因
改进
java.lang.OutOfMemoryError: java heap space
java.lang.OutOfMemoryError: unable to create native thread
产生这种异常的原因是由于系统在不停地创建大量的线程,且不进行释放。
可考虑增加ThreadStackSize的值
Executor
SimpleExecutor
每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor
执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map
BatchExecutor
执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
延迟加载
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
插件运行原理
Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
支持的四种接口
当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。
Java中的对象都是在堆上分配的,而垃圾回收机制会回收堆中不再使用的对象,但是筛选可回收对象,回收对象还有整理内存都需要消耗时间。如果能够通过逃逸分析确定某些对象不会逃出方法之外,那就可以让这个对象在栈上分配内存,这样该对象所占用的内存空间就可以随栈帧出栈而销毁,就减轻了垃圾回收的压力。
栈上分配
在一般应用中,如果不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了。
同步消除
有时锁并不是程序员所写的,有的是JDK实现中就有锁的,比如Vector和StringBuffer这样的类,它们中的很多方法都是有锁的。当我们在一些不会有线程安全的情况下使用这些类的方法时,达到某些条件时,编译器会将锁消除来提高性能。
标量替换
Java虚拟机中的原始数据类型(int,long等数值类型以及reference类型等)都不能再进一步分解,它们就可以称为标量。相对的,如果一个数据可以继续分解,那它称为聚合量,Java中最典型的聚合量是对象。如果逃逸分析证明一个对象不会被外部访问,并且这个对象是可分解的,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。
synchronized
synchronized 是可重入锁!
假设我们现在不知道它是不是一个可重入锁,那我们就应该想方设法来验证它是不是可重入锁?怎么验证呢?看下面的代码!(这些方法输出了相同的线程名称,表明即使递归使用synchronized也没有发生死锁,证明其是可重入的。)
public class Xttblog extends SuperXttblog {
public static void main(String[] args) {
Xttblog child = new Xttblog();
child.doSomething();
}
public synchronized void doSomething() {
System.out.println("child.doSomething()" + Thread.currentThread().getName());
doAnotherThing(); // 调用自己类中其他的synchronized方法
}
private synchronized void doAnotherThing() {
super.doSomething(); // 调用父类的synchronized方法
System.out.println("child.doAnotherThing()" + Thread.currentThread().getName());
}
}
class SuperXttblog {
public synchronized void doSomething() {
System.out.println("father.doSomething()" + Thread.currentThread().getName());
}
}
ReentrantLock
基于AQS实现,每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。
1.构造器
Servlet第一次处理请求时,会调用构造器,来创建Servlet实例。
只会调用一次,Servlet是单例模式,他是以多线程的方式调用service()方法.
Servlet不是线程安全,所以尽量不要再service()方法中操作全局变量。
init()
构造器调用之后马上被调用,用来初化Servlet,只会调用一次
service()
Servlet每次处理请求时都会调用service()方法,用来处理请求
destroy()
Servlet对象销毁前(WEB项目卸载时)调用,用来做一些收尾工作,释放资源
HttpServlet
HttpServlet继承了GenericServlet,而GenericServlet实现Servlet接口.
HttpServlet重写service()方法:
1.在该方法中先将ServletRequest和ServletResponse强转为了HttpServletRequest和HttpServletResponse。
2.然调用重载的service()方法,并将刚刚强转得到对象传递到重载的方法中。
doGet
doPost
doHead
类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
doDelete
doPut
doOptions
返回服务器针对特定资源所支持的HTTP请求方法
doTrace
回显服务器收到的请求,主要用于测试或诊断