GoMind Hub

Java

计算机知识思维导图:Java。网页展示前三层结构,可在线查看完整脑图并下载 GoMind 文件。

2026-08-25

计算机知识学习资料思维导图
## Java
### Java
#### 类加载机制
##### 加载过程
##### 类加载器
##### 双亲委派模型
#### JVM参数
##### Xss
#### 分析工具
##### jstack
##### jstat(JVM Statistics Monitoring Tool)
#### 线程池
##### newCachedThreadPool
##### newFixedThreadPool
##### newSingleThreadExecutor
##### NewScheduledThreadPool
#### string
##### Immutable 不可变性
#### 代理模式
##### 好处
##### 代理类型
#### 框架
##### Spring
##### SpringBoot
#### 常见线程安全问题
##### SimpleDateFormat
#### 常见内存泄露类型
##### ThreadLocal
#### 引用类型
##### 强引用
##### 软引用
##### 弱引用
##### 虚引用
##### 仅有弱引用指向的对象,只要发生gc就会被回收
##### 仅有软引用指向的对象,只有发生gc且内存不足,才会被回收
##### 普通的引用,强引用指向的对象不会被回收
#### 并发
##### volatile
##### synchronized
##### (1).volatile 仅能使用在变量级别;synchronized 则可以 使用在变量、方法、代码块和类级别的 (2).volatile 仅能实现变量的修改可见性,并不能保证原子性; synchronized 则可以保证变量的修改可见性和原子性 (3).volatile 不会造成线程的阻塞;synchronized 可能 会造成线程的阻塞。 (4).volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化
#### 反射
##### 在运行状态中,对于任意一个实体类,都能够知道这个类 的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性
#### 线程
##### wait与sleep
##### 实例方法thread.isInterrupted() 与 静态方法Thread.interrupted()
##### 中断协议
##### interrupt ( )
##### Untitled node
#### 异常
##### throwable
#### GC
##### 判断对象是否存活的算法
##### 垃圾收集算法
##### 垃圾收集器
##### Untitled node
##### 内存溢出分析
##### Untitled node
#### ORM
##### Mybatis
#### 逃逸分析
##### 线程逃逸
##### 方法逃逸
##### 高效优化
#### 锁
##### 可重入锁
##### 不可重如锁
#### 集合
##### Untitled node
#### Servlet
##### 生命周期
##### 实现

> 仅展示前三层结构;请在线查看完整脑图或下载 GoMind 文件。

Java

Java
加载
验证
准备
解析
初始化
加载过程
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类,将会发现可以正常编

译,但永远无法被加载运行
双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载
类加载机制
Xss
设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
JVM参数
runnable
jstack
主要用来查看Java线程的调用堆栈的,可以用来分析线程问题(如死锁)
jstat(JVM Statistics Monitoring Tool)
用于监控虚拟机各种运行状态信息的命令行工具。他可以显示本地或远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GUI图形的服务器上,它是运行期定位虚拟机性能问题的首选工具。
分析工具
newCachedThreadPool
newFixedThreadPool
newSingleThreadExecutor
NewScheduledThreadPool
线程池
保证线程安全,无需同步。不可变对象可以被自由地共享
如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在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);
Immutable 不可变性
string
为其他对象提供一种代理以控制对这个对象的访问
提供统一接口,在不影响系统调用的情况下进行扩展,即遵循开放封闭原则
好处
被代理类不能有final修饰符,否则报错
被代理方法不能有final修饰符,否则代理无效
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
1. 用户请求发送至DispatchServlet
2. DispatcherServlet收到请求调用HandlerMapping处理
3. HandlerMapping找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及拦截器(如果有则生成)一并返回给DispatcherServlet
4. DispatcherServlet调用HandlerAdapter处理
5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)
6. Controller执行完成返回ModelAndView
7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9. ViewReslover解析后返回具体View
mvc
dao
重要模块
ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
AnnotationConfigApplicationContext
Untitled node
ApplicationContext
<?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
singleton
默认的scope,每个scope为singleton的bean都会被定义为一个单例对象,该对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。
prototype
bean被定义为在每次注入时都会创建一个新的对象。
request
bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。
session
bean被定义为在一个session的生命周期内创建一个单例对象
application
bean被定义为在ServletContext的生命周期中复用一个单例对象。
websocket
bean被定义为在websocket的生命周期中复用一个单例对象。
生命周期
Spring没有对bean的多线程安全问题做出任何保证与措施。



对于每个bean的线程安全问题,根本原因是每个bean自身的设计。不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了。
1. 首先执行BeanPostProcessor中的postProcessBeforeInitialization
2. 执行InitializingBean接口中的afterPropertiesSet方法
初始化bean的时候执行,可以针对某个具体的bean在xml进行配置
3. 再执行xml配置中的 init-method方法
初始化bean的时候执行,可以针对某个具体的bean在xml进行配置
4. 最后执行BeanPostProcessor中的postProcessAfterInitialization
创建流程
bean非线程安全。对于每个bean的线程安全问题,根本原因是每个bean自身的设计。不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了。
bean
Spring
SpringBootConfiguration
EnableAutoConfiguration
ComponentScan
核心注解 @SpringBootApplication
SpringBoot
框架
Untitled node
SimpleDateFormat
在SimpleDateFormat转换日期是通过Calendar对象来操作的,SimpleDateFormat继承DateFormat类,DateFormat类中维护一个Calendar对象.



在parse方法的最后,会调用CalendarBuilder的establish方法,入参就是SimpleDateFormat维护的Calendar实例,在establish方法中会调用calendar的clear方法
常见线程安全问题
虽然Entry是弱引用,里面的key在gc发生时会被回收,但是对应的value还是会造成内存泄露
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;

}
常见内存泄露类型
强引用
软引用
弱引用
虚引用
仅有弱引用指向的对象,只要发生gc就会被回收
仅有软引用指向的对象,只有发生gc且内存不足,才会被回收
普通的引用,强引用指向的对象不会被回收
引用类型
1. 保证了不同线程对这个变量进行操作时的可见性,即一个 线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2. 禁止进行指令重排序。
volatile
synchronized
(1).volatile 仅能使用在变量级别;synchronized 则可以 使用在变量、方法、代码块和类级别的 (2).volatile 仅能实现变量的修改可见性,并不能保证原子性; synchronized 则可以保证变量的修改可见性和原子性 (3).volatile 不会造成线程的阻塞;synchronized 可能 会造成线程的阻塞。 (4).volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化
并发
在运行状态中,对于任意一个实体类,都能够知道这个类 的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性
反射
在等待时wait 会释放锁,而sleep 一直持有锁。 wait 通常被用于线程间交互,sleep 通常被用于暂停执行
wait与sleep
实例方法thread.isInterrupted() 与 静态方法Thread.interrupted()
实例方法isInterrupted()只会判断线程是否已中断,即判断中断标志位。而静态方法会清楚中断标志位。
中断协议
优雅地响应中断甚至怎样抛出InterruptedException类比较高深,与C、C++语言不同,Java没有提供一种安全直接的方法来直接停止某个线程,而是提供了一个种叫做中断协议的东西,开放给程序员自己去实现线程怎么终止,Java语言的这种中断机制是一种协议机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程或方法自己处理中断。这好比是家里的父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则完全取决于自己

--------------------- 

作者:TY1972 

来源:CSDN 

原文:https://blog.csdn.net/meiliangdeng1990/article/details/80559012 

版权声明:本文为博主原创文章,转载请附上博文链接!
wait
wait()释放CPU执行权,也释放同步锁,使得其他线程可以使用同步控制块或者方法。
sleep
sleep()释放CPU执行权,但不释放同步锁;
join
InterruptedException 当你使用sleep这些方式时,java会从语法级别强制你使用try...cacth...语句接收InterruptedException,即阻塞中的线程如果收到中断信号,会通抛出InterruptedException异常将具体响应处理转移给开发者,同时把中断状态置为true
可响应中断线程
线程可以调用中断自己的方法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() 的操作读取和清除。

--------------------- 

作者:TY1972 

来源:CSDN 

原文:https://blog.csdn.net/meiliangdeng1990/article/details/80559012 

版权声明:本文为博主原创文章,转载请附上博文链接!
InputStream
OutputStream
不可响应中断线程
interrupt ( )
线程可以调用中断自己的方法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() 的操作读取和清除。

--------------------- 

作者:TY1972 

来源:CSDN 

原文:https://blog.csdn.net/meiliangdeng1990/article/details/80559012 

版权声明:本文为博主原创文章,转载请附上博文链接!
Untitled node
线程
Error
程序无法处理的错误,如OutOfMemoryError、ThreadDeath等。

这些异常发生时,  Java虚拟机(JVM)一般会选择线程终止。
NullPointerException
ArrayIndexOutOfBoundsException
IndexOutOfBoundsException
ArithmeticException
ClassNotFoundException
IllegalAccessException
IllegalArgumentException
IllegalStateException
RuntimeException
受检异常
IOException
SQLException
InterruptedException
IllegalAccessException
ClassNotFoundException
NoSuchMetodException
非运行时异常
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。 

    从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。
非受检异常
Exception
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。
throwable
异常
引用计数法
可达性分析算法
判断对象是否存活的算法
效率问题,标记和清除两个过程的效率都不高
空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
标记清除算法
复制算法
标记整理算法
分代收集算法
垃圾收集算法
serial
新生代收集器,必须暂停其他所有的工作线程,直到它收集结束
parnew (Serial收集器的多线程版本)
Serial收集器的多线程版本
parallel scavenge
serial old
parallel old
cms
G1
垃圾收集器
Untitled node
MaxPermSize设置过小(默认为4M)
tomcat热部署时没有清理之前加载的环境
引用了大量三方jar包
可能的原因
将相同的第三方jar文件移置到tomcat/shared/lib目录下, 这样可以达到减少jar 文档重复占用内存的目的
改进
在 JDK 1.8 中, HotSpot 已经没有 “PermGen space”这个区间了,取而代之是一个叫做 Metaspace(元空间) 的东西。
移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap
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”。
存在递归调用
存在循环依赖
原因
尽量将递归调用铺平,减少栈深度
如果你确认递归实现是正确的,为了允许大量的调用,你可以增加栈的大小。依赖于安装的 Java 虚拟机,默认的线程栈大小可能是 512KB 或者 1MB。你可以使用 -Xss 标识来增加线程栈的大小。这个标识即可以通过项目的配置也可以通过命令行来指定
改进
StackOverFlowError
java.lang.OutOfMemoryError: java heap space
java.lang.OutOfMemoryError: unable to create native thread
产生这种异常的原因是由于系统在不停地创建大量的线程,且不进行释放。



可考虑增加ThreadStackSize的值
内存溢出分析
Untitled node
GC
SimpleExecutor
每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor
执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
BatchExecutor
执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
Executor
延迟加载
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()方法的调用。这就是延迟加载的基本原理。



作者:进阶的小码农

链接:https://juejin.im/post/5ab2012df265da23945f5d01

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
ParameterHandler
ResultSetHandler
StatementHandler
Executor
支持的四种接口
插件运行原理
Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
Mybatis
ORM
线程逃逸
方法逃逸
当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。
栈上分配
在一般应用中,如果不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了。
同步消除
有时锁并不是程序员所写的,有的是JDK实现中就有锁的,比如Vector和StringBuffer这样的类,它们中的很多方法都是有锁的。当我们在一些不会有线程安全的情况下使用这些类的方法时,达到某些条件时,编译器会将锁消除来提高性能。
标量替换
Java虚拟机中的原始数据类型(int,long等数值类型以及reference类型等)都不能再进一步分解,它们就可以称为标量。相对的,如果一个数据可以继续分解,那它称为聚合量,Java中最典型的聚合量是对象。如果逃逸分析证明一个对象不会被外部访问,并且这个对象是可分解的,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。
高效优化
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. ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。 2. ReentrantLock还提供了条件Condition,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock更加适合 3. ReentrantLock支持可中断处理 4. ReentrantLock提供了一个Condition(条件)类,用来实现唤醒与当前锁相关联的线程,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程,粒度更细,效率更高。
可重入锁
不可重如锁
锁
Untitled node
集合
1.构造器
Servlet第一次处理请求时,会调用构造器,来创建Servlet实例。

只会调用一次,Servlet是单例模式,他是以多线程的方式调用service()方法.

Servlet不是线程安全,所以尽量不要再service()方法中操作全局变量。
init()
构造器调用之后马上被调用,用来初化Servlet,只会调用一次
service()
Servlet每次处理请求时都会调用service()方法,用来处理请求
destroy()
Servlet对象销毁前(WEB项目卸载时)调用,用来做一些收尾工作,释放资源
生命周期
doGet
doPost
doHead
类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
doDelete
doPut
doOptions
返回服务器针对特定资源所支持的HTTP请求方法
doTrace
回显服务器收到的请求,主要用于测试或诊断
HttpServlet
HttpServlet继承了GenericServlet,而GenericServlet实现Servlet接口.



HttpServlet重写service()方法:

 1.在该方法中先将ServletRequest和ServletResponse强转为了HttpServletRequest和HttpServletResponse。



2.然调用重载的service()方法,并将刚刚强转得到对象传递到重载的方法中。
实现
Servlet