See More

PK RŒ¨N meta.xml‰vþlinxqXMindR3.7.8.201807240049828461#FFFFFFPKåÐÙíŽ ‰ PK RŒ¨N content.xml \¿£@Java类加载机制加载过程加载验证准备解析初始化类加载器BootstrapClassLoader 启动类加载器ExtensionClassLoader 扩展类加载器

这个加载器由sun.misc.Launcher

$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

这个加载器由sun.misc.Launcher $ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。ApplicationClassLoader 应用程序类加载器 (也称 系统类加载器)

由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回

值,所以一般也称它为系统类加载器。 它负责加载用户类路径(ClassPath)上所指定的类

库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一

般情况下这个就是程序中默认的类加载器

由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回 值,所以一般也称它为系统类加载器。 它负责加载用户类路径(ClassPath)上所指定的类 库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一 般情况下这个就是程序中默认的类加载器
双亲委派模型

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载
好处

使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。 例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。 相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。 如果读者

有兴趣的话,可以尝试去编写一个与rt.jar类库中已有类重名的Java类,将会发现可以正常编

译,但永远无法被加载运行

使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。 例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。 相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。 如果读者 有兴趣的话,可以尝试去编写一个与rt.jar类库中已有类重名的Java类,将会发现可以正常编 译,但永远无法被加载运行
JVM参数Xss

设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
分析工具jstack

主要用来查看Java线程的调用堆栈的,可以用来分析线程问题(如死锁)

主要用来查看Java线程的调用堆栈的,可以用来分析线程问题(如死锁)
runnable
jstat(JVM Statistics Monitoring Tool)

用于监控虚拟机各种运行状态信息的命令行工具。他可以显示本地或远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GUI图形的服务器上,它是运行期定位虚拟机性能问题的首选工具。

用于监控虚拟机各种运行状态信息的命令行工具。他可以显示本地或远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GUI图形的服务器上,它是运行期定位虚拟机性能问题的首选工具。
线程池newCachedThreadPoolnewFixedThreadPoolnewSingleThreadExecutorNewScheduledThreadPoolstringImmutable 不可变性保证线程安全,无需同步。不可变对象可以被自由地共享如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。通过反射可破坏其可变性: String str = "123"; System.out.println(str); Field field = String.class.getDeclaredField("value"); field.setAccessible(true); char[] value = (char[]) field.get(str); value[1] = '3'; System.out.println(str);代理模式好处为其他对象提供一种代理以控制对这个对象的访问提供统一接口,在不影响系统调用的情况下进行扩展,即遵循开放封闭原则代理类型动态代理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();

}

}

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(); } }
被代理类不能有final修饰符,否则报错被代理方法不能有final修饰符,否则代理无效
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();

}

}

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();

}

}

要为每个业务接口创建一个代理类 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(); } }
框架SpringIOC优势对象的统一托管规范生命周期灵活的依赖注入一致的获取对象方式(默认单例)重要模块core

Core包是框架的最基础部分,并提供依赖注入(Dependency Injection)管理Bean容器功能。

Core包是框架的最基础部分,并提供依赖注入(Dependency Injection)管理Bean容器功能。
contextaopexpressionormmvc1. 用户请求发送至DispatchServlet2. DispatcherServlet收到请求调用HandlerMapping处理3. HandlerMapping找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及拦截器(如果有则生成)一并返回给DispatcherServlet4. DispatcherServlet调用HandlerAdapter处理5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)6. Controller执行完成返回ModelAndView7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器9. ViewReslover解析后返回具体Viewdao
ApplicationContextClassPathXmlApplicationContextFileSystemXmlApplicationContextAnnotationConfigApplicationContext

<?xml version="1.0" encoding="UTF-8" ?>

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

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">

<bean id="messageService" class="com.javadoop.example.MessageServiceImpl"/>

</beans>

public class App {

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());

}

}

<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> <bean id="messageService" class="com.javadoop.example.MessageServiceImpl"/> </beans> public class App { 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生命周期singleton

默认的scope,每个scope为singleton的bean都会被定义为一个单例对象,该对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。

默认的scope,每个scope为singleton的bean都会被定义为一个单例对象,该对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。
prototype

bean被定义为在每次注入时都会创建一个新的对象。

bean被定义为在每次注入时都会创建一个新的对象。
request

bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。

bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。
session

bean被定义为在一个session的生命周期内创建一个单例对象

bean被定义为在一个session的生命周期内创建一个单例对象
application

bean被定义为在ServletContext的生命周期中复用一个单例对象。

bean被定义为在ServletContext的生命周期中复用一个单例对象。
websocket

bean被定义为在websocket的生命周期中复用一个单例对象。

bean被定义为在websocket的生命周期中复用一个单例对象。

Spring没有对bean的多线程安全问题做出任何保证与措施。

对于每个bean的线程安全问题,根本原因是每个bean自身的设计。不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了。

Spring没有对bean的多线程安全问题做出任何保证与措施。 对于每个bean的线程安全问题,根本原因是每个bean自身的设计。不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了。
创建流程1. 首先执行BeanPostProcessor中的postProcessBeforeInitialization2. 执行InitializingBean接口中的afterPropertiesSet方法

初始化bean的时候执行,可以针对某个具体的bean在xml进行配置

初始化bean的时候执行,可以针对某个具体的bean在xml进行配置
3. 再执行xml配置中的 init-method方法

初始化bean的时候执行,可以针对某个具体的bean在xml进行配置

初始化bean的时候执行,可以针对某个具体的bean在xml进行配置
4. 最后执行BeanPostProcessor中的postProcessAfterInitialization
bean非线程安全。对于每个bean的线程安全问题,根本原因是每个bean自身的设计。不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了。
SpringBoot核心注解 @SpringBootApplicationSpringBootConfigurationEnableAutoConfigurationComponentScan
常见线程安全问题SimpleDateFormat

在SimpleDateFormat转换日期是通过Calendar对象来操作的,SimpleDateFormat继承DateFormat类,DateFormat类中维护一个Calendar对象.

在parse方法的最后,会调用CalendarBuilder的establish方法,入参就是SimpleDateFormat维护的Calendar实例,在establish方法中会调用calendar的clear方法

在SimpleDateFormat转换日期是通过Calendar对象来操作的,SimpleDateFormat继承DateFormat类,DateFormat类中维护一个Calendar对象. 在parse方法的最后,会调用CalendarBuilder的establish方法,入参就是SimpleDateFormat维护的Calendar实例,在establish方法中会调用calendar的clear方法
常见内存泄露类型ThreadLocal

我们要考虑一种会发生内存泄漏的情况,如果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;

}

我们要考虑一种会发生内存泄漏的情况,如果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; }
虽然Entry是弱引用,里面的key在gc发生时会被回收,但是对应的value还是会造成内存泄露
引用类型强引用软引用弱引用虚引用仅有弱引用指向的对象,只要发生gc就会被回收仅有软引用指向的对象,只有发生gc且内存不足,才会被回收普通的引用,强引用指向的对象不会被回收 并发volatile1. 保证了不同线程对这个变量进行操作时的可见性,即一个 线程修改了某个变量的值,这新值对其他线程来说是立即可见的。2. 禁止进行指令重排序。synchronized (1).volatile 仅能使用在变量级别;synchronized 则可以 使用在变量、方法、代码块和类级别的 (2).volatile 仅能实现变量的修改可见性,并不能保证原子性; synchronized 则可以保证变量的修改可见性和原子性 (3).volatile 不会造成线程的阻塞;synchronized 可能 会造成线程的阻塞。 (4).volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化反射在运行状态中,对于任意一个实体类,都能够知道这个类 的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性<