Otimização de heap e memória
Hoje testei a função automática de classificação de dados de um projeto, classificando dezenas de milhares de registros e imagens no banco de dados. Quando a operação estava próxima do final, Java.lang.OutofMemoryError, foi revelado um erro no espaço da pilha de Java. No passado, raramente encontrava esses erros de memória em programas de escrita, porque o Java tem um mecanismo de coletor de lixo, por isso não prestei muita atenção a ele. Hoje procurei algumas informações on -line e as classifiquei com base nessa base.
1. Pilha e pilha
Construído por heap com novo coletor de lixo é responsável pela reciclagem
1. Quando o programa começa a ser executado, a JVM recebe alguma memória do sistema operacional, parte do qual é a memória da heap. A memória da pilha geralmente é organizada para cima na parte inferior do endereço de armazenamento.
2. A pilha é uma área de dados de "tempo de execução", e o objeto instanciado na classe aloca espaço da pilha;
3. Alocar espaço na pilha é estabelecido através de instruções como "Novo". A pilha é o tamanho da memória alocada dinamicamente, e a vida útil não precisa ser informada ao compilador com antecedência;
4. Diferentemente do C ++, o Java gerencia automaticamente a pilha e a pilha, e o coletor de lixo pode reciclar automaticamente a memória de heap que não é mais usada;
5. A desvantagem é que, como a memória é alocada dinamicamente em tempo de execução, a velocidade de acesso à memória é mais lenta.
Stack - armazenar tipos básicos e tipos de referência, rápido
1. A estrutura de dados de primeira entrada e saída é geralmente usada para salvar parâmetros e variáveis locais no método;
2. Em Java, todas as variáveis de tipos básicos (curtos, int, longos, byte, flutuação, dupla, booleana, char) e tipos de referência são armazenados na pilha;
3. O espaço de vida dos dados na pilha geralmente está nos escopos atuais (a área fechada por {...};
4. A velocidade de acesso da pilha é mais rápida que a pilha, perdendo apenas os registros diretamente localizados na CPU;
5. Os dados na pilha podem ser compartilhados e várias referências podem apontar para o mesmo endereço;
6. A desvantagem é que o tamanho dos dados e a vida útil da pilha devem ser determinados e carecer de flexibilidade.
2. Configurações de memória
1. Verifique o status da memória da máquina virtual
MAXCONTROL LONG = RUNTime.GetRuntime (). MaxMemory (); // Obtenha a quantidade máxima de memória de que a máquina virtual pode controlar long Currentuse = RUNTime.GetRuntime (). TotalMemory (); // Obtenha a quantidade de memória atualmente usada pela máquina virtual
Por padrão, maxControl = 66650112b = 63.5625m da máquina virtual java;
Se você não fizer nada, a USUSUSUSTURA MEDIDA NA MÁQUINA = 5177344B = 4,9375M;
2. Comando para definir o tamanho da memória
-Xms <tamanho> Defina o tamanho inicial da hete java: defina o tamanho da memória do heap de inicialização da JVM; Esse valor pode ser definido o mesmo que -xmx para evitar a memória de redistribuição da JVM toda vez que a coleção de lixo for concluída.
-Xmx <tamanho> Defina o tamanho máximo do monte Java: Defina o tamanho máximo da memória da JVM;
-Xmn <tamanho>: Defina o tamanho da geração jovem, todo o tamanho da pilha = o tamanho da geração jovem + o tamanho da geração antiga + o tamanho da última geração.
-Xss <tamanho> Definir thread java Tamanho da pilha: Defina o tamanho da memória da pilha de thread JVM;
3. Operações específicas (1) Configurações de memória JVM:
Open Myeclipse (Eclipse) Janelas-Preferências-Java
Digite: -xmx128m -xms64m -xmn32m -xss16m
(2) Configurações de memória IDE:
Modifique a configuração em -vmargs em myeclipse.ini (ou eclipse.ini no diretório raiz do eclipse):
(3) configurações de memória do tomcat
Abra a pasta Bin no diretório raiz do tomcat e edite catalina.bat
Modificar para: Set Java_Opts = -xms256m -xmx512m
3. Análise de erro de OrofMemoryError em Java Heap
Quando a JVM é iniciada, a memória da pilha definida pelo parâmetro -xms é usada. À medida que o programa continua e cria mais objetos, a JVM começa a expandir a memória da heap para manter mais objetos. A JVM também usa um coletor de lixo para reciclar a memória. Quando a memória máxima de heap definida por -xmx é quase alcançada, se não houver mais memória poder ser alocada para o novo objeto, a JVM lançará java.lang.outofmemoryerror e o programa falhará. Antes de jogar fora do MemoryError, a JVM tentará liberar espaço suficiente com o coletor de lixo, mas lançará esse erro quando descobrir que ainda não há espaço suficiente. Para resolver esse problema, você precisa ser claro sobre as informações sobre os objetos do programa, como quais objetos criaram, quais objetos ocupam quanto espaço etc. Você pode usar um perfilador ou um analisador de heap para lidar com erros de OrofMemoryError. "Java.lang.outOfMemoryError: Java Heap Space" significa que o heap não tem espaço suficiente e não pode continuar a se expandir. "Java.lang.outOfMemoryError: Permgen Space" significa que a geração permanente está cheia e seu programa não pode mais carregar a classe ou alocar uma string.
4. Coleta de heap e lixo
Sabemos que os objetos são criados na memória da heap, a coleta de lixo é um processo que limpa objetos mortos do espaço da pilha e retorna essas memória à pilha. Para usar o coletor de lixo, a pilha é dividida principalmente em três áreas, a saber, a geração nova, a geração antiga ou a geração titular e o espaço da perm. A nova geração é um espaço usado para armazenar objetos recém -criados e é usado quando o objeto é criado recentemente. Se usados por um longo tempo, eles serão movidos para a geração antiga (ou geração titular) pelo coletor de lixo. O Space Perm é onde a JVM armazena meta dados, como classes, métodos, pools de cordas e detalhes em nível de classe.
5. Resumo:
1. A memória Java Heap faz parte da memória alocada à JVM pelo sistema operacional.
2. Quando criamos objetos, eles são armazenados na memória Java Heap.
3. Para facilitar a coleta de lixo, o espaço da pilha Java é dividido em três áreas, chamado nova geração, geração antiga ou geração titular e espaço de perm.
4. Você pode ajustar o tamanho do espaço da pilha Java usando as opções da linha de comando JVM -xms, -xmx e -xmn.
5. Você pode usar JConsole ou Runtime.MaxMemory (), RunTime.TotalMemory () e RunTime.freememory () para visualizar o tamanho da memória de heap em Java.
6. Você pode usar o comando "jmap" para obter o despejo de heap e usar "jhat" para analisar o despejo de heap.
7. O espaço da pilha Java é diferente do espaço da pilha. O espaço da pilha é usado para armazenar pilhas de chamadas e variáveis locais.
8. O coletor de lixo Java é usado para recuperar a memória ocupada por objetos mortos (objetos que não são mais usados) e liberá -la no espaço da pilha Java.
9. Ao encontrar java.lang.outOfMemoryError, você não precisa se preocupar. Às vezes você só precisa aumentar o espaço da pilha. Mas se isso acontecer com frequência, você deve ver se há um vazamento de memória no programa Java.
10. Use ferramentas de análise de Profiler e Heap Dump para visualizar o espaço da pilha Java e você pode ver quanta memória é alocada para cada objeto.
Explicação detalhada do armazenamento da pilha
Java Stack Storage tem as seguintes características:
1. O tamanho dos dados e o ciclo de vida na pilha devem ser determinados.
Por exemplo, o armazenamento do tipo básico: int a = 1; Essa variável contém um valor literal, a é uma referência ao tipo int, apontando para o valor literal de 3. Devido ao tamanho e vida útil desses dados literais, esses valores literais são fixados em um bloco de programa e, após o bloco do programa que sai, os valores literais desaparecem), existam na pilha para a velocidade de busca.
2. Os dados existentes na pilha podem ser compartilhados.
(1) Armazenamento de dados do tipo básico:
como:
int a = 3; int b = 3;
O compilador primeiro processos int a = 3; Primeiro, ele criará uma referência à variável A na pilha e descobrirá se existe um endereço com um valor literal de 3. Se não for encontrado, ele abrirá um endereço com o valor literal de 3 e depois apontará para o endereço 3. Então processará int b = 3; Depois de criar a variável de referência de B, como já existe um valor literal de 3 na pilha, B é diretamente apontado para o endereço de 3. Dessa maneira, A e B ambos apontam para 3 ao mesmo tempo.
Nota: Esta referência literal é diferente da dos objetos de classe. Supondo que as referências de dois objetos de classe apontem para um objeto ao mesmo tempo, se uma variável de referência de objeto altera o estado interno do objeto, a outra variável de referência do objeto reflete imediatamente essa alteração. Em vez disso, modificar seu valor por meio de uma referência literal não fará com que outro valor seja alterado de acordo. Como no exemplo acima, depois de definirmos os valores de A e B, deixe A = 4; Em seguida, B não será igual a 4, ou igual a 3. Dentro do compilador, quando a = 4 for encontrado, ele se pesquisará se existe um valor literal de 4 na pilha. Caso contrário, reabre o endereço para armazenar o valor de 4; Se já existe, aponte diretamente a este endereço. Portanto, a mudança no valor a não afetará o valor b.
(2) armazenamento de dados de embalagem:
Classes que envolvem os tipos de dados básicos correspondentes, como número inteiro, dupla, string, etc. Todos esses dados de classe existem na pilha. O Java usa a instrução new () para exibir o compilador e só cria dinamicamente conforme necessário em tempo de execução, por isso é mais flexível, mas a desvantagem é que ele leva mais tempo.
Por exemplo: tome string como exemplo.
String é um dados de embalagem especial. Isto é, ele pode ser criado na forma de string str = new string ("abc"); ou pode ser criado na forma de string str = "abc";. O primeiro é o processo de criação de classe padronizado, ou seja, em Java, tudo é um objeto, e um objeto é uma instância da classe, todos criados na forma de new (). Algumas classes em Java, como a classe DateFormat, podem retornar uma classe recém -criada através do método getInstance () da classe, que parece violar esse princípio. Na verdade, não é o caso. Esta classe usa o padrão Singleton para retornar uma instância da classe, mas essa instância é criada dentro da classe através de new (), que oculta esse detalhe de fora.
Então, por que a instância não é criada através de new () em String str = "ABC";? É violado o princípio acima? Na verdade, não há.
Sobre o trabalho interno de String str = "ABC". Java converte internamente esta declaração nas etapas a seguir:
um. Primeiro, defina uma variável de referência de objeto chamada str na classe String: string str;
b. Descubra se existe um endereço com o valor "ABC" na pilha. Caso contrário, abra um endereço com o valor literal "ABC", crie um novo objeto O da classe String e aponte o valor da string de O para este endereço e observe o objeto de referência o próximo a esse endereço na pilha. Se houver um endereço com o valor "ABC", procure o objeto O e retorne o endereço de O.
c. Ponte STR para o endereço do objeto O.
Vale a pena notar que geralmente os valores da string na classe String são armazenados diretamente. Mas em situações como String str = "ABC";, seu valor de string mantém uma referência aos dados presentes na pilha (ou seja: String str = "ABC"; ambos o armazenamento de pilha e armazenamento de heap).
Para ilustrar melhor esse problema, podemos verificá -lo através dos seguintes códigos.
String str1 = "abc"; String str2 = "abc"; System.out.println (str1 == str2); //verdadeiro
(O valor da verdade é retornado apenas se ambas as referências apontarem para o mesmo objeto. STR1 e STR2 apontando para o mesmo objeto)
O resultado mostra que a JVM criou duas referências STR1 e STR2, mas apenas um objeto foi criado e ambas as referências apontaram para esse objeto.
String str1 = "abc"; String str2 = "abc"; str1 = "bcd"; System.out.println (str1 + "," + str2); // bcd, abc System.out.println (str1 == str2); //falso
Isso significa que a mudança na atribuição resulta na alteração na referência do objeto de classe, o STR1 aponta para outro novo objeto, enquanto o STR2 ainda aponta para o objeto original. No exemplo acima, quando alteramos o valor de STR1 para "BCD", a JVM descobriu que não há endereço para armazenar o valor na pilha, então abriu esse endereço e criou um novo objeto cujo valor de string aponta para esse endereço.
De fato, a classe String foi projetada para ser uma classe imutável. Se você deseja alterar seu valor, pode, mas a JVM cria silenciosamente um novo objeto com base no novo valor em tempo de execução (não pode ser alterado com base na memória original) e, em seguida, retorna o endereço desse objeto à referência da classe original. Embora esse processo de criação seja completamente automático, leva mais tempo, afinal. Em um ambiente mais sensível aos requisitos de tempo, terá certos efeitos adversos.
String str1 = "abc"; String str2 = "abc"; str1 = "bcd"; String str3 = str1; System.out.println (STR3); // bcd string str4 = "bcd"; System.out.println (str1 == str4); //verdadeiro
A referência ao objeto STR3 aponta diretamente para o objeto apontado pelo STR1 (observe que o STR3 não cria um novo objeto). Depois que o STR1 mudou seu valor, crie uma referência de string str4 e aponte para um novo objeto criado pelo STR1 modificando o valor. Pode -se descobrir que desta vez STR4 não criou um novo objeto, realizando assim o compartilhamento de dados na pilha novamente.
String str1 = new String ("ABC"); String str2 = "abc"; System.out.println (str1 == str2); //falsoDuas referências foram criadas. Dois objetos foram criados. As duas referências apontam para dois objetos diferentes.
String str1 = "abc"; String str2 = new String ("ABC"); System.out.println (str1 == str2); //falsoDuas referências foram criadas. Dois objetos foram criados. As duas referências apontam para dois objetos diferentes.
Os dois códigos acima indicam que, desde que o objeto seja criado com o novo (), ele será criado na pilha e suas cordas forem armazenadas separadamente. Mesmo que sejam iguais aos dados na pilha, eles não serão compartilhados com os dados na pilha.
Resumir:
(1) Quando definimos uma classe usando um formato como String str = "ABC";, sempre tomamos como certo que criamos um objeto STR da classe String. Preocupe -se com a armadilha! O objeto pode não ter sido criado! A única coisa que é certa é que uma referência à classe String é criada. Quanto a essa referência aponta para um novo objeto, ele deve ser considerado com base no contexto, a menos que você use o método novo () para criar explicitamente um novo objeto. Portanto, para ser mais preciso, criamos uma variável de referência STR para um objeto da classe String, que aponta para uma classe String com um valor de "ABC". Estar ciente disso é útil na solução de problemas de bugs difíceis nos programas.
(2) usando String str = "ABC"; Pode melhorar a velocidade de execução do programa até certo ponto, porque a JVM decidirá automaticamente se é necessário criar um novo objeto com base na situação real dos dados na pilha. Para o código de string str = new String ("ABC");, novos objetos são criados na pilha, independentemente de seus valores de sequência serem iguais ou não, seja necessário criar novos objetos, aumentando assim a carga do programa.
(3) Devido à natureza imutável da classe String (porque o valor da classe Wrapper não pode ser modificado), quando a variável da string precisa ser transformada com frequência, a classe StringBuffer deve ser considerada para melhorar a eficiência do programa.