Estou com um problema com um aplicativo Java em execução no Linux.
Quando inicio o aplicativo, usando o tamanho máximo padrão de heap (64 MB), vejo que, no aplicativo superior, 240 MB de memória virtual são alocados ao aplicativo. Isso cria alguns problemas com outro software no computador, que é relativamente limitado em recursos.
A memória virtual reservada não será usada de qualquer maneira, pelo que entendi, porque quando atingimos o limite de heap, OutOfMemoryError
é lançada. Executei o mesmo aplicativo no Windows e vejo que o tamanho da memória virtual e o tamanho da pilha são semelhantes.
Existe alguma maneira de configurar a memória virtual em uso em um processo Java no Linux?
Edit 1 : O problema não é o Heap. O problema é que, se eu definir um monte de 128 MB, por exemplo, o Linux ainda alocará 210 MB de memória virtual, o que não é necessário, nunca. **
Edit 2 : Using ulimit -v
permite limitar a quantidade de memória virtual. Se o tamanho definido estiver abaixo de 204 MB, o aplicativo não será executado, mesmo que não precise de 204 MB, apenas 64 MB. Então, eu quero entender por que o Java requer tanta memória virtual. Isso pode ser alterado?
Editar 3 : Existem vários outros aplicativos em execução no sistema, que é incorporado. E o sistema tem um limite de memória virtual (de comentários, detalhes importantes).
fonte
Respostas:
Essa é uma reclamação de longa data com Java, mas em grande parte sem sentido, e geralmente baseada na observação de informações erradas. O fraseado usual é algo como "Olá, mundo em Java, leva 10 megabytes! Por que precisa disso?" Bem, aqui está uma maneira de fazer o Hello World em uma JVM de 64 bits reivindicar ocupar mais de 4 gigabytes ... pelo menos por uma forma de medição.
Maneiras diferentes de medir a memória
No Linux, o comando top fornece vários números diferentes de memória. Aqui está o que diz sobre o exemplo Hello World:
A situação do Windows Task Manager é um pouco mais complicada. No Windows XP, existem colunas "Uso da memória" e "Tamanho da memória virtual", mas a documentação oficial é silenciosa sobre o que eles significam. O Windows Vista e o Windows 7 adicionam mais colunas e são realmente documentados . Destes, a medição do "Conjunto de trabalho" é a mais útil; corresponde aproximadamente à soma de RES e SHR no Linux.
Compreendendo 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, o heap 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 espaço do processo (daqui em diante só vou me referir ao Linux, porque é o que eu uso; tenho certeza de que existem ferramentas equivalentes para Janelas). Aqui está um trecho do mapa de memória do programa "Hello World"; o mapa inteiro da memória tem mais de 100 linhas e não é incomum ter uma lista de mil linhas.
Uma explicação rápida do formato: cada linha começa com o endereço de memória virtual do segmento. Isso é seguido pelo tamanho do segmento, pelas permissões e pela origem do segmento. Este último item é um arquivo ou "anon", que indica um bloco de memória alocado via mmap .
Começando do topo, temos
java
). Isto é muito pequeno; tudo o que faz é carregar nas bibliotecas compartilhadas onde o código JVM real está armazenado.-Xmx
valor; isso permite que ele tenha uma pilha contígua. O-Xms
valor é usado internamente para dizer quanto do heap está "em uso" quando o programa é iniciado e para acionar a coleta de lixo à medida que esse limite é atingido.StackOverFlowError
. Para um aplicativo real, você verá dezenas, senão centenas, dessas entradas repetidas no mapa de memória.As bibliotecas compartilhadas são particularmente interessantes: cada biblioteca compartilhada possui pelo menos dois segmentos: um segmento somente leitura que contém o código da biblioteca e um segmento de leitura e gravação que contém dados globais por processo da biblioteca (não sei o que segmento sem permissões é; eu só vi no Linux x64). A parte somente leitura da biblioteca pode ser compartilhada entre todos os processos que usam a biblioteca; por exemplo,
libc
possui 1,5 milhão de espaço de memória virtual que pode ser compartilhado.Quando o tamanho da memória virtual é importante?
O mapa de memória virtual contém muitas coisas. Parte é somente leitura, parte é compartilhada e parte é alocada, mas nunca tocada (por exemplo, quase todo o 4Gb de heap neste exemplo). Mas o sistema operacional é inteligente o suficiente para carregar apenas o que precisa, portanto, o tamanho da memória virtual é em grande parte irrelevante.
Onde o tamanho da memória virtual é importante é se você estiver executando em um sistema operacional de 32 bits, em que só poderá 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, como reduzir o tamanho do heap para mapear na memória um arquivo grande ou criar muitos threads.
Mas, como as máquinas de 64 bits são onipresentes, não acho que demore muito para que o Tamanho da memória virtual seja uma estatística completamente irrelevante.
Quando o tamanho do conjunto residente é importante?
O tamanho do conjunto residente é a parte do espaço de memória virtual que está realmente na RAM. Se o seu RSS crescer como uma parte significativa da sua memória física total, talvez seja hora de começar a se preocupar. Se o seu RSS cresce para ocupar toda a sua memória física e o sistema começa a trocar, já é hora de começar a se preocupar.
Mas o RSS também é enganoso, especialmente em uma máquina com pouco carregamento. O sistema operacional não gasta muito esforço para recuperar as páginas usadas por um processo. Há pouco benefício a ser obtido com isso e o potencial de uma falha de página cara se o processo tocar a página no futuro. Como resultado, a estatística RSS pode incluir muitas páginas que não estão em uso ativo.
Bottom Line
A menos que você esteja trocando, não se preocupe demais com o que as várias estatísticas de memória estão dizendo. Com a ressalva de que um RSS sempre 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 existem algumas etapas que você pode executar para reduzir isso. Mais importante é a quantidade de tempo que você gasta na coleta de lixo e quais partes da pilha estão sendo coletadas.
Acessar o disco (ou seja, um banco de dados) é caro e a memória é barata. Se você pode trocar um pelo outro, faça-o.
fonte
Há um problema conhecido com Java e glibc> = 2.10 (inclui Ubuntu> = 10.04, RHEL> = 6).
A cura é definir esse ambiente. variável:
Se você estiver executando o Tomcat, poderá adicioná-lo ao
TOMCAT_HOME/bin/setenv.sh
arquivo.Para o Docker, adicione-o ao Dockerfile
Existe um artigo da IBM sobre a configuração de MALLOC_ARENA_MAX https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=pt
Esta postagem do blog diz
Há também um bug JDK aberto JDK -8193521 "glibc desperdiça memória com configuração padrão"
pesquise MALLOC_ARENA_MAX no Google ou SO para obter mais referências.
Convém também ajustar outras opções malloc para otimizar a baixa fragmentação da memória alocada:
fonte
MALLOC_ARENA_MAX
pode diminuir o crescimento da memória, mas não resolver o problema completamente.A quantidade de memória alocada para o processo Java está praticamente igual à que eu esperaria. Eu tive problemas semelhantes ao executar Java em sistemas embarcados / com memória limitada. A execução de qualquer aplicativo com limites arbitrários de 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 projetados para uso em sistemas com recursos limitados.
Você tem mais algumas opções e pode limitar a área de cobertura da memória da JVM. Isso pode reduzir a área de cobertura da memória virtual:
Além disso, você também deve definir seu -Xmx (tamanho máximo de heap) para um valor o mais próximo possível do pico de uso real de memória do seu aplicativo. Acredito que o comportamento padrão da JVM ainda é dobrar o tamanho da pilha cada vez que a expande até o máximo. Se você começar com um heap de 32 milhões e seu aplicativo atingir 65 milhões, o heap acabará aumentando 32 milhões -> 64 milhões -> 128 milhões.
Você também pode tentar fazer isso para tornar a VM menos agressiva sobre o crescimento do heap:
Além disso, pelo que me lembro de experimentar isso há alguns anos atrás, o número de bibliotecas nativas carregadas teve um enorme impacto no espaço mínimo. O carregamento do java.net.Socket adicionou mais de 15M se bem me lembro (e provavelmente não).
fonte
A Sun JVM requer muita memória para o HotSpot e é mapeada nas bibliotecas de tempo de execução na memória compartilhada.
Se a memória é um problema, considere usar outra JVM adequada para incorporação. A IBM possui j9 e existe o "jamvm" de código aberto que usa as bibliotecas de caminhos de classe GNU. A Sun também possui a Squeak JVM em execução no SunSPOTS, portanto existem alternativas.
fonte
Apenas um pensamento, mas você pode verificar a influência de uma
ulimit -v
opção .Essa não é uma solução real, pois limitaria o espaço de endereço disponível para todo o processo, mas permitiria verificar o comportamento do seu aplicativo com uma memória virtual limitada.
fonte
Uma maneira de reduzir o valor de heap de um sistema com recursos limitados pode ser brincar com a variável -XX: MaxHeapFreeRatio. Geralmente, isso é definido como 70 e é a porcentagem máxima do heap livre antes do encolhimento do GC. Configurando-o para um valor mais baixo, e você verá, por exemplo, no profiler jvisualvm que um heap sice menor geralmente é usado para o seu programa.
EDIT: Para definir valores pequenos para -XX: MaxHeapFreeRatio, você também deve definir -XX: MinHeapFreeRatio Por exemplo
EDIT2: Adicionado um exemplo para um aplicativo real que inicia e executa a mesma tarefa, uma com parâmetros padrão e outra com 10 e 25 como parâmetros. Não notei nenhuma diferença real de velocidade, embora, em teoria, o java deva usar mais tempo para aumentar a pilha no último exemplo.
No final, o heap máximo é 905, o heap usado é 378
No final, o heap máximo é 722, o heap usado é 378
Na verdade, isso tem algum impacto, já que nosso aplicativo é executado em um servidor de área de trabalho remota e muitos usuários podem executá-lo de uma só vez.
fonte
O java 1.4 da Sun possui os seguintes argumentos para controlar o tamanho da memória:
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
fonte
Não, você não pode configurar a quantidade de memória necessária à VM. No entanto, observe que se trata de memória virtual, não residente, portanto, ela permanece lá sem danos se não for realmente usada.
Por outro lado, você pode tentar outra JVM e depois a Sun, com menor espaço de memória, mas não posso aconselhar aqui.
fonte