多线程即内存溢出处理方式

一、什么是多线程

Java给多线程编程提供了内置的支持。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
这里定义和线程相关的另一个术语-进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。多线程能满足程序员编写高效率的程序来达到充分利用CPU的目的。

二、一个线程的声明周期

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

五种状态:

1.新建状态:
使用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序start()这个线程。
2.就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
3.运行状态:
如果就绪状态的线程获取CPU资源,就可以执行run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
4.阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,是去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

  • 等待阻塞:运行转态中的线程执行wait()方法,使线程进入到等待阻塞状态。
  • 同步阻塞:线程在获取synchronized同步锁失败(因为同步锁等被其他线程占用)。
  • 其他阻塞:通过调用线程的sleep()或join()发出了I/O请求时,线程就会进入到阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕,线程重新转入就绪状态。
    5.死亡状态:
    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

三、线程的优先级

没一个Java线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java线程的优先级是一个整数,其取值范围是1(Thread.MIN_PRIORITY)-10 (Thread.MAX_PRIORITY)。
默认情况下,每一个线程都会分配一个优先级NORM_PRIORINT(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的行程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

四、多线程的创建方式

通过实现Runnable 接口

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;
   
   RunnableDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // 让线程睡眠一会
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}
 
public class TestThread {
 
   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo( "Thread-1");
      R1.start();
      
      RunnableDemo R2 = new RunnableDemo( "Thread-2");
      R2.start();
   }   
}

通过继承Thread类本身;

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   
   ThreadDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // 让线程睡眠一会
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}
 
public class TestThread {
 
   public static void main(String args[]) {
      ThreadDemo T1 = new ThreadDemo( "Thread-1");
      T1.start();
      
      ThreadDemo T2 = new ThreadDemo( "Thread-2");
      T2.start();
   }   
}

Thread方法

序号方法描述
1public void start()使该线程开始执行;Java虚拟机调用该线程的run方法。
2public void run()如果该线程是使用独立的Runnable运行对象构造的,则调Runnable对象的run方法;否则,该方法不执行任何操作并返回。
3public final void setName(String name)改变线程名称使之与参数name相同。
4public final void setPriority(int priotity)更改线程的优先级
5public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程
6public final void join(long millisec)等待该线程终止的时间最长为millis毫秒
7public void interrupt()中断线程
8public final boolean isAlive()测试线程是否处于活动状态

测试线程是否处于活动状态。上述方法是被Thread对象调用的。下面的方法是Thread类的静态方法

序号方法描述
1public static void yield()暂停当前正在执行的线程对象,并执行其他线程。
2public static void sleep(long millisec)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确的影响。
3public static void boolean holdsLock(Object x)当且仅当当前线程在指定的对象上保持监视器锁时,才返回true
4public static Thread currentThread()返回对当前正在执行的线程对象的引用
5public static void dumpStack()将当前线程的堆栈跟踪打印至标准错误流。

通过Callable和Future创建线程

public class CallableThreadTest implements Callable<Integer> {
    public static void main(String[] args)  
    {  
        CallableThreadTest ctt = new CallableThreadTest();  
        FutureTask<Integer> ft = new FutureTask<>(ctt);  
        for(int i = 0;i < 100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);  
            if(i==20)  
            {  
                new Thread(ft,"有返回值的线程").start();  
            }  
        }  
        try  
        {  
            System.out.println("子线程的返回值:"+ft.get());  
        } catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        } catch (ExecutionException e)  
        {  
            e.printStackTrace();  
        }  
  
    }
    @Override  
    public Integer call() throws Exception  
    {  
        int i = 0;  
        for(;i<100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" "+i);  
        }  
        return i;  
    }  
}

五、什么是线程池

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。线程池中的线程由系统管理,程序员不需要费力于线程管理,可以集中精力处理应用程序任务。

六、为什么使用线程池

  • 频繁的创建、销毁对象是一个很消耗性能的事情;
  • 如果用户量比较大,导致占用过多的资源,可能会导致我们的服务由于资源不足而宕机;

优点

  • 线程池中线程的使用率提升,减少对象的创建、销毁;
  • 线程池可以控制线程数,有效的提升服务器的使用资源,避免由于资源不足而发生宕机等问题;

线程池的作用

线程池的作用主要是为了提升系统的性能以及使用率。文章刚开始就提到,如果我们使用最简单的方式创建线程,如果用户量比较大,那么就会产生很多创建和销毁线程的动作,这会导致服务器在创建和销毁线程上消耗的性能可能要比处理实际业务花费的时间和性能更多。线程池就是为了解决这种问题而出现的。
同样思想的设计还有很多,比如数据库连接池,由于频繁的连接数据库,然而创建连接是一个和消耗性能的事情,所有数据库连接池就出现了。

线程池的四种使用方式

1.newCachedThreadPool

创建一个线程池,如果线程池中的线程数据过大,它可以有效的回收多余的线程,如果线程数不足,那么它可以创建新的线程。

public static void method() throws Exception {

    ExecutorService executor = Executors.newCachedThreadPool();

    for (int i = 0; i < 5; i++) {

        final int index = i;

        Thread.sleep(1000);

        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "  " + index);
            }
        });
    }
}

2.newFixedThreadPool

这种方式可以指定线程池中线程数。举个栗子.如果一间澡堂子最大只能容纳20个人同时洗澡,那么后面来的人只能在外面排队等待。如果硬往里冲,那么只会出现一种情况,摩擦摩擦…
首先测试一下最大容量为一个线程,那么会不会是我们预测的结果。

public static void method_01() throws InterruptedException {

    ExecutorService executor = Executors.newFixedThreadPool(1);

    for (int i = 0; i < 10; i++) {

        Thread.sleep(1000);
        final int index = i;

        executor.execute(() -> {
            try {
                Thread.sleep(2 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "  " + index);
        });
    }
    executor.shutdown();
}

优点:两个结果综合说明,newFixedThreadPool的线程数是可以进行控制的,因此我们可以通过控制最大线程来是我们的服务器打到最大使用率,同时又可以保证及时流量突然增大也不会占用服务器过多的资源

3.newScheduledThreadPool

该线程池支持定时,以及周期 性的任务执行,我们可以延迟任务的执行时间,也可以设置一个周期性的时间让任务重复执行。该线程池中有以下两种延迟的方法。

  • scheduleAtFixedRate
//测试一
public static void method_02() {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);

    executor.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            long start = new Date().getTime();
            System.out.println("scheduleAtFixedRate 开始执行时间:" +
                    DateFormat.getTimeInstance().format(new Date()));
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long end = new Date().getTime();
            System.out.println("scheduleAtFixedRate 执行花费时间=" + (end - start) / 1000 + "m");
            System.out.println("scheduleAtFixedRate 执行完成时间:" + DateFormat.getTimeInstance().format(new Date()));
            System.out.println("======================================");
        }
    }, 1, 5, TimeUnit.SECONDS);
}

总结:以上两种方式不同的地方是任务的执行时间,如果间隔时间大于任务的执行时间,任务不受执行时间的影响。如果间隔时间小于任务的执行时间,那么任务执行结束之后,会立马执行,至此间隔时间就会被打乱。

  • scheduleWithFixedDelay
//测试一
public static void method_03() {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

    executor.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            long start = new Date().getTime();
            System.out.println("scheduleWithFixedDelay 开始执行时间:" +
                    DateFormat.getTimeInstance().format(new Date()));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long end = new Date().getTime();
            System.out.println("scheduleWithFixedDelay执行花费时间=" + (end - start) / 1000 + "m");
            System.out.println("scheduleWithFixedDelay执行完成时间:"
                    + DateFormat.getTimeInstance().format(new Date()));
            System.out.println("======================================");
        }
    }, 1, 2, TimeUnit.SECONDS);
}
//测试二
public static void method_03() {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

    executor.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            long start = new Date().getTime();
            System.out.println("scheduleWithFixedDelay 开始执行时间:" +
                    DateFormat.getTimeInstance().format(new Date()));
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long end = new Date().getTime();
            System.out.println("scheduleWithFixedDelay执行花费时间=" + (end - start) / 1000 + "m");
            System.out.println("scheduleWithFixedDelay执行完成时间:"
                    + DateFormat.getTimeInstance().format(new Date()));
            System.out.println("======================================");
        }
    }, 1, 2, TimeUnit.SECONDS);
}

总结:同样的,根scheduleWithFixedDelay测试方法一样,可以测出scheduleWithFixedDelay的间隔时间不会受任务执行时间长短的影响。

4.new SingleThreadExecutor

这是一个单线程池,至始至终都由一个线程来执行。

public static void method_04() {

    ExecutorService executor = Executors.newSingleThreadExecutor();

    for (int i = 0; i < 5; i++) {
        final int index = i;
        executor.execute(() -> {
            try {
                Thread.sleep(2 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "   " + index);
        });
    }
    executor.shutdown();
}

七、内存溢出怎么办

OOM:OutOfMemory(内存溢出)

开发中常见异常:

1.StackOverFlowError

实例:递归调用后方法特别多,将栈空间撑爆

public class StackOverFlowErrorDemo{
	public static void main(){
		test();
	}
	private static void test(){
		//递归调用
		test();
	}
}

结果:

Exception in thread "main" java.lang.StackOverflowError
	at com.coolcoding.boot.oom.StackOverFlowErrorDemo.test(StackOverFlowErrorDemo.java:13)
	at com.coolcoding.boot.oom.StackOverFlowErrorDemo.test(StackOverFlowErrorDemo.java:13)
	at com.coolcoding.boot.oom.StackOverFlowErrorDemo.test(StackOverFlowErrorDemo.java:13)

2.java.lang.OutOfMemoryError:Java heap space

为了调试方便先设置最大堆内存和初始堆内存大小
JVM调试参数设置和说明

-Xms初始堆内存一般为内存的1/16
-Xms最大堆内存一般为内存的1/4

设置:-Xms:10m ; -Xmx:10m
案例:

public class JavaHeapSpaceDemo {public static void main(String[] args) {
        String str = "xxxxxxxxxdddddddddddd";while (true){
        //循环创建字符串对象
            str += str + new Random().nextInt(1111111111) + new Random().nextInt(222222222);
            //从常量池中获取字符串,若不存在,则创建一个字符串放到常量池中
            str.intern();
        }
    }
}

结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  at java.util.Arrays.copyOf(Arrays.java:3332)
  at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
  at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
  at java.lang.StringBuilder.append(StringBuilder.java:208)
  at com.coolcoding.boot.oom.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:14)

3.java.lang.OutOfMemoryError:GC overhead limit exceeded

简单理解:GC回收时间过长,时间过多耗费在GC中,但是回收效果不佳。
原理:

  • 回收过长指的是超过98%的时间用来做GC,并且回收了不到2%的堆内存
  • 连续多次GC,都回收了不到2%的极端情况下才会抛出异常,
  • 如不抛出异常,GC清理后的内存也会很快在此填满,迫使GC在此执行,
  • 就此形成恶性循环

JVM参数设置:
-Xms:10m;-Xmx:10m;-XX:+PrintGCDetails ;-XX:MaxDirectMemory=5m
案例:

public class GCOverheadDemo {public static void main(String[] args) {
        int i = 0;
        List<String> list = new ArrayList<>();
        try {
            while (true){
                list.add(String.valueOf(++i).intern());
            }
        }catch (Throwable e){
            e.printStackTrace();
            throw e;
        }}
}

结果:

[Full GC (Ergonomics) [PSYoungGen: 2048K->2048K(2560K)] [ParOldGen: 7049K->7046K(7168K)] 9097K->9094K(9728K), [Metaspace: 3886K->3886K(1056768K)], 0.0530256 secs] [Times: user=0.14 sys=0.00, real=0.05 secs] 
[Full GC (Ergonomics) java.lang.OutOfMemoryError: GC overhead limit exceeded
[PSYoungGen: 2048K->2048K(2560K)] [ParOldGen: 7050K->7046K(7168K)] 9098K->9094K(9728K), [Metaspace: 3886K->3886K(1056768K)], 0.0493460 secs] [Times: user=0.13 sys=0.00, real=0.05 secs] 
[Full GC (Ergonomics)   at java.lang.Integer.toString(Integer.java:401)
             //回收前2048k -> 回收后2048k 可见GC回收效果不佳
[PSYoungGen: 2048K->2048K(2560K)
[Full GC (Ergonomics) [PSYoungGen: 2048K->0K(2560K)] [ParOldGen: 7020K->1119K(7168K)] 9068K->1119K(9728K), [Metaspace: 3892K->3892K(1056768K)], 0.0176793 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
  at java.lang.String.valueOf(String.java:3099)
  at com.coolcoding.boot.oom.GCOverheadDemo.main(GCOverheadDemo.java:16)
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
  at java.lang.Integer.toString(Integer.java:401)
  at java.lang.String.valueOf(String.java:3099)
  at com.coolcoding.boot.oom.GCOverheadDemo.main(GCOverheadDemo.java:16)
Heap

4.java.lang.OutOfMemoryError:Direct buffer memory

直接内存溢出
原因分析:
直接内存崩溃,此处元空间并不在虚拟机中,而是使用本地内存,与GC无关。
常见于NIO程序中,使用ByteBuffer来读取和写入数据,这是基于通道channel和缓冲区buffer的IO方式,可以使用Native函数直接分配堆外内存,通过存储在JAVA推里面的DirectByteBuffer对象作为这块内存的引用进行操作。该方式在某些常见中能提高性能,因为避免了java堆和native堆中来回拷贝数据。
例如:

ByteBuffer.allocate(capability)堆内内存属于GC管辖,由于需要拷贝所以速度相对较慢
ByteBuffer.allocateDirect(capability)本地内存不属于GC管辖,由于不需要拷贝所以速度较快

如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收,此时堆内存充足,但是本地内存即将耗尽,那么再次尝试分配本地内存就会出现OOM
配置参数:
-Xms:10m;-Xmx:10m; -XX:+PrintGCDetails;+XX:MaxDirectMemorySize=5m

示例:

public class DirectBufferMemoryDemo {public static void main(String[] args) {
        System.out.println("本地内存 = " + (VM.maxDirectMemory() / 1024 / 1024) + "MB");
        try{
            TimeUnit.SECONDS.sleep(3);}catch (InterruptedException e){e.printStackTrace();}
        //allocateDirect 分配直接内存
        ByteBuffer.allocateDirect(6 * 1024 * 1024);
    }
}

结果:

"D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2019.3\lib\idea_rt.jar=49710:D:\Program Files\JetBrains\IntelliJ IDEA 2019.3\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\student.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\workspace\springboot-demo\target\classes;D:\mavenRepository\org\springframework\boot\spring-boot-starter-web\2.2.6.RELEASE\spring-boot-starter-web-2.2.6.RELEASE.jar;D:\mavenRepository\org\springframework\boot\spring-boot-starter\2.2.6.RELEASE\spring-boot-starter-2.2.6.RELEASE.jar;D:\mavenRepository\org\springframework\boot\spring-boot-starter-logging\2.2.6.RELEASE\spring-boot-starter-logging-2.2.6.RELEASE.jar;D:\mavenRepository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\mavenRepository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\mavenRepository\org\apache\logging\log4j\log4j-to-slf4j\2.12.1\log4j-to-slf4j-2.12.1.jar;D:\mavenRepository\org\apache\logging\log4j\log4j-api\2.12.1\log4j-api-2.12.1.jar;D:\mavenRepository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;D:\mavenRepository\org\yaml\snakeyaml\1.25\snakeyaml-1.25.jar;D:\mavenRepository\org\springframework\boot\spring-boot-starter-json\2.2.6.RELEASE\spring-boot-starter-json-2.2.6.RELEASE.jar;D:\mavenRepository\com\fasterxml\jackson\core\jackson-databind\2.10.3\jackson-databind-2.10.3.jar;D:\mavenRepository\com\fasterxml\jackson\core\jackson-annotations\2.10.3\jackson-annotations-2.10.3.jar;D:\mavenRepository\com\fasterxml\jackson\core\jackson-core\2.10.3\jackson-core-2.10.3.jar;D:\mavenRepository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.10.3\jackson-datatype-jdk8-2.10.3.jar;D:\mavenRepository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.10.3\jackson-datatype-jsr310-2.10.3.jar;D:\mavenRepository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.10.3\jackson-module-parameter-names-2.10.3.jar;D:\mavenRepository\org\springframework\boot\spring-boot-starter-validation\2.2.6.RELEASE\spring-boot-starter-validation-2.2.6.RELEASE.jar;D:\mavenRepository\jakarta\validation\jakarta.validation-api\2.0.2\jakarta.validation-api-2.0.2.jar;D:\mavenRepository\org\hibernate\validator\hibernate-validator\6.0.18.Final\hibernate-validator-6.0.18.Final.jar;D:\mavenRepository\org\jboss\logging\jboss-logging\3.4.1.Final\jboss-logging-3.4.1.Final.jar;D:\mavenRepository\com\fasterxml\classmate\1.5.1\classmate-1.5.1.jar;D:\mavenRepository\org\springframework\spring-web\5.2.5.RELEASE\spring-web-5.2.5.RELEASE.jar;D:\mavenRepository\org\springframework\spring-beans\5.2.5.RELEASE\spring-beans-5.2.5.RELEASE.jar;D:\mavenRepository\org\springframework\spring-webmvc\5.2.5.RELEASE\spring-webmvc-5.2.5.RELEASE.jar;D:\mavenRepository\org\springframework\spring-aop\5.2.5.RELEASE\spring-aop-5.2.5.RELEASE.jar;D:\mavenRepository\org\springframework\spring-context\5.2.5.RELEASE\spring-context-5.2.5.RELEASE.jar;D:\mavenRepository\org\springframework\spring-expression\5.2.5.RELEASE\spring-expression-5.2.5.RELEASE.jar;D:\mavenRepository\org\springframework\boot\spring-boot-devtools\2.2.6.RELEASE\spring-boot-devtools-2.2.6.RELEASE.jar;D:\mavenRepository\org\springframework\boot\spring-boot\2.2.6.RELEASE\spring-boot-2.2.6.RELEASE.jar;D:\mavenRepository\org\springframework\boot\spring-boot-autoconfigure\2.2.6.RELEASE\spring-boot-autoconfigure-2.2.6.RELEASE.jar;D:\mavenRepository\org\projectlombok\lombok\1.18.12\lombok-1.18.12.jar;D:\mavenRepository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\mavenRepository\org\springframework\spring-core\5.2.5.RELEASE\spring-core-5.2.5.RELEASE.jar;D:\mavenRepository\org\springframework\spring-jcl\5.2.5.RELEASE\spring-jcl-5.2.5.RELEASE.jar;D:\mavenRepository\cn\hutool\hutool-all\5.1.0\hutool-all-5.1.0.jar;D:\mavenRepository\org\apache\poi\poi-ooxml\4.1.2\poi-ooxml-4.1.2.jar;D:\mavenRepository\org\apache\poi\poi\4.1.2\poi-4.1.2.jar;D:\mavenRepository\commons-codec\commons-codec\1.13\commons-codec-1.13.jar;D:\mavenRepository\org\apache\commons\commons-collections4\4.4\commons-collections4-4.4.jar;D:\mavenRepository\org\apache\commons\commons-math3\3.6.1\commons-math3-3.6.1.jar;D:\mavenRepository\com\zaxxer\SparseBitSet\1.2\SparseBitSet-1.2.jar;D:\mavenRepository\org\apache\poi\poi-ooxml-schemas\4.1.2\poi-ooxml-schemas-4.1.2.jar;D:\mavenRepository\org\apache\xmlbeans\xmlbeans\3.1.0\xmlbeans-3.1.0.jar;D:\mavenRepository\org\apache\commons\commons-compress\1.19\commons-compress-1.19.jar;D:\mavenRepository\com\github\virtuald\curvesapi\1.06\curvesapi-1.06.jar;D:\mavenRepository\com\101tec\zkclient\0.11\zkclient-0.11.jar;D:\mavenRepository\org\apache\zookeeper\zookeeper\3.4.13\zookeeper-3.4.13.jar;D:\mavenRepository\org\slf4j\slf4j-log4j12\1.7.30\slf4j-log4j12-1.7.30.jar;D:\mavenRepository\log4j\log4j\1.2.17\log4j-1.2.17.jar;D:\mavenRepository\jline\jline\0.9.94\jline-0.9.94.jar;D:\mavenRepository\org\apache\yetus\audience-annotations\0.5.0\audience-annotations-0.5.0.jar;D:\mavenRepository\io\netty\netty\3.10.6.Final\netty-3.10.6.Final.jar;D:\mavenRepository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;D:\mavenRepository\com\google\guava\guava\29.0-jre\guava-29.0-jre.jar;D:\mavenRepository\com\google\guava\failureaccess\1.0.1\failureaccess-1.0.1.jar;D:\mavenRepository\com\google\guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;D:\mavenRepository\com\google\code\findbugs\jsr305\3.0.2\jsr305-3.0.2.jar;D:\mavenRepository\org\checkerframework\checker-qual\2.11.1\checker-qual-2.11.1.jar;D:\mavenRepository\com\google\errorprone\error_prone_annotations\2.3.4\error_prone_annotations-2.3.4.jar;D:\mavenRepository\com\google\j2objc\j2objc-annotations\1.3\j2objc-annotations-1.3.jar;D:\mavenRepository\com\baomidou\dynamic-datasource-spring-boot-starter\3.0.0\dynamic-datasource-spring-boot-starter-3.0.0.jar;D:\mavenRepository\org\springframework\boot\spring-boot-starter-aop\2.2.6.RELEASE\spring-boot-starter-aop-2.2.6.RELEASE.jar;D:\mavenRepository\org\aspectj\aspectjweaver\1.9.5\aspectjweaver-1.9.5.jar;D:\mavenRepository\mysql\mysql-connector-java\8.0.19\mysql-connector-java-8.0.19.jar;D:\mavenRepository\org\springframework\boot\spring-boot-starter-jdbc\2.2.6.RELEASE\spring-boot-starter-jdbc-2.2.6.RELEASE.jar;D:\mavenRepository\com\zaxxer\HikariCP\3.4.2\HikariCP-3.4.2.jar;D:\mavenRepository\org\springframework\spring-jdbc\5.2.5.RELEASE\spring-jdbc-5.2.5.RELEASE.jar;D:\mavenRepository\org\springframework\spring-tx\5.2.5.RELEASE\spring-tx-5.2.5.RELEASE.jar;D:\mavenRepository\org\mybatis\spring\boot\mybatis-spring-boot-starter\1.3.5\mybatis-spring-boot-starter-1.3.5.jar;D:\mavenRepository\org\mybatis\spring\boot\mybatis-spring-boot-autoconfigure\1.3.5\mybatis-spring-boot-autoconfigure-1.3.5.jar;D:\mavenRepository\org\mybatis\mybatis\3.4.6\mybatis-3.4.6.jar;D:\mavenRepository\org\mybatis\mybatis-spring\1.3.3\mybatis-spring-1.3.3.jar" com.coolcoding.boot.oom.DirectBufferMemoryDemo
[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->983K(9728K), 0.0023899 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
本地内存 = 5MB
[GC (Allocation Failure) [PSYoungGen: 2552K->504K(2560K)] 3031K->1543K(9728K), 0.0035927 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (System.gc()) [PSYoungGen: 781K->504K(2560K)] 1820K->1591K(9728K), 0.0013692 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 504K->0K(2560K)] [ParOldGen: 1087K->1333K(7168K)] 1591K->1333K(9728K), [Metaspace: 4029K->4029K(1056768K)], 0.0155318 secs] [Times: user=0.03 sys=0.00, real=0.02 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
  at java.nio.Bits.reserveMemory(Bits.java:694)
  at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
  at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
  at com.coolcoding.boot.oom.DirectBufferMemoryDemo.main(DirectBufferMemoryDemo.java:17)
Heap
 PSYoungGen      total 2560K, used 53K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 2% used [0x00000000ffd00000,0x00000000ffd0d748,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 7168K, used 1333K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 18% used [0x00000000ff600000,0x00000000ff74d618,0x00000000ffd00000)
 Metaspace       used 4061K, capacity 4568K, committed 4864K, reserved 1056768K
  class space    used 450K, capacity 460K, committed 512K, reserved 1048576K

5.java.lang.OutOfMemoryError:unable to create new native thread

高并发情况下会出现该异常,该异常与对应的平台有关
原因分析:
一个应用进程创建太多的线程,超过系统承载极限。如Linux默认允许单个进程可以创建的线程数是1024个。
解决方法:

  • 降低应用程序创建线程的数量,分析应用是否真的需要创建那么多线程,将线程数降到最低
  • 修改服务器配置,如修改Linux服务器配置,扩大Linux默认限制

案例:

public class UnableCreateNewThreadDemo {public static void main(String[] args) {
        //不断for循环创建线程
        for (int i = 0; ; i++) {
            new Thread(()->{
                //设置Integer.MAX_VALUE 以保持线程还在运行中
                try{ TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);}catch (InterruptedException e){e.printStackTrace();}
            }).start();
        }
    }
}

试验:
将该文件上传放到linux 下
执行javac -d 文件名
执行java 包名.文件名 启动测试应用
达到系统负载后则会抛出java.lang.OutOfMemoryError:unable to create new native thread
若要修改系统配置可以先查看对应用户的线程限制
linux下查看对应用户的线程限制个数

//查看限制个数
ulimit -u
//编辑修改
vim /etc/security/limits.d/90-nproc.conf

如修改对z3用户的配置
在这里插入图片描述

6.java.lang.OutOfMemoryError:Metaspace

在cmd中执行,一下命令可以查看默认的JVM参数基本配置,可以查看metaspaceSize的大小

java -XX:+PrintFlagsInitial

metaspace存放数据:(永久代JAVA8之后被metaspace取代了)

  • 虚拟机加载的类信息
  • 常量池
  • 静态变量
  • 即使编译后的代码
    JVM参数配置
-XX:MetaspaceSize=8m -XX:MAXMetaspaceSize=8m

案例:

public class MetaspaceOOMTest {
    //准备一个静态类
    static class OOMTest {
    };public static void main(String[] args) {
        int i = 0;
        try {
            while (true) {
                //循环不断创建静态类来填充metaspace的空间
                i++;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMTest.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        return methodProxy.invoke(o, args);
                    }
                });
                enhancer.create();
            }
        } catch (Exception e) {
        //记录查看创建了多少个静态类OOMTest
            System.out.println(i + "次后发生异常");
            e.printStackTrace();
        }
    }
}

结果:

279次后发生异常
org.springframework.cglib.core.CodeGenerationException: java.lang.OutOfMemoryError-->Metaspace
  at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:538)
  at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
  at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:582)
  at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:131)
  at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
  at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:569)
  at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:384)
  at com.coolcoding.boot.oom.MetaspaceOOMTest.main(MetaspaceOOMTest.java:34)
Caused by: java.lang.OutOfMemoryError: Metaspace
  at java.lang.ClassLoader.defineClass1(Native Method)
  at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
  at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:535)
  ... 7 more
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值