Pergunta

Eu tenho um problema com uma aplicação Java rodando sob Linux.

Quando eu iniciar o aplicativo, usando o tamanho da pilha máximo padrão (64 MB), eu vejo usando o aplicativo tops que 240 MB de memória virtual são alocados para a aplicação. Isso cria alguns problemas com algum outro software no computador, que é relativamente limitado de recursos.

A memória virtual reservada não será usado de qualquer maneira, tanto quanto eu entendo, porque uma vez que atingir o limite de montão uma OutOfMemoryError é lançada. Corri a mesma aplicação no Windows e vejo que o tamanho da memória virtual eo tamanho da pilha são semelhantes.

Existe uma maneira que eu posso configurar a memória virtual em uso por um processo de Java no Linux?

Editar 1 : O problema não é o Heap. O problema é que se eu definir uma pilha de 128 MB, por exemplo, ainda Linux aloca 210 MB de memória virtual, que não é necessário, nunca. **

Editar 2 : Usando ulimit -v permite limitar a quantidade de memória virtual. Se o conjunto de tamanho é inferior a 204 MB, em seguida, o aplicativo não será executado mesmo que ele não precisa de 204 MB, apenas 64 MB. Então, eu quero entender por que Java requer tanta memória virtual. isso pode ser mudado?

Editar 3 : Existem vários outros aplicativos em execução no sistema, que é incorporado. E o sistema tem um limite de memória virtual (a partir de comentários, importante detalhe).

Foi útil?

Solução

Esta tem sido uma queixa de longa data com Java, mas é em grande parte sem sentido, e, geralmente, com base em olhar para a informação errada. O fraseado usual é algo como "Olá Mundo em Java leva 10 megabytes! Por que ele precisa disso?" Bem, aqui está uma maneira de fazer Olá Mundo em uma JVM reivindicação de 64 bits para tirar mais de 4 gigabytes ... pelo menos por uma forma de medição.

java -Xms1024m -Xmx4096m com.example.Hello

diferentes maneiras de medir memória

No Linux, o comando top dá-lhe vários números diferentes para a memória. Aqui está o que ele diz sobre o exemplo Olá Mundo:

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 2120 kgregory  20   0 4373m  15m 7152 S    0  0.2   0:00.10 java
  • VIRT é o espaço de memória virtual: a soma de tudo no mapa de memória virtual (veja abaixo). É em grande parte sem sentido, exceto quando não é (veja abaixo).
  • RES é o tamanho do conjunto residente: o número de páginas que estão atualmente residente na RAM. Em quase todos os casos, este é o único número que você deve usar quando dizer "grande demais". Mas ainda não é um número muito bom, especialmente quando se fala de Java.
  • SHR é a quantidade de memória residente que é compartilhada com outros processos. Para um processo de Java, este é normalmente limitada a bibliotecas compartilhadas e JARfiles mapeados na memória. Neste exemplo, eu só tinha um processo de execução Java, então eu suspeito que a 7k é resultado de bibliotecas usadas pelo sistema operacional.
  • SWAP não é ativada por padrão, e não é mostrado aqui. Indica a quantidade de memória virtual que está atualmente residente em disco, se é ou não é realmente no espaço de swap . O sistema operacional é muito bom em manter páginas ativas em RAM, e as únicas curas para troca são: (1) compra de mais memória, ou (2) reduzir o número de processos, por isso é melhor para ignorar este número.

A situação para o Gerenciador de Tarefas do Windows é um pouco mais complicado. No Windows XP, há "Uso de memória" e "Memória Virtual Size" colunas, mas o documentação oficial é omissa sobre o que eles significam. Windows Vista e Windows 7 adicionar mais colunas, e eles são, na verdade, documentado . Destes, a medição "Conjunto de trabalho" é o mais útil; que corresponde aproximadamente à soma das FER e SHR em Linux.

Compreender o Mapa de Memória Virtual

A memória virtual consumida por um processo é o total de tudo o que está no mapa de memória do processo. Isso inclui dados (por exemplo, a pilha Java), mas também todas as bibliotecas compartilhadas e arquivos mapeados na memória usados ??pelo programa. No Linux, você pode usar o comando pmap para ver todas as coisas mapeadas no processo espaço (de agora em diante eu só vou para se referir ao Linux, porque é o que eu uso, eu tenho certeza que existem ferramentas equivalentes para o Windows). Aqui está um trecho do mapa de memória do programa "Olá Mundo"; todo o mapa de memória tem mais de 100 linhas, e não é incomum ter uma lista mil-line.

0000000040000000     36K r-x--  /usr/local/java/jdk-1.6-x64/bin/java
0000000040108000      8K rwx--  /usr/local/java/jdk-1.6-x64/bin/java
0000000040eba000    676K rwx--    [ anon ]
00000006fae00000  21248K rwx--    [ anon ]
00000006fc2c0000  62720K rwx--    [ anon ]
0000000700000000 699072K rwx--    [ anon ]
000000072aab0000 2097152K rwx--    [ anon ]
00000007aaab0000 349504K rwx--    [ anon ]
00000007c0000000 1048576K rwx--    [ anon ]
...
00007fa1ed00d000   1652K r-xs-  /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar
...
00007fa1ed1d3000   1024K rwx--    [ anon ]
00007fa1ed2d3000      4K -----    [ anon ]
00007fa1ed2d4000   1024K rwx--    [ anon ]
00007fa1ed3d4000      4K -----    [ anon ]
...
00007fa1f20d3000    164K r-x--  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f20fc000   1020K -----  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f21fb000     28K rwx--  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
...
00007fa1f34aa000   1576K r-x--  /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000   2044K -----  /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000     16K r-x--  /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3837000      4K rwx--  /lib/x86_64-linux-gnu/libc-2.13.so
...

Uma rápida explicação do formato: cada linha começa com o endereço de memória virtual do segmento. Isto é seguido pelo segmento de tamanho, as permissões, e a fonte do segmento. Este último item é um arquivo ou "Anon", que indica um bloco de memória alocada via mmap .

A partir do topo, temos

  • O carregador JVM (ou seja, o programa que é executado quando você digita java). Isto é muito pequena; tudo que faz é carregar as bibliotecas compartilhadas onde o código verdadeiro JVM é armazenada.
  • Um grupo de blocos Anon segurando o heap Java e dados internos. Esta é uma Sun JVM, então a pilha é broken em várias gerações, cada um dos quais é o seu próprio bloco de memória. Note que a JVM aloca espaço de memória virtual com base no valor -Xmx; isso permite que ele tem um monte contíguo. O valor -Xms é usado internamente para dizer o quanto da pilha está "em uso" quando o programa começa e para a coleta de lixo gatilho como o limite estiver próximo.
  • A memória mapeada jarfile, neste caso, o arquivo que contém as "classes JDK." Quando a memória de mapear um JAR, você pode acessar os arquivos dentro dela de forma muito eficiente (versus lê-lo desde o início de cada vez). O Sun JVM vai memória de mapear todos os JARs no classpath; se suas necessidades código do aplicativo para acessar um JAR, você também pode memória mapear-lo.
  • dados por thread para dois threads. O bloco 1 M é uma pilha de rosca; Eu não sei o que vai para o bloco de 4K. Para um aplicativo real, você vai ver dezenas se não centenas de essas entradas repetidas através do mapa de memória.
  • Uma das bibliotecas compartilhadas que contém o código JVM real. Há várias dessas medidas.
  • A biblioteca compartilhada para a biblioteca padrão C. Esta é apenas uma das muitas coisas que as cargas de JVM que não são estritamente parte do Java.

As bibliotecas compartilhadas são particularmente interessantes: cada biblioteca compartilhada tem pelo menos dois segmentos: um segmento de somente leitura que contém o código da biblioteca, e um segmento de leitura e escrita que contém dados de processo per mundial para a biblioteca (eu não saber o que o segmento com nenhuma permissão é; eu só vi isso em x64 Linux). A parte somente leitura da biblioteca pode ser compartilhado entre todos os processos que usam a biblioteca; por exemplo, libc tem 1.5M de espaço de memória virtual que pode ser compartilhado.

Quando é Virtual Tamanho da memória Importante?

O mapa de memória virtual contém um monte de coisas. Alguns dos que é somente leitura, alguns dos que é compartilhado, e alguns dos que é alocado, mas nunca tocou (por exemplo, quase todos os 4GB de pilha neste exemplo). Mas o sistema operacional é inteligente o suficiente para carregar apenas o que ele precisa, de modo que o tamanho da memória virtual é em grande parte irrelevante.

Onde tamanho da memória virtual é importante é se você estiver executando em um sistema operacional de 32 bits, onde você só pode alocar 2 GB (ou, em alguns casos, 3Gb) de espaço de endereço do processo. Nesse caso, você está lidando com um recurso escasso, e pode ter que fazer trocas, tais como reduzir o seu tamanho da pilha, de modo a memória de mapear um arquivo grande ou criar lotes de linhas.

Mas, dado que as máquinas de 64 bits são onipresentes, eu não acho que vai ser muito antes Virtual tamanho da memória é uma estatística completamente irrelevante.

Quando é residente Set Tamanho Importante?

tamanho Residente Set é que parte do espaço de memória virtual que está realmente em RAM. Se o seu RSS cresce para ser uma parte significativa da sua memória física total, pode ser hora de começar a se preocupar. Se o seu RSS cresce para ocupar toda a sua memória física, e seu sistema começa a trocar, é tempo bem passado para começar a se preocupar.

Mas RSS também é enganador, especialmente em uma máquina levemente carregado. O sistema operacional não gastar um monte de esforço para recuperar as páginas usadas por um processo. Há pouco benefício a ser obtido ao fazê-lo, eo potencial para uma falha de página caro se o processo toca a página no futuro. Como resultado, a estatística de RSS podem incluir muitas páginas que não estão em uso ativo.

Bottom Line

A menos que você está trocando, não ficar excessivamente preocupados com o que as várias estatísticas de memória estão dizendo. Com a ressalva de que um RSS crescente pode indicar algum tipo de vazamento de memória.

Com um programa Java, é muito mais importante prestar atenção ao que está acontecendo na pilha. A quantidade total de espaço consumido é importante, e há alguns passos que você pode tomar para reduzir isso. Mais importante é a quantidade de tempo que você gasta na coleta de lixo, e quais partes do montão estão ficando collected.

Acessando o disco (ou seja, um banco de dados) é caro, ea memória é barato. Se você pode trocar um para o outro, fazê-lo.

Outras dicas

Há um problema conhecido com Java e glibc> = 2,10 (inclui Ubuntu> = 10,04, RHEL> = 6).

A cura é para definir esse env. variável:

export MALLOC_ARENA_MAX=4

Se você estiver executando Tomcat, você pode adicioná-lo ao arquivo TOMCAT_HOME/bin/setenv.sh.

Para Docker, adicione a Dockerfile

ENV MALLOC_ARENA_MAX=4

Há um artigo da IBM sobre a configuração MALLOC_ARENA_MAX https://www.ibm.com/developerworks/ comunidade / blogs / kevgrig / entry / linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage? lang = en

Este post diz

memória residente tem sido conhecida a fluência de uma forma semelhante a um vazamento de memória ou fragmentação de memória.

Há também um bug JDK aberta JDK-8193521 "memória resíduos glibc com a configuração padrão "

procurar MALLOC_ARENA_MAX no Google ou SO para mais referências.

Você pode querer ajustar também outras opções malloc para otimizar a baixa fragmentação de memória alocada:

# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536

A quantidade de memória alocada para o processo de Java é praticamente on-par com o que eu poderia esperar. Eu tive problemas semelhantes em execução Java em sistemas embarcados / memória limitada. Correndo qualquer aplicação com limites arbitrários VM ou em sistemas que não possuem quantidades adequadas de troca tendem a quebrar. Parece ser a natureza de muitos aplicativos modernos que não são de design para uso em sistemas de recursos limitados.

Você tem mais algumas opções que você pode tentar limitar o seu consumo de memória da JVM. Isso pode reduzir o consumo de memória virtual:

XX: ReservedCodeCacheSize = 32m tamanho do cache de código reservados (em bytes) - máximo tamanho do cache de código. [Solaris de 64 bits, amd64, e -server x86: 48m; dentro 1.5.0_06 e anteriores, Solaris 64 bits e and64:. 1024m]

XX: MaxPermSize = 64m Tamanho da Geração Permanente. [5.0 e mais recente: 64 bit VMs são dimensionadas de 30% maior; 1,4 amd64: 96m; 1.3.1 -client:. 32m]

Além disso, você também deve definir o seu -Xmx (tamanho máximo pilha) para um valor o mais próximo possível ao o uso de memória de pico real de sua aplicação. Eu acredito que o comportamento padrão do JVM ainda é a duplo o tamanho da pilha de cada vez que se expande-lo ao máximo. Se você começar com pilha 32M e seu aplicativo atingiu o pico de 65M, então a pilha acabaria crescendo 32M -> 64M -.> 128M

Você também pode tentar isso para fazer a VM menos agressivo sobre o crescimento da pilha:

XX: MinHeapFreeRatio = 40 porcentagem mínima de heap livre após GC para expansão evitar.

Além disso, pelo que me lembro de experimentar com este, há alguns anos, o número de bibliotecas nativas carregado teve um enorme impacto sobre a pegada mínimo. Carregando java.net.Socket adicionou mais de 15M se bem me lembro (e eu provavelmente não).

O Sun JVM requer muita memória para HotSpot e ele mapeia nas bibliotecas de tempo de execução na memória compartilhada.

Se a memória é um problema considerar o uso de outra JVM adequado para a incorporação. IBM tem J9, e há o "JamVM" Open Source que utiliza as bibliotecas de classpath GNU. Também Sun tem o Squeak JVM em execução nos manchas solares por isso há alternativas.

Apenas um pensamento, mas você pode verificar a influência de uma opção ulimit -v .

Isso não é uma solução real, uma vez que iria limitar o espaço de endereço disponível para todas processo, mas que permitem verificar o comportamento do seu aplicativo com uma memória virtual limitada.

Uma forma de reduzir o sice montão de um sistema com recursos limitados pode ser para brincar com o XX: MaxHeapFreeRatio variável. Este é normalmente ajustado para 70, e é a percentagem máxima da pilha que é livre antes da GC encolhe. Defini-lo para um valor inferior, e você vai ver no exemplo do profiler jvisualvm que uma sice pilha menor é normalmente utilizado para o seu programa.

EDIT: Para definir valores pequenos para -XX: MaxHeapFreeRatio você também deve definir XX: MinHeapFreeRatio Por exemplo

java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

EDIT2: Adicionado um exemplo para uma aplicação real que começa e faz a mesma tarefa, um com parâmetros padrão e um com 10 e 25 como parâmetros. Eu não notei qualquer diferença de velocidade real, embora java, em teoria, deve usar mais tempo para aumentar a pilha no último exemplo.

parâmetros predefinidos

No final, montão máxima é 905, pilha utilizada é 378

MinHeap 10, MaxHeap 25

No final, montão máxima é 722, pilha utilizada é 378

Isso realmente tem algum inpact, como nossos aplicativo é executado em um servidor de desktop remoto, e muitos usuários podem executá-lo ao mesmo tempo.

Java da Sun 1.4 tem os seguintes argumentos para o tamanho da memória de controle:

-Xmsn Especificar o tamanho inicial, em bytes, do pool de alocação de memória. Este valor deve ser um múltiplo de 1024 maior do que 1MB. Anexar a letra k ou K para indicar kilobytes, ou M ou M para indicar megabytes. O padrão valor é 2MB. Exemplos:

           -Xms6291456
           -Xms6144k
           -Xms6m

-Xmxn Especificar o tamanho máximo, em bytes, do pool de alocação de memória. Este valor deve um múltiplo de 1024 maior do que 2MB. Anexar a letra k ou K para indicar kilobytes, ou M ou M para indicar megabytes. O padrão valor é 64 MB. Exemplos:

           -Xmx83886080
           -Xmx81920k
           -Xmx80m

http://java.sun.com /j2se/1.4.2/docs/tooldocs/windows/java.html

Java 5 e 6 têm um pouco mais. Consulte http://java.sun.com/javase/technologies/hotspot/vmoptions .jsp

Não, você não pode quantidade de memória configure necessária por VM. No entanto, nota que esta é a memória virtual, não residente, por isso só fica lá sem danos se não for realmente usado.

Alernatively, você pode tentar alguma outra JVM então Sun um, com menor consumo de memória, mas não posso aconselhar aqui.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top