Diagrama de estrutura interna da JVM
As máquinas virtuais Java são divididas principalmente em cinco áreas: área de método, pilha, pilha Java, registro de PC e pilha de métodos locais. Vamos dar uma olhada em algumas questões importantes sobre a estrutura da JVM.
1. Quais áreas são compartilhadas? Quais são privados?
A pilha Java, a pilha de métodos locais e o contador de programas são estabelecidos e destruídos à medida que o thread do usuário começa e termina.
Cada fio possui essas áreas independentes. A área do método e a pilha são compartilhados por todos os threads em todo o processo JVM.
2. O que é salvo na área do método? Será reciclado?
A área do método não é apenas informações e código de método salvos. Ao mesmo tempo, em uma sub-região chamada Pool constante de tempo de execução, várias referências simbólicas na tabela constante no arquivo de classe, bem como referências diretas traduzidas. Esta informação é acessada através de um objeto de classe na pilha como uma interface.
Embora as informações de tipo sejam armazenadas na área do método, elas também serão recicladas, mas as condições de reciclagem são relativamente rigorosas:
(1) Todas as instâncias desta classe foram recicladas
(2) O carregador de classe Carregando esta classe foi reciclado
(3) O objeto de classe desta classe não é referenciado em nenhum lugar (incluindo o acesso a Class.ForName Reflection)
3. O conteúdo do pool constante na área de método é inalterado?
O pool constante de tempo de execução na área de métodos salva os dados no pool estático constante no arquivo de classe. Além de armazenar várias referências literais e simbólicas geradas no momento da compilação, ele também contém referências diretas traduzidas. Mas isso não significa que o pool constante não mude durante o tempo de execução. Por exemplo, ao executar, você pode chamar o método estagiário da string e colocar novas constantes de sequência no pool.
pacote com.cdai.jvm; public class RunTimeConstantpool {public static void main (string [] args) {string s1 = new String ("hello"); String S2 = new String ("Hello"); System.out.println ("Antes do estagiário, S1 == S2:" + (S1 == S2)); s1 = s1.intern (); s2 = s2.intern (); System.out.println ("After Intern, S1 == S2:" + (S1 == S2)); }}
4. Todas as instâncias de objetos são alocadas na pilha?
Com a maturidade gradual da tecnologia de análise de fuga, as tecnologias de alocação na pilha e otimização de substituição escalar tornaram "todos os objetos alocados no heap" menos absoluto.
A chamada fuga significa que, quando o ponteiro de um objeto é referenciado por vários métodos ou threads, dizemos que esse ponteiro escapa.
De um modo geral, os objetos Java são alocados na pilha e apenas os ponteiros do objeto são salvos na pilha. Supondo que uma variável local não escape durante a execução do método (exposta ao exterior do método), ela será alocada diretamente na pilha e continuará sendo executada na pilha de chamadas. Após a execução do método, o espaço da pilha é reciclado e as variáveis locais também são recicladas. Isso reduz a alocação de um grande número de objetos temporários na pilha e melhora a eficiência da reciclagem de GC.
Além disso, a análise de fuga também omitirá bloqueios em variáveis locais que não escaparam e omitem os bloqueios de propriedade dessa variável.
Ao ativar o método de análise de escape, adicione os parâmetros de inicialização da JVM: -xx: +OScapeanAlysis? EscapeanAlySistest.
5. Quantas maneiras existem para acessar objetos na pilha?
(1) acesso direto ao ponteiro
A referência na pilha salva um ponteiro para um objeto na pilha e você pode localizar o objeto de uma só vez, que tem uma velocidade de acesso mais rápida.
No entanto, quando os objetos são movidos na pilha (cada um dos objetos geralmente é movido durante a coleta de lixo), o valor da variável de ponteiro na pilha também precisa ser alterado. Atualmente, o hotspot da JVM adota esse método.
(2) acesso indireto para manipular
A referência na pilha aponta para uma alça no pool da alça, e o objeto é acessado através do valor nessa alça. Portanto, a alça é como um ponteiro secundário, que requer dois posicionamentos para acessar o objeto, que é mais lento que o posicionamento do ponteiro direto, mas quando o objeto se move na pilha, não há necessidade de alterar o valor referenciado na pilha.
Como transbordar JVM
Depois de entender o papel das cinco áreas de memória das máquinas virtuais Java, vamos continuar aprendendo em que circunstâncias essas áreas transbordam.
1. Configuração de parâmetros da máquina virtual
-Xms: o tamanho da pilha inicial, o padrão é 1/64 da memória física (<1 GB); Padrão (o parâmetro MINNHEAPFREERATIO pode ser ajustado) Quando a memória de heap livre for menor que 40%, a JVM aumentará a pilha até o limite máximo de -xmx.
-Xmx: tamanho máximo da pilha, padrão (o parâmetro maxheapfreeratio pode ser ajustado) quando a memória de heap livre for maior que 70%, a JVM reduzirá a pilha até o limite mínimo de -xms.
-Xss: tamanho da pilha para cada thread. Após o JDK5.0, o tamanho da pilha de cada rosca é de 1m e, no passado, o tamanho da pilha de cada rosca é de 256k. Ele deve ser ajustado adequadamente de acordo com o tamanho da memória necessário do thread do aplicativo. Na mesma memória física, reduzir esse valor pode gerar mais threads. No entanto, o sistema operacional ainda tem um limite para o número de encadeamentos em um processo e não pode ser gerado infinitamente, com valores de experiência variando de cerca de 3000 a 5000. Geralmente, pequenas aplicações, se a pilha não for muito profunda, 128k devem ser suficientes. Para aplicações grandes, 256k é recomendado. Esta opção tem um grande impacto no desempenho e requer testes estritos.
-Xx: PermSize: Defina o valor inicial da geração permanente (Perm gen). O valor padrão é 1/64 da memória física.
-Xx: maxPermsize: Defina o valor máximo da geração persistente. 1/4 de memória física.
2. O transbordamento da área do método
Como a área do método contém as informações relevantes da classe, quando carregamos muitas classes, a área do método transbordará. Aqui, tentamos transbordar a área do método através de dois métodos: proxy dinâmico do JDK e proxy do CGLIB.
2.1 proxy dinâmico JDK
pacote com.cdai.jvm.overflow; importar java.lang.reflect.invocationHandler; importar java.lang.reflect.method; importar java.lang.reflect.proxy; public class Methodareaoverflow {interface estática oomiInterface {} classe estática oomObject implementa oomiInterface {} classe estática oomObject2 implementa oomiInterface {} public static void main (string [] args) {final oomobject = newOObject (); while (true) {oomiInterface proxy = (oomiinterface) proxy.newproxyInstance (thread.currentThread (). getContextClassLoader (), oomObject.class.getInterfaces (), novo Método, Método, object () @Override Object Invoke (Object Proxy, Método, Método, Object (Object), @Overrride Object (Object), Métodos, Métodos, Métodos, OBJET (OBJET) (OOMOBJATION) (OOMOBSTION) (proxy), o object @Override Object. System.out.println ("Interceptor1 está funcionando"); System.out.println (proxy.getclass ()); System.out.println ("proxy1:" + proxy); Oomiinterface proxy2 = (oomiinterface) proxy.newProxyInstance (thread.currentThread (). GetContextClassLoader (), oomobject.class.getInterfaces (), new InvocationHler () {@Override Object Invoke (object »Método, Método, [] OBTON (] argts () (@Override Object Invoke (object« está funcionando "); retorno método.invoke (objeto, args);}}); System.out.println (proxy2.getClass ()); System.out.println ("proxy2:" + proxy2); }}} Embora continuemos chamando o método proxy.newInstance () para criar a classe proxy, a JVM não possui um estouro de memória.
Cada chamada gera uma instância diferente da classe proxy, mas o objeto de classe da classe proxy não mudou. É proxy
A classe tem um cache para o objeto de classe da classe proxy? Os motivos específicos serão analisados em detalhes no subsequente "proxy dinâmico e cglib JDK".
2.2 Agente do CGLIB
O CGLIB também cache o objeto de classe da classe Proxy, mas podemos configurá -lo para não cache o objeto de classe.
Dessa forma, o objetivo de transbordar a área do método pode ser alcançado criando repetidamente classes de proxy.
pacote com.cdai.jvm.overflow; importar java.lang.reflect.method; importação net.sf.cglib.proxy.enhancer; importação net.sf.cglib.proxy.methodintercept; importação net.sf.cglib.proxy.methodProxy; public class Metodareaoverflow2 {classe estática oomObject {} public static void main (string [] args) {while (true) {intensificador intensificador = new aprimor (); intensificador.SetSUPERClass (OOMOBJET.CLASS); intensificador.setUseCache (false); intensancer.setCallback (new MethodIntercept () {@Override public Object Intercept (objeto obj, método do método, objeto [] args, métodproxy proxy) lança throwable {return métod.invoke (obj, args);}}); OOMObject proxy = (oomObject) aprimor.create (); System.out.println (proxy.getclass ()); }}}
3. Overflow Heap
O excesso de heap é relativamente simples. Você pode transbordar a pilha criando um grande objeto de matriz.
pacote com.cdai.jvm.overflow; classe pública heopoverflow {private static final int mb = 1024 * 1024; @Suppresswarnings ("não utilizados") public static void main (string [] args) {byte [] bigmemory = novo byte [1024 * mb]; }}
4. FLUSH FLACK
O transbordamento da pilha também é comum. Às vezes, quando a chamada recursiva que escrevemos não possui a condição de terminação correta, o método continuará a recorrer, a profundidade da pilha continuará aumentando e, eventualmente, o excesso de pilha ocorrerá.
pacote com.cdai.jvm.overflow; classe pública Stackoverflow {private static int stackDepth = 1; public static void StackOverflow () {StackDepth ++; StackOverflow (); } public static void main (string [] args) {try {Stackoverflow (); } catch (Exceção e) {System.err.println ("Profundidade da pilha:" + StackDepth); E.PrintStackTrace (); }}}