Artigo Java Magazine 11 - New I/O Fundamental

Artigo publicado pela JAva Magazine.

Esse artigo faz parte da revista Java Magazine edição 10. Clique aqui para ler todos os artigos desta edição

 

 

Atenção: por essa edição ser muito antiga não há arquivo PDF para download.Os artigos dessa edição estão disponíveis somente através do formato HTML. 

 

Pente fino

New I/O Fundamental

Parte 2: Operações Avançadas

Arquivos mapeados em memória, I/O não-bloqueante, locking de arquivos e multiplexação – o estado-da-arte em I/O com Java

No artigo anterior apresentamos os conceitos básicos do NIO e algumas novas operações oferecidas por essa API, como a transferência direta entre buffers e buffers diretos. Neste segundo artigo, apresentaremos recursos avançados da API New I/O que colocam a linguagem Java em pé de igualdade com linguagens de mais baixo nível como C/C++, na área de manipulação de I/O: mapeamento de arquivos em memória, file locking, I/O não-bloqueante e multiplexação e I/O.

Mapeamento de arquivo em memória

Quando um arquivo é aberto por um processo, o sistema operacional (SO) cria um buffer na memória virtual para a transferência de dados entre o processo e o arquivo. Ou seja, em vez de escrever diretamente no arquivo, o processo escreve em um buffer e o SO, de tempos em tempos, transfere o conteúdo do buffer para o arquivo (a leitura é feita de forma similar, usando também um buffer). Embora seja um procedimento transparente ao programador, a transferência para buffers intermediários penaliza a performance em algumas situações, principalmente quando o arquivo manipulado é muito grande ou acessado por vários processos.

Para resolver esse problema, sistemas operacionais modernos oferecem a possibilidade de mapear um arquivo diretamente em memória (memory-mapped files). A principal vantagem em relação ao procedimento tradicional é que o SO não precisa alocar memória física para o arquivo –­ além da economia de memória, ganha-se em performance, pois os acessos à memória mapeada nunca geram page faults.[1]

A economia de memória é ainda maior quando vários processos mapeiam o mesmo arquivo. Pelo procedimento tradicional, seria preciso alocar uma região de memória física para cada processo acessando o arquivo. Além disso, as modificações realizadas na memória mapeada são imediatamente refletidas no arquivo e vice-versa.

Para mapear um arquivo em memória com NIO, basta chamar map(FileChannel.MapMode modo, long pos, long tamanho) em um FileChannel associado ao arquivo, e será retornado um objeto java.nio.MappedByteBuffer. Os parâmetros pos e tamanho definem o trecho do arquivo a ser mapeado, e modo, a maneira como o mapeamento será feito.

O exemplo da Listagem 1 compara o consumo de memória usando o mapeamento direto e a alocação de buffers, e a Listagem 2 mostra o resultado para a um arquivo de 15 MB – note que a quantia de memória alocada para a JVM não se altera quando é usado mapeamento em memória, mas sim apenas após a alocação de um buffer.

A aplicação JCanyon (veja links) é um exemplo interessante do uso de NIO um simulador de vôo que usa o mapeamento direto em memória para ler as imagens de satélite (que ocupam cerca de 200 Mb) e dados topográficos (mais de 100 Mb), além de buffers diretos para o envio de informações à placa de vídeo.

File Locking

Imagine a situação em que vários threads precisem ler e alterar um mesmo arquivo. É preciso um mecanismo que garanta que, enquanto um thread altera o arquivo, os demais não possam acessá-lo. Como os threads estão sendo executados na mesma JVM, bastaria isolar os trechos que fazem acesso ao arquivo em blocos synchronized, para que o próprio sistema de threads da JVM resolvesse o problema. Mas e se o arquivo fosse acessado por threads executando em JVMs diferentes, ou mesmo por programas escritos em outras linguagens de programação?

Nessas situações, a saída é delegar ao sistema operacional o controle de acesso ao arquivo, através do mecanismo de file locking, que em NIO é implementado pelos métodos lock() e tryLock() da classe FileChannel. O primeiro método bloqueia o thread atual até que ela obtenha o lock no arquivo (representado pela classe java.nio.channels.FileLock); o segundo não bloqueia o thread, mas retorna null caso não tenha sido possível obter o lock (veja mais sobre o conceito de bloqueio na seção "I/O não bloqueante", a seguir).

Também é possível obter um lock apenas para um trecho de um arquivo, através dos métodos sobrecarregados lock(int posInicial, int tamanho, boolean compartilhado) e " [...] continue lendo...

Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados