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 Unsafeda 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 e putfield códigos de bytes, além da restrição de que um final 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 Unsaferecursos 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 Unsafecapacidades 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.