jvm启动参数不会消耗在-Xms声明的内存。
jvm启动参数不会消耗在-Xms声明的内存。
在运行 Java 应用程序之前,我的 Linux 系统可用的内存为 4942356kb。我的启动参数使用了“-Xmx2048m”和“-Xms2048m”,以使最大堆内存和初始堆内存为 2G。但我发现,启动应用程序后,我的 Linux 系统可用内存逐步减少,而不是在启动后完全消耗 2G 内存。有人可以告诉我原因和机制吗?
另一个问题是,我的 JVM 启动参数为“-XX:+PrintGCDetails -Xss512k -Xmx4096m -Xms4096m -Xloggc:gc_01221859.log”,在启动之前的可用内存为5G,而在 1 小时后,我发现该应用程序已经有 14,053 个线程。显然,14053 * 512k = 7195136 = 7.2G 栈内存已经被消耗,这显然比系统可用内存和总内存都要大。有人能为我解释一下吗?
当线程数为 14053 时,我使用“jcmd VM.native_memory summary”来转储 Native 内存,输出为:
Native Memory Tracking: Total: reserved=8688195KB, committed=8645103KB - Java Heap (reserved=2181632KB, committed=2181632KB) (mmap: reserved=2181632KB, committed=2181632KB) - Class (reserved=5997KB, committed=5997KB) (classes #8918) (malloc=5997KB, #7418) - Thread (reserved=5815888KB, committed=5815888KB) (thread #11139) (stack: reserved=5752332KB, committed=5752332KB) (malloc=35936KB, #44562) (arena=27620KB, #22266) - Code (reserved=51385KB, committed=8293KB) (malloc=1465KB, #2689) (mmap: reserved=49920KB, committed=6828KB) - GC (reserved=85657KB, committed=85657KB) (malloc=5889KB, #155) (mmap: reserved=79768KB, committed=79768KB) - Compiler (reserved=294KB, committed=294KB) (malloc=196KB, #400) (arena=98KB, #2) - Internal (reserved=52609KB, committed=52609KB) (malloc=52609KB, #283728) - Symbol (reserved=13406KB, committed=13406KB) (malloc=10010KB, #98269) (arena=3396KB, #1) - Memory Tracking (reserved=301363KB, committed=301363KB) (malloc=301363KB, #28396) - Pooled Free Chunks (reserved=179933KB, committed=179933KB) (malloc=179933KB) - Unknown (reserved=32KB, committed=32KB) (mmap: reserved=32KB, committed=32KB)
顶部输出为:
top - 18:47:02 up 397 days, 26 min, 1 user, load average: 3.68, 3.46, 2.58 Tasks: 164 total, 1 running, 163 sleeping, 0 stopped, 0 zombie Cpu(s): 2.5%us, 0.6%sy, 0.0%ni, 96.7%id, 0.1%wa, 0.0%hi, 0.0%si, 0.1%st Mem: 5990984k total, 5144764k used, 846220k free, 11564k buffers Swap: 0k total, 0k used, 0k free, 132748k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 11513 wuchang 20 0 12.1g 3.7g 15m S 188.8 64.9 41:46.42 java 1264 wuchang 20 0 17116 1240 900 R 1.8 0.0 0:00.02 top 1 root 20 0 19232 364 84 S 0.0 0.0 0:02.57 init
free 输出为:
total used free shared buffers cached Mem: 5990984 5786744 204240 0 12012 134092 -/+ buffers/cache: 5640640 350344 Swap: 0 0 0
我写了一个非常简单的 Java 代码,其作用只是创建线程:
public class CreateThread { private static int threadNumber = 0; public static void main(String[] args) { while (true) { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("Thread " + threadNumber++); while(true){ try { Thread.sleep(20000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }).start(); } } }
将其包装成 jar 文件在 Linux 中运行,类似于:
java -Xss512k -jar CreateThread.jar > /home/wuchang/test
在报告之前,它总共创建了 32313 个线程:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread at java.lang.Thread.start0(Native Method) at java.lang.Thread.start(Thread.java:714) at CreateThread.main(CreateThread.java:22)
另一个问题是,为什么错误是 OutOfMemory 而不是 StackOverflow?
我的 Linux 系统线程的一些限制是:
[wuchang@hz10-45-88 ~]$ cat /proc/sys/kernel/threads-max 93335 [wuchang@hz10-45-88 ~]$ ulimit -u 46667
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
更新1:每次使用不同的启动参数运行我的 Java 应用程序,包括三种情况:
case 1: -Xss512k -Xmx4096m -Xms4096m case 2: -Xss512k -Xmx2048m -Xms2048m case 3: -Xss256k -Xmx2048m -Xms2048m
当然,由于我的应用程序是一个压力测试,经过一段时间后,进程将被操作系统自动终止。我已经记录了它在被终止前最后一次 top 输出如下:
case 1: PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 18136 wuchang 20 0 15.4g 4.3g 6456 S 99.3 75.0 36:16.22 java case 2: PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 9689 wuchang 20 0 15.0g 4.2g 14m S 98.6 73.0 55:36.62 java case 3: PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 8237 wuchang 20 0 10.1g 4.2g 9284 S 100.6 74.0 60:43.95 java
每次在启动进程之前,系统中的可用内存大约为5G。
从上面的输出中,每次 VIRT 都有很大变化,但是 RES 大致保持不变。所以,我的附加问题是:
- 在我的进程被操作系统杀死之前,最大的 VIRT 取决于什么?
- 如何根据 1)启动前的操作系统可用内存和 2)JVM 启动参数来估计最大线程数?
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
更新2:
我已经运行了 @Ivan 提供的代码:
public class CreateThread { private static int threadNumber = 0; public static int doRecursiveCall(int i) throws StackOverflowError { return doRecursiveCall(++i); } public static void warmupNativeStack() { int sideEffect; try { sideEffect = doRecursiveCall(0); } catch (StackOverflowError ignored) { return; } System.out.println("Never happend " + sideEffect); } public static void main(String[] args) { while (true) { new Thread(new Runnable() { @Override public void run() { warmupNativeStack(); System.out.println("Thread " + threadNumber++); while (true) { try { Thread.sleep(20000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }).start(); } } }
结果是:
java -Xss512k -Xmx2048m -Xms2048m -jar CreateThread.jar totally 8600 threads are created.before exit,the max VIRT is 8.7G and max RSS is 4.5G java -Xss256k -Xmx2048m -Xms2048m -jar CreateThread.jar totally 16780 threads are created.before exit,the max VIRT is 8.7G and max RSS is 4.6G
实验表明,在这种情况下,最大的 VIRT 和 RSS 与 JVM 启动参数无关,它们保持不变。因此,根据 更新1 和 更新2 的实验结果,我猜测,导致我的应用程序被操作系统终止的原因可能是 RSS 内存已达到限制,而不是 线程数 或 总 VIRT,对吗?
默认情况下,在应用程序执行期间,JVM会逐步操作页面。如果要改变这种行为,请使用以下选项-XX:+AlwaysPreTouch
。
在这种情况下,JVM在JVM初始化期间提前触摸堆内存(堆内存的每个页面都是按需清零)。
还要记得JVM本机内存。
更新:
很可能你的内存已被交换。您可以使用以下方法检查交换使用情况。
更新2:
在这种情况下,你正在处理超额配置内存。内存超额配置是一种操作系统特性,允许使用的内存空间比物理机器实际拥有的多。本机堆栈足够懒惰,可以避免触碰后端物理内存。因此,您的虚拟内存是12.1g,RSS是3.7g。要进行更详细的分析,可以使用 pmap -x
。
我写了一段非常简单的Java代码,其功能仅是创建线程。
这就是为什么为线程分配的内存没有被使用。在您的线程中做一些有用的事情。例如:
public class CreateThread { private static int threadNumber = 0; public static int doRecursiveCall(int i) throws StackOverflowError { return doRecursiveCall(++i); } public static void warmupNativeStack() { int sideEffect; try { sideEffect = doRecursiveCall(0); } catch (StackOverflowError ignored) { return; } System.out.println("Never happend " + sideEffect); } public static void main(String[] args) { while (true) { new Thread(new Runnable() { @Override public void run() { warmupNativeStack(); System.out.println("Thread " + threadNumber++); while (true) { try { Thread.sleep(20000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }).start(); } } }
为什么错误是OutofMemory而不是StackOverflow?
实际上,您很幸运因为在现实生活中,一切都以OOM killer结束。在您的情况下,通过操作系统请求一个新的Java线程。操作系统尝试创建一个新的本机线程,该线程需要分配内存,但由于缺少物理内存而分配失败。在现实生活中,您的Java应用程序将被Linux内核(OOM killer)杀死。
而“Stack Overflow”是用于其他目的(请参见另一个答案)。
在我的进程被操作系统杀死之前,最大虚拟内存依赖于什么?
参见《Linux上的虚拟内存大小》。
如何根据操作系统可用内存和JVM启动参数估算最大线程数?
这取决于许多因素,JVM参数并不起任何作用。例如,您必须了解在应用程序中调用堆栈的最大深度 - 通过您的和我的示例检查线程数。