Prefácio
Registre um processo e ideias de solução de problemas de vazamento de memória fora de heap de JVM online, incluindo algumas "análises de princípio de alocação de memória de JVM" e "métodos de solução de problemas de JVM comuns e compartilhamento de ferramentas" , espero ajudar a todos.
Em todo o processo de investigação, também dei muitos desvios, mas no artigo ainda escreverei os pensamentos e ideias completos, como lição para as gerações futuras, o artigo também resume o problema de vazamento de memória rapidamente Vários princípios de investigação.
"O conteúdo principal deste artigo:"
Descrição da falha e processo de solução de problemas
Análise de causas e soluções de falha
JVM heap de memória e princípio de alocação de memória off-heap
Introdução e uso de instruções e ferramentas de solução de problemas de vazamento de memória de processo comumente usadas
Descrição de falha
Durante a pausa para o almoço de 12 de agosto, nosso serviço comercial recebeu um alarme de que a memória física (16G) ocupada pelo processo de serviço ultrapassou o limite de 80% e ainda estava aumentando.
O sistema de monitoramento chama o gráfico para visualizar:
Por exemplo, há um vazamento de memória no processo Java e nosso limite de memória heap é 4G, esse tipo de memória maior que 4G está quase cheia de memória deve ser um vazamento de memória fora do heap JVM.
Confirme a configuração de inicialização do processo de serviço naquele momento:
-Xms4g -Xmx4g -Xmn2g -Xss1024K -XX:PermSize=256m -XX:MaxPermSize=512m -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=80
Embora nenhum novo código tenha sido lançado naquele dia , "Estamos usando a fila de mensagens para enviar o script de reparo de dados históricos naquela manhã. Esta tarefa chamará um grande número de uma de nossas interfaces de serviço" , portanto, inicialmente suspeita-se que esteja relacionado a esta interface.
A figura a seguir mostra a mudança no tráfego da interface de chamada no dia:
Pode-se observar que o número de chamadas no momento do incidente aumentou muito (mais de 5.000 vezes por minuto) em comparação com a situação normal (mais de 200 vezes por minuto).
"Paramos temporariamente o script de enviar mensagens. O número de chamadas para esta interface caiu para mais de 200 vezes por minuto, e a memória do contêiner não aumentou mais em uma inclinação extremamente alta. Tudo parecia estar de volta ao normal."
A seguir, verifique se esta interface tem um vazamento de memória.
Processo de investigação
Em primeiro lugar, vamos revisar a alocação de memória do processo Java para facilitar nossa explicação das idéias de solução de problemas abaixo.
"Pegue a versão JDK1.8 que usamos online como exemplo . " Existem muitos resumos sobre a alocação de memória JVM na Internet, então não farei uma segunda criação.
A área de memória JVM é dividida em dois blocos: área de heap e área de não heap.
Área de pilha: É a nova geração e a velhice como a conhecemos.
Área não heap: conforme mostrado na figura, a área não heap possui uma área de metadados e memória direta.
"Uma observação adicional aqui é que a geração permanente (que é nativa do JDK8) armazena as classes usadas pela JVM durante o tempo de execução e os objetos da geração permanente são coletados como lixo durante o GC completo."
Depois de revisar a alocação de memória da JVM, vamos voltar à falha.
Análise de memória heap
Embora seja basicamente confirmado no início que não tem nada a ver com a memória heap, porque o uso de memória perdida excede o limite de memória heap de 4G, vamos olhar para a memória heap em busca de pistas.
Observamos a curva de ocupação da memória da nova geração e da geração anterior e as estatísticas do número de tempos de recuperação. Não houve nenhum grande problema, como de costume. Em seguida, despejamos um log da memória heap da JVM no contêiner no local do acidente.
Despejo de memória heap
Comando de despejo de instantâneo de memória de heap:
jmap -dump:live,format=b,file=xxxx.hprof pid
❝Voiceover: Você também pode usar jmap -histo: live pid para visualizar diretamente os objetos ativos na memória heap.
Após exportar, baixe o arquivo Dump de volta para o local, e então use o MAT (Memory Analyzer) do Eclipse ou o JVisualVM que acompanha o JDK para abrir o arquivo de log.
Use o MAT para abrir o arquivo conforme mostrado:
"Você pode ver que há alguns objetos grandes relacionados a nio na memória heap, como o nioChannel que está recebendo mensagens da fila de mensagens e nio.HeapByteBuffer, mas o número não é grande e não pode ser usado como base para julgamento. Vamos observá-lo primeiro."
Em seguida, comecei a navegar pelo código da interface. A lógica principal dentro da interface é chamar o cliente WCS do grupo e escrever os dados na tabela do banco de dados para o WCS depois de consultar a tabela. Não há outra lógica adicional.
Depois de descobrir que não há uma lógica especial, comecei a duvidar se há um vazamento de memória no pacote do cliente WCS. O motivo para essa suspeita é que a camada inferior do cliente WCS é encapsulada pelo cliente SCF. Como uma estrutura RPC, o protocolo de transmissão de comunicação subjacente pode ser aplicável Memória direta.
"Meu código gerou um bug no cliente WCS, fazendo com que ele se aplicasse continuamente a chamadas diretas de memória e, eventualmente, consumisse a memória."
Entrei em contato com o oficial de serviço da WCS e descrevi o problema que encontramos com eles. Eles nos responderam e farão um teste de pressão da operação de gravação localmente para ver se nosso problema pode ser reproduzido.
Uma vez que leva tempo para esperar por seus comentários, estamos prontos para descobrir os motivos por nós mesmos.
"Concentrei minhas suspeitas na memória direta. Suspeitei que a quantidade de chamadas de interface era muito grande, e o uso indevido de nio pelo cliente levou ao uso de ByteBuffer para solicitar muita memória direta."
❝ "Voiceover: O resultado final provou que essa ideia pré-concebida levou a um desvio no processo de investigação. No processo de investigação do problema, é possível estreitar o escopo da investigação com suposições razoáveis, mas é melhor primeiro considerar todas as possibilidades Deixe claro que, quando você se vir profundamente em uma certa possibilidade, sem sucesso, deve olhar para trás e examinar outras possibilidades no tempo. ”
Reprodução de ambiente sandbox
Para restaurar o cenário de falha naquele momento, solicitei uma máquina de teste de estresse no ambiente sandbox para garantir a consistência com o ambiente online.
"Primeiro, vamos simular a situação de estouro de memória (um grande número de chamadas para a interface):"
Deixamos o script continuar a enviar dados, chamar nossa interface e continuar a observar o uso de memória.
Quando a chamada é iniciada, a memória continua a crescer e não parece estar restrita (o GC Completo não é acionado devido a restrições).
"A seguir, vamos simular o volume normal da chamada (interface de chamada normal):"
Cortamos o volume normal de chamadas da interface (menor e uma chamada em lote a cada 10 minutos) para a máquina de teste de estresse e obtivemos as tendências de memória de geração anterior e memória física como a seguinte figura:
"A questão é: por que a memória continua subindo e devorando a memória?"
Na época, foi especulado que, como o processo JVM não limitava o tamanho da memória direta (-XX: MaxDirectMemorySize), a memória fora do heap continuava a aumentar e não acionaria a operação FullGC.
"A imagem acima pode tirar duas conclusões:"
Quando a quantidade de chamadas de interface para vazamentos de memória for grande, se outras condições, como a geração antiga no heap, não atenderem à condição FullGC, não será FullGC e a memória direta aumentará totalmente.
No caso de um volume de chamadas normalmente baixo, os vazamentos de memória são mais lentos, FullGC sempre virá e a parte vazada será recuperada.Este também é o motivo pelo qual não há problemas em horários normais e está funcionando normalmente por muito tempo.
"Como mencionado acima, não há limite de memória direto nos parâmetros de inicialização do nosso processo, então adicionamos a configuração -XX: MaxDirectMemorySize e testamos novamente no ambiente sandbox."
Acontece que a memória física ocupada pelo processo continuará aumentando, excedendo o limite que definimos, e a configuração de "looks" parece não funcionar.
Isso me surpreendeu. Existe um problema com o limite de memória da JVM?
"Quando chego aqui, posso ver que meu pensamento está obcecado por vazamentos diretos de memória durante o processo de investigação, e isso se foi para sempre."
❝ "Voiceover: Devemos confiar no domínio da memória do JVM. Se descobrirmos que os parâmetros são inválidos, devemos descobrir o motivo por nós mesmos e ver se usamos os parâmetros incorretos."
Análise de memória direta
Para investigar melhor o que está na memória direta, comecei a trabalhar na memória direta. Visto que a memória direta não é como a memória heap, é fácil ver todos os objetos ocupados. Precisamos de alguns comandos para solucionar problemas de memória direta. Usei vários métodos para ver quais são os problemas da memória direta.
Exibir informações de memória de processo pmap
mapa de memória de relatório pmap de um processo (ver informações de mapa de memória de processo)
O comando pmap é usado para relatar a relação de mapeamento de memória do processo e é uma boa ferramenta para depuração, operação e manutenção do Linux.
pmap -x pid 如果需要排序 | sort -n -k3**
Após a execução, obtive a seguinte saída, e a saída é a seguinte:
..
00007fa2d4000000 8660 8660 8660 rw--- [ anon ]
00007fa65f12a000 8664 8664 8664 rw--- [ anon ]
00007fa610000000 9840 9832 9832 rw--- [ anon ]
00007fa5f75ff000 10244 10244 10244 rw--- [ anon ]
00007fa6005fe000 59400 10276 10276 rw--- [ anon ]
00007fa3f8000000 10468 10468 10468 rw--- [ anon ]
00007fa60c000000 10480 10480 10480 rw--- [ anon ]
00007fa614000000 10724 10696 10696 rw--- [ anon ]
00007fa6e1c59000 13048 11228 0 r-x-- libjvm.so
00007fa604000000 12140 12016 12016 rw--- [ anon ]
00007fa654000000 13316 13096 13096 rw--- [ anon ]
00007fa618000000 16888 16748 16748 rw--- [ anon ]
00007fa624000000 37504 18756 18756 rw--- [ anon ]
00007fa62c000000 53220 22368 22368 rw--- [ anon ]
00007fa630000000 25128 23648 23648 rw--- [ anon ]
00007fa63c000000 28044 24300 24300 rw--- [ anon ]
00007fa61c000000 42376 27348 27348 rw--- [ anon ]
00007fa628000000 29692 27388 27388 rw--- [ anon ]
00007fa640000000 28016 28016 28016 rw--- [ anon ]
00007fa620000000 28228 28216 28216 rw--- [ anon ]
00007fa634000000 36096 30024 30024 rw--- [ anon ]
00007fa638000000 65516 40128 40128 rw--- [ anon ]
00007fa478000000 46280 46240 46240 rw--- [ anon ]
0000000000f7e000 47980 47856 47856 rw--- [ anon ]
00007fa67ccf0000 52288 51264 51264 rw--- [ anon ]
00007fa6dc000000 65512 63264 63264 rw--- [ anon ]
00007fa6cd000000 71296 68916 68916 rwx-- [ anon ]
00000006c0000000 4359360 2735484 2735484 rw--- [ anon ]
Pode-se perceber que o resultado final é o mapeamento da memória heap, que ocupa 4G, e a outra possui uma pegada de memória muito pequena, mas ainda não conseguimos ver o problema por meio dessas informações.
NativeMemoryTracking
❝Native Memory Tracking (NMT) é uma função usada pelo Hotspot VM para analisar o uso da memória interna da VM. Podemos usar o jcmd (integrado no jdk) para acessar os dados NMT.
O NMT deve primeiro ser ativado por meio dos parâmetros de inicialização da VM, mas deve-se observar que ativar o NMT causará perda de desempenho de cerca de 5% a 10%.
-XX:NativeMemoryTracking=[off | summary | detail]
# off: 默认关闭
# summary: 只统计各个分类的内存使用情况.
# detail: Collect memory usage by individual call sites.
Em seguida, execute o processo, você pode usar o seguinte comando para visualizar a memória direta:
jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]
# summary: 分类内存使用情况.
# detail: 详细内存使用情况,除了summary信息之外还包含了虚拟内存使用情况。
# baseline: 创建内存使用快照,方便和后面做对比
# summary.diff: 和上一次baseline的summary对比
# detail.diff: 和上一次baseline的detail对比
# shutdown: 关闭NMT
Nós usamos:
jcmd pid VM.native_memory detail scale=MB > temp.txt
Obtenha o resultado conforme mostrado:
Nenhuma das informações que nos foi dada na imagem acima pode ver claramente o problema, pelo menos eu ainda não consegui ver o problema através dessas várias informações.
A investigação parece ter chegado a um impasse.
Não há como sair da dúvida
Quando a investigação parou, obtivemos uma resposta do WCS e do SCF, "Ambas as partes confirmaram que não há vazamento de memória em seu pacote". O WCS não usou memória direta e o SCF foi usado como o protocolo RPC subjacente, mas Não vai deixar um bug de memória tão óbvio, caso contrário, deve haver muito feedback online.
Ver informações de memória JVM jmap
Nesse ponto, se não consegui encontrar o problema, abri um novo contêiner de sandbox novamente, executei o processo de serviço e, em seguida, executei o comando jmap para dar uma olhada na "configuração real" da memória JVM :
jmap -heap pid
obteve a resposta:
Attaching to process ID 1474, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.66-b17
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 4294967296 (4096.0MB)
NewSize = 2147483648 (2048.0MB)
MaxNewSize = 2147483648 (2048.0MB)
OldSize = 2147483648 (2048.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 1932787712 (1843.25MB)
used = 1698208480 (1619.5378112792969MB)
free = 234579232 (223.71218872070312MB)
87.86316621615607% used
Eden Space:
capacity = 1718091776 (1638.5MB)
used = 1690833680 (1612.504653930664MB)
free = 27258096 (25.995346069335938MB)
98.41346682518548% used
From Space:
capacity = 214695936 (204.75MB)
used = 7374800 (7.0331573486328125MB)
free = 207321136 (197.7168426513672MB)
3.4349974840697497% used
To Space:
capacity = 214695936 (204.75MB)
used = 0 (0.0MB)
free = 214695936 (204.75MB)
0.0% used
concurrent mark-sweep generation:
capacity = 2147483648 (2048.0MB)
used = 322602776 (307.6579818725586MB)
free = 1824880872 (1740.3420181274414MB)
15.022362396121025% used
29425 interned Strings occupying 3202824 bytes
Nas informações de saída, pode-se observar que as gerações antigas e novas são normais, o metaespaço ocupa apenas 20M, e a memória direta parece ser 2g ...
OK? Porque MaxMetaspaceSize = 17592186044415 MB
? "Aparência e sem restrições como」 .
Dê uma olhada em nossos parâmetros de inicialização:
-Xms4g -Xmx4g -Xmn2g -Xss1024K -XX:PermSize=256m -XX:MaxPermSize=512m -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=80
O que se configura é -XX:PermSize=256m -XX:MaxPermSize=512m
o espaço de memória da geração permanente. "E depois de 1.8, a máquina virtual Hotspot foi removida permanentemente gerações, usando o yuan em vez do espaço." Uma vez que estamos usando o JDK1.8 online, "então temos a capacidade máxima das restrições de espaço de yuan simplesmente não fazem" , o -XX:PermSize=256m -XX:MaxPermSize=512m
que Os dois parâmetros são parâmetros expirados para 1.8.
A figura a seguir descreve a mudança de 1,7 para 1,8, a geração permanente:
"Será que a memória do metaspace vazou?"
Escolhi testar localmente, o que é conveniente para alterar os parâmetros, e também é conveniente usar a ferramenta JVisualVM para ver visualmente as alterações de memória.
Use JVisualVM para observar o processo em execução
Primeiro limite o meta-espaço, use parâmetros -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m
e depois chame a interface problemática localmente.
Obtenha como mostrado:
"Pode-se ver que quando o meta-espaço se esgota, o sistema inicia Full GC, a memória do meta-espaço é recuperada e muitas classes são descarregadas."
Em seguida, removemos a restrição de meta espaço, ou seja, usamos os parâmetros que apresentavam problemas antes:
-Xms4g -Xmx4g -Xmn2g -Xss1024K -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=80 -XX:MaxDirectMemorySize=2g -XX:+UnlockDiagnosticVMOptions
Obtenha como mostrado:
“Percebe-se que o metaespaço está subindo, e as classes carregadas também estão subindo com o aumento do número de ligações, mostrando uma tendência de correlação positiva”.
Aldeia Liu An Hua Ming You Yi
Resolva os problemas imediatamente, "Com cada interface de chamada, muito provavelmente uma classe está constantemente sendo criada, ocupando espaço de memória yuan yuan .
Observe a situação de carregamento da classe JVM - detalhada
❝Ao depurar um programa, às vezes é necessário verificar as classes carregadas pelo programa, a situação de recuperação da memória, a interface local chamada, etc. Neste momento, o comando -verbose é necessário. Em myeclipse, você pode clicar com o botão direito para definir (como segue), ou pode inserir java-na linha de comando verboso para ver.
-verbose:class 查看类加载情况
-verbose:gc 查看虚拟机中内存回收情况
-verbose:jni 查看本地方法调用的情况
No ambiente local, adicionamos parâmetros de inicialização para -verbose:class
chamar a interface ciclicamente.
Você pode ver que incontáveis são gerados com.alibaba.fastjson.serializer.ASMSerializer_1_WlkCustomerDto
:
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_WlkCustomerDto from file:/C:/Users/yangzhendong01/.m2/repository/com/alibaba/fastjson/1.2.71/fastjson-1.2.71.jar]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_WlkCustomerDto from file:/C:/Users/yangzhendong01/.m2/repository/com/alibaba/fastjson/1.2.71/fastjson-1.2.71.jar]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_WlkCustomerDto from file:/C:/Users/yangzhendong01/.m2/repository/com/alibaba/fastjson/1.2.71/fastjson-1.2.71.jar]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_WlkCustomerDto from file:/C:/Users/yangzhendong01/.m2/repository/com/alibaba/fastjson/1.2.71/fastjson-1.2.71.jar]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_WlkCustomerDto from file:/C:/Users/yangzhendong01/.m2/repository/com/alibaba/fastjson/1.2.71/fastjson-1.2.71.jar]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_WlkCustomerDto from file:/C:/Users/yangzhendong01/.m2/repository/com/alibaba/fastjson/1.2.71/fastjson-1.2.71.jar]
Ao chamá-lo muitas vezes e acumular certas classes, executamos manualmente Full GC para reciclar o carregador de classes. Descobrimos que um grande número de classes relacionadas a fastjson foram recicladas.
"Se você usar o jmap para verificar o carregamento da classe antes de reciclar, também poderá encontrar um grande número de classes relacionadas ao fastjson:"
jmap -clstats 7984
Agora eu tenho uma direção, "verifique o código com atenção desta vez" , verifique onde fastjson é usado na lógica do código e encontrei o seguinte código:
/**
* 返回Json字符串.驼峰转_
* @param bean 实体类.
*/
public static String buildData(Object bean) {
try {
SerializeConfig CONFIG = new SerializeConfig();
CONFIG.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
return jsonString = JSON.toJSONString(bean, CONFIG);
} catch (Exception e) {
return null;
}
}
Causa raiz do problema
Serializamos a classe de entidade do campo camel case em um campo sublinhado antes de chamar wcs. Isso requer o uso de SerializeConfig de Fastjson e instanciamos em um método estático. Quando SerializeConfig é criado, uma classe de proxy ASM é criada por padrão para realizar a serialização do objeto de destino. Ou seja, as classes com.alibaba.fastjson.serializer.ASMSerializer_1_WlkCustomerDto
que são frequentemente criadas acima . Se reutilizarmos SerializeConfig, fastjson irá procurar as classes proxy criadas e reutilizá-las. Mas se new SerializeConfig () não puder encontrar a classe de proxy gerada originalmente, ele continuará a gerar uma nova classe de proxy WlkCustomerDto.
O código-fonte do local do problema nas duas imagens a seguir:
Usamos SerializeConfig como uma variável estática da classe, e o problema foi resolvido.
private static final SerializeConfig CONFIG = new SerializeConfig();
static {
CONFIG.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
}
O que fastjson SerializeConfig faz
Introdução ao SerializeConfig:
❝A principal função do SerializeConfig é configurar e registrar a classe de serialização (classe de implementação da interface ObjectSerializer) correspondente a cada tipo Java. Por exemplo, Boolean.class usa BooleanCodec (veja o nome para saber que esta classe implementará serialização e desserialização juntas) Como a classe de implementação de serialização, float []. Class usa FloatArraySerializer como a classe de implementação de serialização. Algumas dessas classes de implementação de serialização são implementadas por padrão no FastJSON (como as classes básicas Java) e algumas são geradas por meio da estrutura ASM (como o próprio usuário Classe de definição) e até mesmo algumas classes de serialização definidas pelo usuário (por exemplo, a implementação padrão da estrutura do tipo Date é convertida em milissegundos e o aplicativo precisa ser convertido em segundos). Claro, isso envolve o uso de ASM para gerar classes serializadas ou JavaBeans O problema de serialização da classe de serialização, o julgamento aqui é baseado em se o ambiente Android (a variável de ambiente "java.vm.name" é "dalvik" ou "lemur" é o ambiente Android), mas o julgamento não está apenas aqui, mas também o acompanhamento Julgamentos mais específicos.
Teoricamente falando, se cada instância de SerializeConfig serializar a mesma classe, ela encontrará a classe proxy dessa classe gerada antes para serialização. Nosso serviço instancia um objeto ParseConfig para definir as configurações de desserialização Fastjson toda vez que a interface é chamada. Se o proxy ASM não estiver desabilitado, já que cada chamada para ParseConfig é uma nova instância, está sempre verificado A classe de proxy criada não está disponível, então Fastjson constantemente cria novas classes de proxy e as carrega no metaspace, o que eventualmente leva à expansão contínua do metaspace e esgota a memória da máquina.
Os problemas só ocorrerão após a atualização do JDK1.8
Ainda vale a pena prestar atenção à causa do problema. Por que esse problema não ocorre antes da atualização? Isso é para analisar a diferença entre a máquina virtual de ponto de acesso que vem com jdk1.8 e 1.7.
❝ A partir do jdk1.8, a máquina virtual do ponto de host que vem com ela cancela a área permanente no passado e adiciona uma área de metaspace. Do ponto de vista funcional, o metaspace pode ser considerado semelhante à área permanente e sua função principal é armazenar metadados , Mas o mecanismo real é bem diferente.
Em primeiro lugar, o valor máximo padrão do metasspaço é o tamanho da memória física de toda a máquina, então a expansão contínua do metasspaço fará com que os programas java invadam a memória disponível do sistema e, eventualmente, o sistema não terá memória disponível; enquanto a área permanente tem um tamanho padrão fixo e não se expandirá para todo A memória disponível da máquina. Quando a memória alocada se esgota, ambos disparam gc total, mas a diferença é que quando a área permanente está em gc total, os metadados de classe (objeto de classe) na área permanente são recuperados com um mecanismo semelhante quando a memória heap é recuperada. Objetos que não podem ser alcançados pela referência raiz podem ser reciclados. Metaspace determina se os metadados da classe podem ser reciclados. Ele é julgado com base na possibilidade de reciclagem do Classloader que carrega os metadados da classe. Desde que o Classloader não possa ser reciclado, os metadados da classe são carregados por meio dele Não será reciclado. Isso também explica por que nossos dois serviços têm problemas após a atualização para 1.8, porque na versão jdk anterior, embora muitas classes de proxy tenham sido criadas toda vez que fastjson foi chamado, muitas classes de proxy foram carregadas na área permanente. Instâncias de classe, mas essas instâncias de classe são criadas quando o método é chamado e ficam inacessíveis após a chamada ser concluída. Portanto, quando a memória de área permanente estiver cheia e o gc completo for acionado, eles serão reciclados.
Ao usar 1.8, porque essas classes de proxy são carregadas por meio do Classloader do thread principal, este Classloader nunca será reciclado durante a execução do programa, portanto, as classes de proxy carregadas por meio dele nunca serão recicladas. Isso levou à expansão contínua do metaespaço, que acabou esgotando a memória da máquina.
Esse problema não se limita ao fastjson, desde que seja necessário carregar e criar classes através do programa, esse problema pode ocorrer. "Especialmente no framework, um grande número de ferramentas como ASM, javassist, etc. são frequentemente usadas para aprimoramento de bytecode. De acordo com a análise acima, antes de jdk1.8, porque na maioria dos casos a classe carregada dinamicamente pode ser obtida em gc completo Reciclagem, portanto, não está sujeito a problemas " e, portanto, muitos frameworks e kits de ferramentas não lidam com esse problema. Depois de atualizado para 1.8, esses problemas podem ser expostos.
Resumindo
O problema foi resolvido. Em seguida, analisei todo o processo de solução de problemas. Todo o processo revelou muitos problemas para mim. O mais importante é " Não estou familiarizado com a alocação de memória de diferentes versões do JVM" , o que levou a erros de julgamento da geração anterior e do metaespaço. , Fiz muitos desvios e passei muito tempo investigando na memória direta.
Em segundo lugar, a investigação precisa de “um é cuidadoso e o outro é abrangente.” É melhor separar todas as possibilidades primeiro, caso contrário, é fácil cair no escopo da investigação definido por você mesmo e entrar em um beco sem saída.
Finalmente, some os ganhos com este problema:
A partir de JDK 1.8, a máquina virtual do ponto de host que vem com ele cancela a área permanente anterior e adiciona uma área de metaspace. Do ponto de vista funcional, o metaspace pode ser considerado semelhante à área permanente. Sua função principal é armazenar metadados, mas O mecanismo real é bastante diferente.
A memória na JVM precisa ser restrita na inicialização, incluindo a familiar memória heap, mas também a memória direta e a área de meta-geração.Este é o último pacote para garantir a operação normal dos serviços online.
Ao usar bibliotecas de classes, preste mais atenção à maneira como o código é escrito e tente não mostrar vazamentos de memória óbvios.
Para bibliotecas que usam ferramentas de aprimoramento de bytecode, como ASM, tome cuidado ao usá-las (especialmente após JDK 1.8).
referência
Observe o processo de carregamento da classe quando o programa está em execução
blog.csdn.net/tenderhearted/article/details/39642275
Introdução geral do metaespaço (a razão pela qual a geração permanente é substituída, as características do metaespaço, o método de análise da memória do metaespaço)
https://www.cnblogs.com/duanxz/p/3520829.html
Procedimentos de solução de problemas comuns para exceções de uso de memória java (incluindo exceções de memória off-heap)
https://my.oschina.net/haitaohu/blog/3024843
Interpretação completa da memória off-heap da análise de código-fonte JVM
http://lovestblog.cn/blog/2015/05/12/direct-buffer/
Desinstalação de classes JVM
https://www.cnblogs.com/caoxb/p/12735525.html
fastjson abre asm em jdk1.8
https://github.com/alibaba/fastjson/issues/385
fastjson : PropertyNamingStrategy_cn
https://github.com/alibaba/fastjson/wiki/PropertyNamingStrategy_cn
Desconfie de vazamentos de memória Metaspace causados por agentes dinâmicos
https://blog.csdn.net/xyghehehehe/article/details/78820135
Não tem jeito, mas a técnica pode ser alcançada; se não tem jeito acaba com a técnica
Bem-vindos a todos que sigam a conta pública do Java Way
Bom artigo, estou lendo ❤️