当前位置: 首页 > 知识库问答 >
问题:

jvm启动参数不会消耗-xms中声明的内存

端木震博
2023-03-14

在我运行java应用程序之前,我的linux空闲内存为4942356KB,我的启动参数使用-xmx2048m和-xms2048m使最大堆内存和初始堆内存为2G,但我发现在我启动应用程序之后,我的linux系统空闲内存在一步一步地减少,而不是启动后总共消耗了2G内存,谁能告诉我原因和机制?

还有一个问题,我的jvm启动参数是-xx:+printgcdetails-xss512k-xmx4096m-xms4096m-xloggc:gc_01221859.log,启动前的空闲内存是5g,1个小时后,我发现这个应用程序已经有14053个线程,显然消耗了14053*512k=7195136=7.2G堆栈内存,明显大于系统空闲内存和总内存,有人能给我解释一下吗?

当线程数为14053时,我使用jcmd vm.native_memory summary 转储本机内存,它输出:

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   

自由输出为:

             total       used       free     shared    buffers     cached
Mem:       5990984    5786744     204240          0      12012     134092
-/+ buffers/cache:    5640640     350344
Swap:            0          0          0
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?

[wuchang@hz10-45-88 ~]$ cat /proc/sys/kernel/threads-max 
93335
[wuchang@hz10-45-88 ~]$ ulimit -u
46667
case 1: -Xss512k -Xmx4096m -Xms4096m
case 2: -Xss512k -Xmx2048m -Xms2048m
case 3: -Xss256k -Xmx2048m -Xms2048m
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保持大致相同。因此,我的补充问题是:

>

  • 在我的进程被OS杀死之前,最大VIRT取决于什么?

    我如何根据1)启动前的OS空闲内存和2)JVM启动参数来估计最大线程?

    我已经运行了@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的实验结果,我猜测,我的app被操作系统杀死的原因可能是RSS内存达到了极限,而不是线程数或VIRT总量,对吗?

  • 共有1个答案

    邓翼
    2023-03-14

    默认情况下,JVM在应用程序执行过程中以增量方式接触页面。要更改此行为,请使用以下选项-xx:+alwayspretouch。在本例中,JVM在JVM初始化期间预置堆(堆的每个页面都是按需为零的)。

    也不要忘记JVM本机内存

    很可能你的记忆被交换了。您可以使用以下说明检查交换使用情况。

    在这种情况下,你是在处理过度promise。内存过度提交是操作系统的一个特性,它允许使用比物理机器实际拥有的更多的内存空间。本机堆栈足够懒散,以避免触及备份的物理内存。因此,您有12.1G的虚拟内存和3.7G的RSS。要进一步进行更详细的分析,可以使用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();
          }
        }
    }
    

    事实上,你很幸运,因为在现实生活中,一切都以OOM杀手结束。在您的例子中,通过OS请求一个新的Java线程。操作系统尝试创建一个新的本机线程,这需要为该线程分配内存,但由于缺少物理内存,分配失败。在现实生活中,您的java应用程序将被Linux内核杀死(OOM杀手)。堆栈溢出用于其他目的(参见另一个答案)

    在我的进程被OS杀死之前,最大VIRT取决于什么?

    请参阅Linux上的虚拟内存大小

    它取决于许多因素,JVM参数不起任何作用。例如,您必须知道应用程序中调用堆栈的最大深度--用您的示例和我的示例检查线程计数。

     类似资料:
    • 问题内容: 请说明JVM 中Xms和Xmx参数的使用。它们的默认值是多少? 问题答案: 该标志指定Java虚拟机(JVM)的最大内存分配池,而Xms指定初始内存分配池。 这意味着你的JVM将以一定数量的内存启动,并且将能够使用最大数量的内存。例如,启动如下所示的JVM将以256 MB的内存启动它,并将允许该进程使用最多2048 MB的内存: 也可以以不同的大小(例如千字节,兆字节等)指定内存标志。

    • 我在spring boot中创建了一些服务,我有11个fat jars,我将它们部署在docker容器中,我怀疑每个jar在没有任何使用的情况下消耗了1到1.5 GB的RAM,我通过运行以下命令来检查RAM: 起初我以为是java容器,我试图改成一个使用alpine的容器,但没有任何变化,所以我认为唯一的问题是我的罐子。有没有办法更改罐子正在使用的 RAM?或者这种行为是正常的,因为每个罐子都有一

    • 我编写的java应用程序遇到了一个问题,导致硬件性能问题。问题(我很确定)是,我运行应用程序的一些机器只有1GB的内存。当我启动java应用程序时,我将堆大小设置为-xms512m-xmx1024m。 我的第一个问题是,我的假设是否正确,因为我将机器的所有内存分配给java堆,这显然会导致性能问题?

    • 我正在寻找一种策略来调整我正在运行的应用程序的JVM参数。 该应用程序本身是一个网络应用程序,大多数时候不做太多事情,也不需要太多内存。假设300MB。时不时地,外部的一些东西会触发大量的繁重处理,偶尔需要更多内存。假设1400 MB。 我在JDK 15上运行,没有任何其他JVM参数在kubernetes集群中的pod上。 我想: 确保我的应用程序持续运行 确保我的应用程序从底层系统占用的内存不会

    • 我有以下代码: 您可以看到每个操作分配5M。当我设置时,它无一例外地成功运行,而当时,它会引发异常。有人能解释一下为什么吗?我在Windows 7、64bit、Eclipse 4.3下。以下代码是相同的结果: