Java sun.misc.Unsafe
classe está em uso desde 2002. Ela fornece métodos essenciais de baixo nível que os desenvolvedores de estrutura usam para fornecer recursos e desempenho que de outra forma não seriam obtidos. Infelizmente, Unsafe
também tem problemas de longa data relacionados à capacidade de manutenção da JVM. E, como o nome indica, não é exatamente seguro de usar. Um JEP mais recente propõe remover os métodos de acesso à memória de sun.misc.Unsafe em uma versão Java futura. Mas o que irá substituí-los?
Este artigo analisa Unsafe
class, por que alguns de seus métodos estão programados para remoção e o que os desenvolvedores podem fazer para se preparar para essa mudança.
Por que os métodos inseguros do Java estão sendo obsoletos
Java sun.misc.Unsafe
class faz algumas coisas especiais que de outra forma não seriam permitidas. Seus poderes se enquadram em duas categorias gerais:
- Acesso e alocação de memória de baixo nível, semelhante a um ponteiro
- Construção de classe fora dos meios normais (mesmo além da reflexão)
No exercício destes poderes, alguns métodos de Unsafe
proteções padrão de quebra de classe e gerenciamento de memória integrados à JVM. Por causa disso, as implementações de JVM variam na forma como acomodam Unsafe
. Como resultado, o código é mais frágil e pode não ser portátil entre versões de JVM ou sistemas operacionais. Tê-lo em uso também prejudica a capacidade dos desenvolvedores de desenvolver componentes internos da JVM.
Por que os desenvolvedores usam o Unsafe
Unsafe
não é de todo ruim, e é por isso que é mantido há tanto tempo. O Guia de Baeldung para sun.misc.Unsafe inclui uma visão geral do que os desenvolvedores podem fazer com o Unsafe
aula:
- Instanciação de classe sem construtores
- Manipulação direta de campos de classe
- Lançando exceções verificadas quando elas não são tratadas pelo escopo
- Acesso direto às operações de memória heap
- Acesso às operações de comparação e troca (CAS)
A operação comparar e trocar do Java é um bom exemplo de por que temos um Unsafe
aula. CAS é uma instrução em nível de hardware, permitindo acesso à memória atômica. A atomicidade oferece benefícios significativos de desempenho para threads simultâneos porque nos permite modificar a memória sem bloquear. Tradicionalmente, as APIs Java padrão não podiam aproveitar esse recurso porque ele é específico do sistema operacional.
Aqui está um trecho de uma operação de comparação e troca usando Unsafe
da introdução da Oracle ao sun.misc.Unsafe:
public final class AtomicCounter implements Counter {
private static final Unsafe unsafe;
private static final long valueOffset;
private volatile int value = 0;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe"); // (1)
f.setAccessible(true); // (2)
unsafe = (Unsafe) f.get(null); // (3)
valueOffset = unsafe.objectFieldOffset
(AtomicCounter.class.getDeclaredField("value")); // (4)
} catch (Exception ex) { throw new Error(ex); }
}
@Override
public int increment() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1; // (5)
}
@Override
public int get() {
return value;
}
}
Você notará até mesmo o acesso a Unsafe
é estranho. É um membro de classe estático que deve primeiro ser acessado com reflexão (1) e depois definido manualmente como accessible
(2), e então referenciado, novamente com reflexão (3).
Com o Unsafe
classe em mãos, obtemos o deslocamento do campo de valor no presente AtomicCounter
classe (4). O deslocamento nos informa onde o campo reside na memória em relação à alocação de memória da classe. Observe que aqui estamos lidando com ponteiros, que podem não ser familiares aos desenvolvedores Java, embora sejam padrão em linguagens de gerenciamento direto de memória, como C/C++. Os ponteiros nos dão acesso direto à memória heap, colocando o ônus do acesso à memória sobre o desenvolvedor – algo que o mecanismo de coleta de lixo do Java foi explicitamente projetado para evitar.
Para executar a instrução CAS, o getAndAddInt()
função é chamada (5). Isso diz ao sistema operacional para executar a instrução atômica, usando this
como quadro de referência, no valueOffset
localização e com o “delta” de 1 – o que significa que o sistema operacional deve aumentar em 1.
Refatorando sun.misc.Unsafe
Agora você já experimentou usar Unsafe
. O exemplo também destaca algumas de suas desvantagens.
A execução de operações de memória incorretas pode fazer com que a JVM “trave” sem lançar uma exceção de plataforma. O mau gerenciamento de memória também pode levar a vazamentos de memória, que são notoriamente fáceis de criar e difíceis de diagnosticar e corrigir em algumas linguagens. O acesso direto à memória também pode abrir brechas de segurança, como buffer overflows. (Consulte Stack Overflow para uma análise mais detalhada das desvantagens de usar sun.misc.Unsafe.)
Mas os bugs criados pelos programadores são apenas parte do problema. Usando Unsafe
também resulta em código específico de implementação. Isso significa que os programas que os utilizam podem não ser tão portáteis. Também torna mais difícil para uma JVM alterar suas implementações nessas áreas.
Por todas essas razões, os desenvolvedores de Java estão se movendo para introduzir uma forma ordenada pela plataforma para executar as ações atualmente associadas ao Unsafe
aula. O problema é que o código usando sun.misc.Unsafe
está em todo lugar. Atinge várias estruturas em pontos críticos. E porque Unsafe
lida com operações delicadas de baixo nível, o código é especialmente arriscado para refatorar.
Alternativas para osun.misc.Unsafe
Os desenvolvedores de Java estão atualmente no processo de substituição Unsafe
recursos com versões padrão e menos problemáticas. Vejamos as alternativas, algumas das quais foram introduzidas no Java 9.
VarHandles
Os identificadores de variáveis são descritos no JEP 193. Este recurso cobre um dos maiores usos de Unsafe
, que acessa e manipula diretamente campos no heap. Vale a pena ler os objetivos do PEC para entender o que esse recurso faz, mas aqui vai um resumo:
- Não deve ser possível colocar a JVM em um estado de memória corrompida.
- O acesso a um campo de um objeto segue as mesmas regras de acesso que com
getfield
eputfield
códigos de bytes, além da restrição de que umfinal
campo de um objeto não pode ser atualizado. - Suas características de desempenho devem ser iguais ou semelhantes às do equivalente
sun.misc.Unsafe
operações. - A API deve ser melhor que a
sun.misc.Unsafe
API.
Em geral, VarHandles nos oferece uma versão melhor e mais segura do Unsafe
recursos e não compromete o desempenho. Você pode ver a variedade de tipos de operação que esse recurso suporta consultando o Javadoc para AccessModes. Você notará que há suporte para comparação e troca, junto com muitos outros tipos de operações primitivas.
Usando VarHandle
instâncias é uma maneira limpa e segura de realizar muitas operações de baixo nível diretamente nos campos, sem correr os riscos que corremos com Unsafe
.
Métodos
O MethodHandles
class (JEP 274) contém uma classe Lookup que substitui o uso de reflexão para obter e alterar permissões em campos (a técnica que você viu anteriormente no exemplo CAS). Com esta classe, as permissões são concedidas de acordo com o acesso do código que contém, em vez de serem definidas diretamente com reflexão.
A API Stack-Walking
O original Unsafe.getCallerClass()
é parcialmente substituído pela API Stack-Walking fornecida no JEP 259. Ela fornece uma maneira mais segura, embora mais indireta, de acessar a classe do chamador.
Soluções ainda necessárias
Alguns excelentes Unsafe
recursos ainda precisam ser substituídos. Um exemplo importante é a capacidade de criação de proxies e mocks. (Ben Evans fornece uma boa visão geral da questão em seu Revista Java artigo, Inseguro em qualquer velocidade.)
Também é interessante observar como os projetos do mundo real estão lutando com esse problema. Por exemplo, considere como o projeto Objenesis Git lidou com a eliminação de Unsafe.defineClass() da API.
Com base neste exemplo, podemos perceber que ainda há trabalho a ser feito para substituir totalmente Unsafe
capacidades de criar classes sem construtores.
Conclusão
O anel de latão para Java Unsafe
jornada é substituir completamente todos os recursos de acesso à memória de Unsafe
com versões autorizadas. Como você pode ver, esse trabalho não está acontecendo em um só lugar. Pedaços serão adicionados por meio de APIs mais recentes, embora VarHandle
aborda uma grande parte do trabalho.
Ter novas versões da funcionalidade é apenas metade da batalha. As diversas estruturas e projetos que atualmente dependem Unsafe
métodos devem migrar para as novas opções, o que não é nada simples. Em alguns casos, será necessária uma refatoração séria.
Gradualmente, a JVM provavelmente continuará a descontinuar e remover o Unsafe
recursos como alternativas válidas se solidificam. Parece que os administradores do Java estão empenhados em corrigir de forma responsável esta área de longa data da plataforma, garantindo ao mesmo tempo a estabilidade do ecossistema que depende dela.