1、案例一:堆溢出演示
1.1、简单springboot项目创建
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.5</version><relativePath/></parent><groupId>blnp.net.cn</groupId><artifactId>demo-items</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId><version>2.2.5.RELEASE</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.0</version></dependency></dependencies>
</project>
完整代码,可以通过下载文章顶部的《案例一:堆溢出演示代码》,进行演示复现。
1.2、配置项目启动参数
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause -XX:MetaspaceSize=64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=system/heap/heapdump.hprof -Xms50m -Xmx50m -Xloggc:system/log/gc-oomHeap.log
特别说明:这里有几个注意事项需要留意下,我这边使用的JDK版本是(1.8.0_131)。堆大小设置建议直接设置为 50m,较为容易复现(java.lang.OutOfMemoryError: Java heap space)错误。此外也要特别注意参数的大小写问题,如果因为部分单词小写了,也是无法启动并且会输出以下异常:
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Unrecognized VM option 'PrintGcDateStamps'
Did you mean '(+/-)PrintGCDateStamps'?Process finished with exit code 1
项目启动成功后,通过访问该接口(http://localhost:8080/add)进行异常触发。
与此同时工程根目录下的(system)目录将会生成以下几个文件:
- 这里的目录需要手动进行创建
- 关于 .hprof 文件,如果目录已经有同名的文件,后续发生异常时将不会进行覆盖输出
- 对于 .log 则相反会持续不断的追加输出
1.3、分析生成文件
1、GCeasy 在线分析
通过访问GCeasy,将上述代码生成的 gc-oomHeap.log 文件上传至网站并执行分析操作。
如果程序生成GC日志文件不是很大的会,不需要多久就会生成对应的分析报告。分析报告有两种,一个是直接在该站点查看在线分析报告;另一个则是通过下载生成的 PDF分析报告。
在线分析报告:
- Ergonomics:表示jvm在自适应调优
- Allocation Failure:表示内存分配失败
离线分析报告:
根据下图图表可以明显的看出是堆空间消耗完了,我们分配的是50m。但是具体是哪里分配了那么多空间,目前还不知道从哪里查看。
2、使用 jvisualvm 分析
使用JDK自带的 jvisualvm 进行日志分析查看,可以通过找到安装目录(D:\Program Files\Java\jdk1.8.0_321\bin)或者通过 cmd 命令行唤出 GUI页面。
打开工具页面后,选择左上角 “文件”菜单的 “装入” 按钮。将程序生成的堆 Dump 文件导入工具中,具体操作如下示意图:
等待装载完成后,将会显示如下图所示的页面。能够查看到程序所运行的环境信息、版本信息以及发生了什么异常、异常所在的线程位置是哪里,都能查看得知:
点击 “ 导致 OutOfMemoryError 异常错误的线程: http-nio-8080-exec-1”,可以看到具体是哪块的代码有这个问题,以及产生原因是什么。
根据线程栈反馈的代码位置以及方法名,可以定位得知:
3、使用 MemoryAnalyzer 分析
装载完成后显示如下页面,当然会有一个弹窗询问是否直接开始查看相关疑点缺陷。可以关掉进行手动查看:
2、案例二:元空间溢出
2.1、编写模拟代码
package blnp.net.cn.jvm.controller;import blnp.net.cn.jvm.entity.People;
import blnp.net.cn.jvm.service.PeopleSevice;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;/*** <p>内存问题演示代码</p>** @author lyb 2045165565@qq.com* @createDate 2024/4/10 19:33*/
@RestController
public class MemoryTestController {@Resourceprivate PeopleSevice peopleSevice;/*** 用途:案例1:模拟线上环境OOM* @author liaoyibin* @date 19:46 2024/4/10* @desc <pre>* 演示使用启动参数为:-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause -XX:MetaspaceSize=64m* -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=system/heap/heapdump.hprof -Xms50m -Xmx50m -Xloggc:system/log/gc-oomHeap.log* </pre>** 执行该方法后,将会反馈异常如下:* <pre>* 2024-04-10 20:08:12.057 INFO 44472 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms* addblnp.net.cn.jvm.service.PeopleSevice@64f50fcc* java.lang.OutOfMemoryError: Java heap space* Dumping heap to system/heap/heapdump.hprof ...* Unable to create system/heap/heapdump.hprof: File exists* 2024-04-10 20:08:12.581 ERROR 44472 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java heap space] with root cause** java.lang.OutOfMemoryError: Java heap space* </pre>* @params []* @param**/@RequestMapping("/add")public void addObject(){System.err.println("add"+peopleSevice);ArrayList<People> people = new ArrayList<>();while (true){people.add(new People());}}/*** 用途:案例2:模拟元空间OOM溢出* @author liaoyibin* @date 10:56 2024/4/11* @desc 1、项目启动参数配置:* -XX:+PrintGCDetails -XX:MetaspaceSize=60m -XX:MaxMetaspaceSize=60m -Xss512K -XX:+HeapDumpOnOutOfMemoryError* -XX:HeapDumpPath=system/heap/heapdumpMeta.hprof -XX:SurvivorRatio=8 -XX:+TraceClassLoading -XX:+TraceClassUnloading* -XX:+PrintGCDateStamps -Xms60m -Xmx60M -Xloggc:system/log/gc-oomMeta.log** 2、执行异常:* [Loaded java.nio.channels.CancelledKeyException from D:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]* Exception in thread "http-nio-8080-exec-1" java.lang.OutOfMemoryError: Metaspace* [Loaded java.lang.Throwable$WrappedPrintStream from D:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]** @params []* @param**/@RequestMapping("/metaSpaceOom")public void metaSpaceOom(){//用于 Java 虚拟机的类加载系统的管理接口。ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();while (true){Enhancer enhancer = new Enhancer();enhancer.setSuperclass(People.class);/*** setUseCache:选择为 true 的话,使用和更新一类具有相同属性生成的类的静态缓存,* 而不会在同一个类文件还继续被动态加载并视为不同的类,这个其实跟类的 equals() 和 hashCode() 有关,* 它们是与 cglib 内部的 class cache 的 key 相关的.** <p>* 测试结果的区别:* 1、false:由于元空间内存有限,而动态代理在不断的反射创建新的对象。最终导致元空间内存溢出了* 2、true:程序无影响,持续运行中(因为动态代理反射的类的结构属性都是一致的,所以可以直接共享使用)* </p>* **/enhancer.setUseCache(false);enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {System.out.println("我是加强类,输出print之前的加强方法");return methodProxy.invokeSuper(o,objects);});People people = (People)enhancer.create();people.print();System.out.println(people.getClass());//返回自 Java 虚拟机开始执行到目前已经加载的类的总数。System.out.println("totalClass:" + classLoadingMXBean.getTotalLoadedClassCount());//返回当前加载到 Java 虚拟机中的类的数量。System.out.println("activeClass:" + classLoadingMXBean.getLoadedClassCount());//返回自 Java 虚拟机开始执行到目前已经卸载的类的总数。System.out.println("unloadedClass:" + classLoadingMXBean.getUnloadedClassCount());}}
}
2.2、访问接口触发异常
java.lang.OutOfMemoryError: Metaspace
Dumping heap to system/heap/heapdumpMeta.hprof ...
Heap dump file created [29901158 bytes in 0.092 secs]
[Loaded sun.net.ResourceManager from D:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.io.InterruptedIOException from D:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.net.SocketException from D:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.nio.channels.CancelledKeyException from D:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
Exception in thread "http-nio-8080-exec-1" java.lang.OutOfMemoryError: Metaspace
[Loaded java.lang.Throwable$WrappedPrintStream from D:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded sun.reflect.GeneratedSerializationConstructorAccessor27 from __JVM_DefineClass__]
[Loaded java.util.IdentityHashMap$KeySet from D:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
可以明显看到程序已经抛出异常,反馈元空间内存不足了。此时我们也可以通过命令行(jstat -gc {pid}(程序的进程ID) 1000(单位毫秒,即每秒监测一次) 10(监测总次数)),可以查看得知以下结果:
可以看到此时程序已经挂了,并且在断的进行 FullGC。但是回收后没有任何变化,空间并未能释放。
关键词解释:
- S0C:新生代幸存者0区大小(单位:字节)
- S1C:新时代幸存者1区大小
- S0U:新生代幸存者0区,已使用大小
- S1U:新时代幸存者1区,已使用大小
- EC:伊甸园区大小
- EU:伊甸园区已使用大小
- OC:老年代区大小
- OU:老年代区已使用大小
- MC:方法区大小
- MU:方法区已使用大小
- CCSC:压缩类空间大小(在64位系统可能存在压缩为32位系统的场景)
- CCSU:压缩类已使用大小
- YGC:发生 YGC 的次数
- YGCT:YGC 的时间
- FGC:发生 FullGC的次数
- FGCT:FullGC的时间
- GCT:总时间
除了通过命令行可以查看得知问题外,通过 jvisualvm 工具的可视化监控页面也能直观的查看到元空间溢出问题。因为我们启动参数设置的就是 60m,而程序在运行中在 60m 中没有任何降低,说明当前没有对象可以回收且由于没空间导致无法进行继续分配,进而抛出元空间溢出异常。
jvisualvm 分析Dump文件:
使用 MAT 分析日志:
载入完成后,我们直接选择“直方图”进行查看。并对直方图列表中的项设置为按 packet 分组。
随后对排名第一的包进行展开查看,发现占用最多的是 java.lang.reflect 中的 Method。对 Method 的记录进行右键引用跟踪查询。发现全是动态代理在不断的创建对象以及对应的属性和方法。
3、案例三:GC overhead limit exceeded
详见代码内的说明描述:
package blnp.net.cn.jvm.demos;import java.util.ArrayList;
import java.util.List;
import java.util.UUID;/*** <p>测试 GC overhead limit exceeded</p>** @author lyb 2045165565@qq.com* @desc* 启动参数:-XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=system/heap/dumpExceeded.hprof -XX:+PrintGCDateStamps -Xms10m -Xmx10m -Xloggc:system/log/gc-oomExceeded.log* @createDate 2024/4/11 16:31*/
public class OomGcTest {public static void main(String[] args) {/** 执行结果:* Connected to the target VM, address: '127.0.0.1:49836', transport: 'socket'* java.lang.OutOfMemoryError: GC overhead limit exceeded* Dumping heap to system/heap/dumpExceeded.hprof ...* Heap dump file created [10595605 bytes in 0.023 secs]* ************i: 63792* java.lang.OutOfMemoryError: GC overhead limit exceeded* at java.lang.String.substring(String.java:1933)* at java.util.UUID.digits(UUID.java:386)* **///test1();/** 执行结果:* Connected to the target VM, address: '127.0.0.1:52213', transport: 'socket'* java.lang.OutOfMemoryError: Java heap space* Dumping heap to system/heap/dumpExceeded.hprof ...* Unable to create system/heap/dumpExceeded.hprof: File exists* ************i: 29114* 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:448)* at java.lang.StringBuilder.append(StringBuilder.java:136)* at java.lang.StringBuilder.append(StringBuilder.java:131)* at blnp.net.cn.jvm.demos.OomGcTest.test2(OomGcTest.java:84)* at blnp.net.cn.jvm.demos.OomGcTest.main(OomGcTest.java:31)* **/test2();}/*** 用途:第一段代码,运行期间将内容放入常量池* <pre>* intern() 方法:* 1、如果字符串常量池里面已经包含了等于字符串 x 的字符串,那么就返回常量池中这个字符串的引用* 2、如果常量池中不存在,那么就会把当前字符串添加到常量池并返回这个字符串的引用* </pre>** @author liaoyibin* @date 16:43 2024/4/11* @params []* @param**/public static void test1() {int i = 0;List<String> list = new ArrayList<>();try {while (true) {list.add(UUID.randomUUID().toString().intern());i++;}} catch (Throwable e) {System.out.println("************i: " + i);e.printStackTrace();throw e;}}/*** 用途:第二段代码,不停的追加字符串 str* <pre>* 不停的追加字符串的特点是,前一个字符串对象引用将会作废(也就是下一次GC时可以回收)。* 该示例执行不会那么快抛出异常,是因为只有一个对象是在不断的变大且是无法回收的,直到内存溢出才会抛出异常,也就是为什么对比第一段* 代码为什么异常抛出的那么慢。* 两段代码执行后,抛出异常确不一样。这是因为在JDK6时新增的错误类型(GC overhead limit exceeded)限制,定义是:超过98%的时间用来* 做GC并且回收了不到 2% 的堆内存时就会抛出该异常。而我们的 test1() 方法就是这个原因。* 至于方法二为什么没有抛出这个异常,是因为我们是在不断的对字符串进行追加。而这个特点是字符串追加完后,之前的字符串对象是已经作废了并且是可以回收的状态。* 那么程序不断运行时,这个对象自然是越来越多。同理可回收的阈值肯定是不止 2% 的。而程序持续运行的期间,由于堆空间的有限而某个对象的不断变大。那肯定是会抛出* 堆空间不足的问题。总结来说就是:** 1、Java heap space:对应的 Demo 每次都能回收大部分的对象(也就是中间产生的UUID),只不过有一个对象是无法回收的,慢慢长大直到内存溢出。* 2、GC overhead limit exceeded:对应的 Demo 由于每个字符串都在被 list 引用,所以无法回收,很快就用完内存,触发不断回收的机制** 关于如何解决 GC overhead limit exceeded错误:* 1、排查程序中是否存在大量的死循环或有使用大内存的代码* 2、通过添加参数【-XX:-UseGCOverheadLimit】来禁用该检查,从而延迟程序的异常反馈。直到程序抛出:Java heap space 错误* 3、通过分析 dump 内存,确认是否有内存泄漏。如果没有则加大内存配置* </pre>** @author liaoyibin* @date 16:48 2024/4/11* @params []* @param**/public static void test2() {String str = "";Integer i = 1;try {while (true) {i++;str += UUID.randomUUID();}} catch (Throwable e) {System.out.println("************i: " + i);e.printStackTrace();throw e;}}
}
4、案例四:线程溢出
注意:切记,如果需要模拟该案例,一定要去虚拟机中去操作演示。否则在 mac/windows 系统中会直接关机重启;在Linux系统中会直接崩溃并且无法再进行任何命令操作!!!
4.1、Xss 参数使用
这里在复现该案例之前,我们先验证参数(-Xss512k)是否可以生效。新建一个java文件,如下所示:
/*** <p></p>** @author lyb 2045165565@qq.com* @createDate 2024/4/12 10:09*/
public class XssParamTest {public static void main(String[] args) {System.out.println("程序开始执行!");try {Thread.sleep(100000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("程序执行已完成!");}
}
执行编译命令(javac XssParamTest.java)生成字节码文件,这里需要留意的是因为代码里存在中文。如果在Windows系统中执行时会抛出异常:
.\XssParamTest.java:10: 错误: 编码GBK的不可映射字符System.out.println("绋嬪簭寮?濮嬫墽琛岋紒");^
1 个错误
因为字符集冲突,windows中文版,默认的字符集为:GBK,而当你的java文件当中的汉字不是字符集:GBK时,javac进行编译的时候就会报错。解决方式是在编译时指定编码参数(Linux中不需要)。
#编译
javac -encoding utf-8 .\XssParamTest.java#运行
java XssParamTest
此时运行起该程序后,我们查看下该Java进程的栈空间大小。因为在JDK5以后默认的栈空间大小是 1M,因此这里确认是否是 1M。查看命令如下:
#查看进程ID
jps#查看栈空间大小
jinfo -flag ThreadStackSize {pid}
看到这里可能会疑问,默认不应该是 1024吗,为什么结果是 0?-XX:ThreadStackSize=0不是说虚拟机栈大小为0,0表示为jvm默认值(这个虚拟机栈默认值是根据操作系统和JDK版本来的)。详见官网文档地址:
而如果是在Linux系统中执行,对应的参数如下所示:
验证修改栈空间大小,是否运行生效:
4.2、异常复现
切记切记,一定要在虚机上操作复现。否则机器会挂掉!!
import java.util.concurrent.CountDownLatch;/*** <p>线程溢出异常复现</p>** @author lyb 2045165565@qq.com* @createDate 2024/4/12 11:04*/
public class NativeOutOfMemoryErrorTest {public static void main(String[] args) {for (int i = 0; ; i++) {System.out.println("i = " + i);new Thread(new HoldThread()).start();}}
}class HoldThread extends Thread {CountDownLatch cdl = new CountDownLatch(1);@Overridepublic void run() {try {cdl.await();} catch (InterruptedException e) {}}
}
将程序上传至虚机或者虚机上新建该Java文件,并对文件进行编译输出。如下所示:
这里开始执行程序,在执行过程也查看下当前程序进程的栈空间大小是多少。程序脚本的代码就是在不断的创建线程。
此时虚机服务器已经挂掉了,无法输入任何命令。并且执行的程序已经抛出异常如下所示,需要特别留意的是目前创建的线程总数是(29989):
i = 29989
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native threadat java.lang.Thread.start0(Native Method)at java.lang.Thread.start(Thread.java:719)at NativeOutOfMemoryErrorTest.main(NativeOutOfMemoryErrorTest.java:14)
这里我先将虚机关掉重启,再次执行并获取执行进程分配的栈空间大小具体是多少。由于我这边执行太快了,很快服务器就挂掉了无法执行其他命令进行检查,因此在代码打印新增休眠操作。延迟程序的执行。
再次执行后创建的线程总数是(29968),可以看到和上一次相比差不了多少。这是因为我们给虚机分配的资源,目前最大只能创建这么多。因为默认分配的栈空间大小是 1M,此时如果我们再次执行的时候,指定栈空间大小。最终生成的线程数会更多还是更少?
4.3、问题分析
通过 -Xss 可以设置每个线程栈大小的容量,JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。正常情况下,在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。能创建的线程数的具体计算公式如下:
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
- MaxProcessMemory:指的是进程可寻址的最大空间,也就是一个进程的最大内存
- JVMMemory:JVM内存
- ReservedOsMemory:保留的操作系统内存
- ThreadStackSize:线程栈的大小
在java语言里,当你创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象同时创建一个操作系统线程,而这个系统线程的内存用的不是JVMMemory,而是系统中剩下的内存(MaxProcessMemory - JVMMemory - ReservedOsMemory)。
由公式得出结论:你给JVM内存越多,那么你能创建的线程越少,越容易发生java.lang.OutOfMemoryError: unable to create new native thread
如果程序中有bug,导致创建大量不需要的线程或者线程没有及时回收,那么必须解决这个bug,修改参数是不能解决问题的。如果程序确实需要大量的线程,现有的设置不能达到要求,那么可以通过修改MaxProcessMemory,JVMMemory,ThreadStackSize这三个因素,来增加能创建的线程数。
- MaxProcessMemory 使用64位操作系统
- JVMMemory 减少JVMMemory的分配
- ThreadStackSize 减小单个线程的栈大小
经实测,在32位windows系统下较为严格遵守;64位系统下只能保证正/负相关性,甚至说相关性也不能保证。即:在测试的过程中,64位操作系统下调整Xss的大小并没有对产生线程的总数产生影响,程序执行到极限的时候,操作系统会死机。无法看出效果
在32位win7操作系统下测试,jdk版本1.8(适配32位操作系统)会发现调整Xss的大小会对线程数量有影响,如下表所示:
由上可见,64位操作系统对于实验的结果是不明显的,但是32位操作系统对于Xss的设置对于实验结果是明显的,为什么会产生这样的结果?我们上面讲到过线程数量的计算公式:
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
其中MaxProcessMemory表示最大寻址空间,在32位系统中,CPU的“寻址范围”就受到32个二进制位的限制,也就是说,假设它要访问内存,它的能力是,只能访问4G内存。
32位二进制数最大值是11111111 11111111 11111111 11111111b,2的32次方 4294967296 = 4194304k(1k是1024) = 4096M(1M是1048576) = 4GB。也就是说32位CPU只能访问4GB的内存。再减去显卡上的显存等内存,可用内存要小于4G,所以32位操作系统可用线程数量是有限的。
64位二进制数的最大值是11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111b,2的64次方 = 17179869184 GB,大家可以看看64位操作的寻址空间大小比32位操作系统多了太多,所以这也是为什么我们总是无法测试出很好效果的原因。
但是事实上我们在64位系统中测试时,也还是发生了一样的异常,这是因为线程总数也受到系统空闲内存和操作系统的限制,检查是否该系统下有此限制:
#查看MaxProcessMemory,也就是系统最大pid值,在大型系统里可适当调大
cat /proc/sys/kernel/pid_max#查看系统当前的最大线程数阈值
cat /proc/sys/kernel/threads-max#查看当前用户最多可以运行多少进程或线程
ulimit -u#查看可以拥有虚拟内存的数量
cat /proc/sys/vm/max_map_count
max_map_count:文件包含限制一个进程可以拥有的VMA(虚拟内存区域)的数量。虚拟内存区域是一个连续的虚拟地址空间区域。
在进程的生命周期中,每当程序尝试在内存中映射文件,链接到共享内存段,或者分配堆空间的时候,这些区域将被创建。
调优这个值将限制进程可拥有VMA的数量。限制一个进程拥有VMA的总数可能导致应用程序出错,因为当进程达到了VMA上线但又只能释放少量的内存给其他的内核进程使用时,操作系统会抛出内存不足的错误。如果你的操作系统在NORMAL区域仅占用少量的内存,那么调低这个值可以帮助释放内存给内核用。
真实测试发现,当我们增大了threads-max参数时(比如扩大10倍:echo 301290 > /proc/sys/kernel/threads-max),运行相关程序,发现可创建的线程数确实扩大了。但是仍受相关因素的影响,不能保证创建的线程数也扩大对应的倍数。要想创建更多线程,会发现在执行完程序报错的log日志(自动在当前目录下生成)中有说明: