Prefácio
A classe insegura é usada em várias classes do código -fonte do JDK. Esta classe fornece algumas funções subjacentes para ignorar a JVM, e sua implementação pode melhorar a eficiência. No entanto, é uma faca de dois gumes: como o nome prenunciou, é inseguro e a memória que aloca precisa ser manualmente livre (não reciclada pelo GC). A classe insegura fornece uma alternativa simples a certos recursos do JNI: garantir a eficiência e facilitar as coisas.
Essa classe pertence à classe ao sol.
Este artigo é principalmente sobre a compilação e tradução dos seguintes artigos.
http://mishadoff.com/blog/java-magic-part-4-sun-do-misc-dot-unsfe/
1. A maioria dos métodos da API insegura são implementações nativas, que consistem em 105 métodos, incluindo principalmente as seguintes categorias:
(1) Informações relacionadas. Retorne principalmente algumas informações de memória de baixo nível: endereço (), PageSize ()
(2) Objetos relacionados. Forneça principalmente o objeto e seus métodos de manipulação de domínio: alocateInstance (), ObjectFielfOffset ()
(3) classe relacionada. Forneça principalmente a classe e seus métodos de manipulação de domínio estático: staticfieldoffset (), definição (), definitivamenteSoMousClass (), secureclassinitialized ()
(4) Matrizes relacionadas. Manipulação de matriz Método: ArrayBaseoffset (), ArrayIndexScale ()
(5) relacionado à sincronização. Forneça principalmente primitivas de sincronização de baixo nível (como Primitives CAS baseadas em CPU (compare e swap)): MonitorEnter (), TryMonitorEnter (), Monitorexit (), ComparaDswapint (), PutorderEdint ()
(6) relacionado à memória. Método de acesso à memória direta (ignorar a pilha JVM e manipular a memória local diretamente): alocatememory (), copymemory (), freememory (), getAddress (), getint (), putint ()
2. Obter instância de classe insegura
O design de classe inseguro é fornecido apenas ao carregador de classe de inicialização confiável da JVM e é uma classe típica de padrões singleton. Seu método de aquisição de instância é o seguinte:
public estático inseguro getunsafe () {classe cc = Sun.reflelect.reflection.getCalerClass (2); if (cc.getclassloader ()! = null) lançar nova segurança ("inseguro"); retornar theunsfe;}Um carregador de classe não iniciado chamará diretamente o método insefe.getunsafe () e lançará uma segurança (o motivo específico envolve o mecanismo de carregamento pai da classe JVM).
Existem duas soluções. Uma é especificar a classe a ser usada como a classe de inicialização através do parâmetro JVM - XbootclassPath. O outro método é a reflexão de Java.
Campo f = insefa.class.getDecLaredfield ("theUsafe"); f.SetAccessible (true); inseguro inseguro = (inseguro) f.get (nulo);Definindo brutalmente acessíveis a true para a instância privada de singleton e, em seguida, obtenha diretamente um objeto fundido para inseguro através do método GET de Field. No IDE, esses métodos serão marcados como erro e podem ser resolvidos pelas seguintes configurações:
Preferências -> java -> compilador -> erros/avisos -> API depreciada e restrita -> Referência proibida -> Aviso
3. Cenários de aplicação "interessantes" de classe insegura
(1) ignorar o método de inicialização da classe. O método alocateInstance () se torna muito útil quando você deseja ignorar construtores de objetos, verificadores de segurança ou construtores sem público.
classe A {private Long a; // valor não inicializado public a () {this.a = 1; // inicialização} public a () {return this.a; }}A seguir, é apresentada uma comparação do método de construção, método de reflexão e alocarInstance ()
A1 = novo a (); // constructoro1.a (); // imprime 1 a O2 = a.class.newInstance (); // reflexoo2.a (); // imprime 1 a o3 = (a) inseguro.AllocateInstance (A.Class); // insefa3.a (); // imprime 0
alocadaInstance () não entra no método do construtor e, no modo singleton, parecemos ver uma crise.
(2) modificação da memória
A modificação da memória é relativamente comum na linguagem C. Em Java, ele pode ser usado para ignorar o verificador de segurança.
Considere as seguintes regras de verificação de acesso simples:
classe Guard {private int Access_allowed = 1; public boolean GiveAccess () {return 42 == Access_allowed; }}Em circunstâncias normais, o GiveAcess sempre retorna falsa, mas nem sempre acontece
Guarda guarda = new guarda (); guarda.giveaccess (); // false, sem acesso // Bypassunsafe inseguro = getUNSafe (); campo f = guarda.getclass (). getDecaredfield ("access_allowed"); inseguro.putInt (guarda, unsefe.objectfielffset (f), 42); // Memory Corrupção Guard.giveAccess (); // verdadeiro, acesso concedidoAo calcular o deslocamento da memória e usando o método putInt (), o access_allowed da classe é modificado. Quando uma estrutura de classe é conhecida, o deslocamento de dados sempre pode ser calculado (consistente com o cálculo do deslocamento de dados na classe em C ++).
(3) Implementar a função sizeof () semelhante à linguagem C
Implementar uma função sizeof () do tipo c combinando a função de reflexão Java e ObjectFielfOffset ().
public static sizeof (objeto o) {inseguro u = getUNSafe (); HashSet campos = new HashSet (); Classe C = O.getClass (); while (c! = object.class) {for (campo f: c.getDecLaredFields ()) {if ((f.getModifiers () & modifier.static) == 0) {campos.add (f); }} c = c.getSuperclass (); } // Fique com deslocamento maxSize longo = 0; para (campo F: campos) {long offset = U.ObjectFieldOffset (f); if (deslocamento> maxSize) {maxsize = offset; }} return ((maxSize/8) + 1) * 8; // preenchimento}A idéia do algoritmo é muito clara: comece da subclasse subjacente, retire os domínios não estáticos de si mesmo e de todas as suas superclasses, por sua vez, coloque-os em um hashset (os cálculos repetidos são apenas uma vez, o Java é um único herança) e, em seguida, use o ObjectFieldSet () para obter um desvio máximo e finalmente considerar o alinhamento.
Em uma JVM de 32 bits, o tamanho pode ser obtido lendo um longo com um deslocamento de arquivo de classe de 12.
public static sizeof (objeto objeto) {return getunsafe (). getAddress (normalize (getunsafe (). getint (objeto, 4l)) + 12l);}A função normalize () é um método que converte
private estática long normalize (int valor) {if (value> = 0) retorna valor; Retornar (0L >>> 32) e valor;}O tamanho dos dois sizeof () calculado é o mesmo. A implementação mais padrão de tamanho de () é usar java.lang.instrument, no entanto, requer especificar o parâmetro de linha de comando -Javaagent.
(4) Implementar replicação superficial de java
O esquema de replicação superficial padrão é implementar a interface clonável ou as funções de replicação implementadas por si só, e elas não são funções multiuso. Ao combinar o método sizeof (), pode ser alcançada cópia superficial.
Objeto estático SHOULLOWCOPY (objeto obj) {tamanho longo = sizeof (obj); long start = toaddress (obj); endereço longo = getUNSafe (). alocatememory (tamanho); getunsfe (). CopyMemory (start, endereço, tamanho); Retorne de address (endereço);}Os seguintes toaddress () e fromddress () convertem o objeto em seu endereço e a operação reversa, respectivamente.
Toaddress longo estático (objeto obj) {objeto [] Array = new Object [] {obj}; Longo Caseffset = getUNSafe (). ArrayBaseOffset (objeto []. Classe); retornar normalize (getUNSafe (). getInt (array, bSharoffset));} objeto estático doddress (endereço longo) {object [] array = new Object [] {null}; Longo Caseffset = getUNSafe (). ArrayBaseOffset (objeto []. Classe); getunsfe (). Retorno da matriz [0];}A função de cópia superficial acima pode ser aplicada a qualquer objeto Java e seu tamanho é calculado dinamicamente.
(5) eliminar senhas na memória
Os campos de senha são armazenados na String, no entanto, a reciclagem de string é gerenciada pela JVM. A maneira mais segura é substituir o campo de senha após o uso.
Campo stringValue = string.class.getDeclaredfield ("value"); stringValue.setAccessible (true); char [] mem = (char []) stringValue.get (senha); para (int i = 0; i <mem.length; i ++) {[i] = '?';}(6) carregamento dinâmico de classes
O método padrão de classes de carregamento dinamicamente é classe.ForName () (Ao escrever programas JDBC, lembro -me profundamente). Inseguro também pode carregar dinamicamente os arquivos da classe Java.
byte [] ClassContents = getClassContent (); classe C = getUNSafe (). DefinClass (NULL, ClassContents, 0, ClassContents.Length); c.getMethod ("a"). Invoke (c.newInstance (), nulo); // O método 1GetClassContent () lê um arquivo de classe para uma matriz de bytes. byte estático privado [] getClassContent () lança exceção {arquivo f = new File ("/home/mishadoff/tmp/a.class"); FileInputStream input = new FileInputStream (f); byte [] content = novo byte [(int) f.Length ()]; input.read (content); input.Close (); Retornar conteúdo;}Pode ser aplicado em carregamento dinâmico, proxy, fatiamento e outras funções.
(7) A exceção de detecção de pacotes é uma exceção de tempo de execução.
getunsfe (). ThrowException (new IoException ());
Isso pode ser feito quando você não deseja capturar a exceção verificada (não recomendada).
(8) serialização rápida
O serializável Java padrão é muito lento e também limita que a classe deve ter um construtor público sem parâmetros. Externizável é melhor, ele precisa especificar um esquema para a classe ser serializada. Bibliotecas de serialização eficientes populares, como Kryo, confiando em bibliotecas de terceiros, aumentarão o consumo de memória. Você pode obter o valor real do domínio na classe através do getint (), getLong (), getObject () e outros métodos, e persistir informações como o nome da classe no arquivo juntos. Kryo tentou usar inseguro, mas não há dados específicos de melhoria de desempenho. (http://code.google.com/p/kryo/issues/detail?id=75)
(9) alocar memória em heap não Java
Novo usando Java alocará memória para objetos na pilha, e o ciclo de vida do objeto será gerenciado pela JVM GC.
classe SuperArray {private final static int byte = 1; tamanho longo privado; endereço longo privado; public superarray (tamanho longo) {this.size = size; endereço = getUNSafe (). alocatememory (tamanho * byte); } public void Set (Long i, byte value) {getUNSafe (). putbyte (endereço + i * byte, valor); } public int get (longo idx) {return getUNSafe (). getByte (endereço + idx * byte); } public size longo () {tamanho de retorno; }}A memória alocada pelo inseguro não é limitada pelo número inteiro.max_value e é alocada na memória não-heap. Ao usá -lo, você precisa ser muito cauteloso: se você esquecer de reciclá -lo manualmente, ocorrerá vazamentos de memória; Se você acessar o endereço ilegal, fará com que a JVM falhe. Ele pode ser usado quando você precisar alocar grandes áreas contínuas, programação em tempo real (não tolerando a latência da JVM). Java.nio usa essa tecnologia.
(10) Aplicações na concorrência de Java
Ao usar o insefe.compareandswap (), ele pode ser usado para implementar estruturas de dados sem bloqueio eficientes.
classe em cascounter implementa o contador {private volátil contador longo = 0; privado inseguro inseguro; Offset privado longo; public cascounter () lança exceção {insefe = getUNSafe (); offset = insefa.objectfieldOffset (cascounter.class.getDecaredfield ("contador")); } @Override public void increment () {muito antes = contador; while (! insefe.compareandswaplong (isto, deslocamento, antes, antes de + 1)) {antes = contador; }} @Override public Long GetCounter () {Return Counter; }}Através do teste, a estrutura de dados acima é basicamente a mesma que a eficiência das variáveis atômicas Java. As variáveis atômicas Java também usam o método comparaandswap () da UNSAFE, e esse método acabará por corresponder aos primitivos correspondentes da CPU, por isso é muito eficiente. Aqui está uma solução para implementar o Hashmap sem bloqueio (http://www.azulsystems.com/about_us/presentations/lock-frie-hash. A idéia desta solução é: analise cada estado, crie cópias, modifique cópias, use primitivas CAS, bloqueios de spin). Nas máquinas de servidores comuns (núcleo <32), usando o concorrente (antes do JDK8, o bloqueio de separação de 16 canais padrão foi implementado e o concorrente foi implementado usando o bloqueio) é obviamente suficiente.
Resumir
O acima é o conteúdo inteiro deste artigo. Espero que o conteúdo deste artigo tenha certo valor de referência para o estudo ou trabalho de todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.