Desde o trabalho, mais e mais código foram escritos, o programa se tornou cada vez mais inchado e a eficiência se tornou cada vez menos. Isso não é absolutamente permitido para um programador como eu que persegue a perfeição. Portanto, além de otimizar constantemente a estrutura do programa, a otimização da memória e o ajuste de desempenho se tornaram meus "truques" habituais.
Para otimizar e ajustar a memória e o desempenho dos programas Java, definitivamente não é possível não entender os princípios internos das máquinas virtuais (ou especificações mais rigorosas). Aqui está um bom livro "Máquina Virtual Java (Second Edition)" (de Bill Venners, traduzida por Cao Xiaogang e Jiang Jing. De fato, este artigo é o entendimento pessoal do autor das máquinas virtuais Java depois de ler este livro). Obviamente, os benefícios da compreensão das máquinas virtuais Java não se limitam aos dois benefícios acima. De uma perspectiva técnica mais profunda, entender as especificações e a implementação das máquinas virtuais Java será mais útil para escrevermos o código Java eficiente e estável. Por exemplo, se entendermos o modelo de memória da máquina virtual Java e o mecanismo de reciclagem de memória da máquina virtual, não confiaremos muito nela, mas explicitamente "liberaremos a memória" quando necessário (o código Java não pode liberar explicitamente a memória, mas também podemos informar o coletor de lixo que o objeto precisa ser reciclado pela referência do objeto), assim como a redução do objeto; Se entendermos como a pilha Java funciona, podemos reduzir o risco de excesso de pilha, reduzindo o número de camadas recursivas e o número de loops. Para os desenvolvedores de aplicativos, eles podem não envolver diretamente o trabalho da implementação subjacente dessas máquinas virtuais Java, mas entender esse conhecimento de fundo terá mais ou menos um impacto sutil e bom nos programas que escrevemos.
Este artigo explicará brevemente o modelo de arquitetura e memória da máquina virtual Java. Se houver alguma palavra inadequada ou explicações imprecisas, certifique -se de corrigi -las. Estou muito honrado!
Arquitetura de Máquina Virtual Java
Subsistema de carregamento de classe
Existem dois carregadores de classe para máquinas virtuais Java, a saber, o carregador de classe de inicialização e o carregador definido pelo usuário.
A classe Carregando o subsistema carrega a classe na área de dados de tempo de execução através do nome totalmente qualificado da classe (nome do pacote e nome da classe, a montagem de rede também inclui URL). Para cada tipo carregado, a máquina virtual Java cria uma instância da classe java.lang.class para representar o tipo, que é colocado na área de heap na memória, e as informações do tipo carregado estão localizadas na área do método, que é a mesma que todos os outros objetos.
Antes de carregar um tipo, o subsistema de carregamento de classe deve não apenas localizar e importar o arquivo de classe binária correspondente, mas também verificar a correção da classe importada, alocar e inicializar a memória para variáveis de classe e analisar referências de símbolo como referências diretas. Essas ações estão estritamente na seguinte ordem:
1) Carregamento - Localizar e carregar dados binários do tipo;
2) Conexão - Execute verificação, preparação e análise (opcional)
3) Verifique para garantir a correção do tipo importado
4) Prepare -se para alocar a memória para variáveis de classe e inicializá -las para valores padrão
5) Analise a referência simbólica no tipo de aplicação direta
Área de método
Para cada tipo carregado pelo subsistema de carregamento de classe, a máquina virtual salva os seguintes dados na área de método:
1. Nome totalmente qualificado do tipo
2. Nome totalmente qualificado do tipo Superclass (java.lang.Object não possui superclass)
3. É o tipo de classe do tipo A ou um tipo de interface
4. Modificador de acesso ao tipo
5. Nome totalmente qualificado Lista ordenada de qualquer hiperinterface direta
Além das informações do tipo básico acima, as seguintes informações também serão salvas:
6. Tipo de piscina constante
7. Informações de campo (incluindo nome de campo, tipo de campo, modificador de campo)
8. Informações do método (incluindo nome do método, tipo de retorno, número e tipo de parâmetros, modificadores de método. Se o método não for abstrato e local, o método bytecode, pilha de operando e a tabela de tamanho e exceção da área variável local no quadro da pilha de métodos também serão salvos)
9. Todas as variáveis de classe, exceto constantes (na verdade, são variáveis estáticas da classe. Como as variáveis estáticas são compartilhadas por todas as instâncias e estão diretamente relacionadas ao tipo, são variáveis de nível de classe e são salvas na área de métodos como membros da classe)
10. Uma referência ao carregador de classe
// O retorno é o String de referência de classe Class.class.getClassLoader () que foi salvo agora; uma referência à classe Classe // retornará a string de referência.class da classe Salvada agora;
Observe que a área do método também pode ser reciclada pelo coletor de lixo.
pilha
Todas as instâncias de classe ou matrizes criadas pelos programas Java no tempo de execução são colocadas na mesma pilha, e cada máquina virtual Java também possui um espaço de heap, e todos os threads compartilham um monte (é por isso que um programa Java multiered causará problemas de sincronização no acesso ao objeto).
Como cada máquina virtual Java possui implementações diferentes da especificação da máquina virtual, podemos não saber qual a forma de cada máquina virtual Java representa instâncias de objetos na pilha, mas podemos vislumbrar as seguintes implementações possíveis:
Contador de programas
Para executar os programas Java, cada thread possui seu próprio registro de PC (contador de programas), que é criado quando o thread inicia, com um tamanho de uma palavra, e é usado para salvar o local da próxima linha de código que precisa ser executada.
Pilha java
Cada encadeamento possui uma pilha Java, que salva o estado de execução do encadeamento em unidades de quadros de pilha. Existem dois tipos de operações de máquinas virtuais na pilha Java: Pressionamento e empilhamento da pilha, os quais têm quadros. O quadro da pilha salva dados como parâmetros de entrada, variáveis locais, resultados de operação intermediária etc., que são exibidos quando o método é concluído e depois liberado.
Dê uma olhada no instantâneo da memória do quadro da pilha quando duas variáveis locais são adicionadas juntas
Pilha de métodos locais
É aqui que Java chama a biblioteca local do sistema operacional, usada para implementar o JNI (interface nativa Java, interface local Java)
Engine de execução
O núcleo da máquina virtual Java controla carregando Java Bytecode e análise; Para executar programas Java, cada thread é uma instância de um mecanismo independente de execução de máquina virtual. Desde o início ao final do ciclo de vida do encadeamento, ele está executando o bytecode ou executando métodos locais.
Interface local
Conectado à pilha de métodos locais e à biblioteca do sistema operacional.
Nota: Todos os lugares mencionados no artigo referem -se a "especificações de máquina virtual Java para plataformas Javaee e Javase".
Prática de otimização de memória da máquina virtual
Como a memória é mencionada, os vazamentos de memória devem ser mencionados. Como todos sabemos, o Java se desenvolveu a partir da base do C ++, e um grande problema com os programas C ++ é que os vazamentos de memória são difíceis de resolver. Embora a JVM de Java tenha seu próprio mecanismo de coleta de lixo para reciclar a memória, em muitos casos, os desenvolvedores do programa Java não precisam se preocupar muito, mas também há problemas de vazamento, que são um pouco menores que o C ++. Por exemplo, existe um objeto referenciado, mas inútil no programa: se o programa faz referência ao objeto, mas não o usará ou não poderá usá -lo no futuro, o espaço de memória que ele ocupa será desperdiçado.
Vamos primeiro analisar como o GC funciona: monitore o status de execução de cada objeto, incluindo o aplicativo, citação, citação, atribuição, etc. Quando o objeto não for mais citado, solte o objeto (o foco do GC Este artigo não será explicado demais). Muitos programadores Java dependem demais do GC, mas a chave para o problema é que, por que seja bom o mecanismo de coleta de lixo da JVM, a memória é sempre um recurso limitado. Portanto, mesmo que o GC conclua a maior parte da coleta de lixo para nós, ainda é necessário prestar atenção à otimização da memória durante o processo de codificação adequadamente. Isso pode reduzir efetivamente o número de GCs, melhorando a utilização da memória e maximizando a eficiência do programa.
No geral, a otimização da memória das máquinas virtuais Java deve começar a partir de dois aspectos: máquinas virtuais Java e aplicativos Java. O primeiro refere -se ao controle do tamanho da partição de memória lógica da máquina virtual através de parâmetros da máquina virtual de acordo com o design do aplicativo, para que a memória da máquina virtual complemente os requisitos de memória do programa; Este último se refere à otimização de algoritmos do programa, redução da carga do GC e melhorando a taxa de sucesso da reciclagem do GC.
Os parâmetros para otimizar a memória da máquina virtual através dos parâmetros são os seguintes:
Xms
Tamanho inicial da pilha
XMX
Java Heap Valor máximo
1mn
Tamanho da moça da geração jovem
XSS
Tamanho da pilha para cada tópico
Os acima são três parâmetros mais usados, alguns:
XX: MINNHEAPFREERATIO = 40
Porcentagem mínima de pilha livre após o GC para evitar a expansão.
Xx: maxheapfreeratio = 70
A porcentagem máxima de pilha livre após o GC para evitar diminuir.
Xx: newratio = 2
Proporção de tamanhos de geração nova/antiga. [SPARC -Client: 8; x86 -erver: 8; x86 -cient: 12.] -Cliente: 8 (1.3.1+), x86: 12]
XX: Jornal = 2,125m
Tamanho padrão da nova geração (em bytes) [5.0 e as VMs mais recentes: 64 bits são escalonadas 30% maiores; x86: 1m; x86, 5,0 ou mais: 640K]
Xx: maxnewsize =
Tamanho máximo da nova geração (em bytes). Desde 1.4, o MaxNewsize é calculado em função do newratio.
XX: SurvivorRatio = 25
Proporção do tamanho do espaço do Éden/Sobrevivente [SPARC em 1.3.1: 25; Outras plataformas Solaris em 5.0 e anterior: 32]
XX: PermSize =
Tamanho inicial da geração permanente
Xx: maxpermsize = 64m
Tamanho da geração permanente. [5.0 e mais recente: VMs de 64 bits são escalonadas 30% maiores; 1.4 AMD64: 96M; 1.3.1 -Client: 32m.]
O que é mencionado abaixo para melhorar a utilização da memória e reduzir os riscos de memória, otimizando os algoritmos do programa é inteiramente empírico e é apenas para referência. Se houver alguma inadequação, por favor me corrija, obrigado!
1. Libere a referência de objetos inúteis o mais rápido possível (xx = null;)
Veja um pedaço de código:
Lista pública <Pagedata> parse (página htmlpage) {list <Pagedata> list = null; tente {list Valuelist = Page.getByxPath (config.getContentXPath ()); if (valuelist == null || valuelist.isempty ()) {Lista de retorno; } // Crie um objeto quando necessário, salve a memória e melhore a lista de eficiência = novo ArrayList <Pagedata> (); Pagedata Pagedata = new Pagedata (); StringBuilder Value = new StringBuilder (); for (int i = 0; i <valorelist.size (); i ++) {htmlelement content = (htmlelement) valuelist.get (i); Domnodelist <Htmlelement> imgs = content.getElementsByTagName ("img"); if (imgs! = null &&! imgs.isempty ()) {for (htmlelement img: imgs) {tente {htmlimage imagem = (htmlimage) img; String path = image.getsrcattribute (); String format = path.substring (path.lastIndexof ("."), Path.length ()); String localPath = "D:/Images/" + Md5helper.md5 (caminho) .Redplace ("//", "). Substituir ("/",", ") + formato; Arquivo localfile = novo arquivo (local local); if (! localfile.exists ()) {localfile.createnewfile (); Image.SAveas (LocalFile); } image.setAttribute ("src", "arquivo: ////" + localPath); LocalFile = NULL; imagem = nulo; img = null; } catch (Exceção e) {}} // Este objeto não será usado no futuro. Limpar a referência a ele é equivalente a contar com o GC com antecedência. O objeto pode reciclar imgs = null; } String text = content.asxml (); value.append (text) .append ("<br/>"); valuelista = nulo; content = null; texto = nulo; } Pagedata.setContent (value.toString ()); Pagedata.SetcharSet (Page.getPageEncoding ()); list.add (Pagedata); // o Pagedata = nulo; é inútil porque a lista ainda mantém a referência ao objeto, e o GC não reciclará o valor = nulo; // não há lista = nulo aqui; Como a lista é o valor de retorno do método, caso contrário, o valor de retorno que você obtém do método sempre estará vazio, e esse tipo de erro não é fácil de ser descoberto ou excluído} Catch (Exceção e) {} Return List; }2. Use os tipos de dados de coleção com cuidado, como matrizes, árvores, gráficos, listas vinculadas e outras estruturas de dados. Essas estruturas de dados são mais complicadas de reciclar para GC.
3. Evite solicitar explicitamente o espaço da matriz. Quando você precisar se inscrever explicitamente, tente estimar seu valor razoável com a maior precisão possível.
4. Tente evitar a criação e inicialização de um grande número de objetos no construtor padrão da classe e evite resíduos desnecessários de recursos de memória ao chamar seu próprio construtor da classe.
5. Tente evitar o sistema forçado para reciclar a memória de lixo e aumentar o tempo final da reciclagem de lixo no sistema
6. Tente usar variáveis de valor instantâneo ao desenvolver aplicativos de chamada de método remoto, a menos que o chamador remoto precise obter o valor da variável de valor instantâneo.
7. Tente usar a tecnologia de pool de objetos em cenários apropriados para melhorar o desempenho do sistema