Os sistemas operacionais realizam operações de E / S em áreas de memória. Essas áreas de memória, no que diz respeito ao sistema operacional, são seqüências contíguas de bytes. Não é surpresa, portanto, que apenas os buffers de byte sejam elegíveis para participar de operações de E / S. Lembre-se também de que o sistema operacional acessará diretamente o espaço de endereço do processo, neste caso o processo da JVM, para transferir os dados. Isso significa que as áreas de memória que são alvos de perações de E / S devem ser sequências contíguas de bytes. Na JVM, uma matriz de bytes não pode ser armazenada contiguamente na memória, ou o Garbage Collector pode movê-lo a qualquer momento. Matrizes são objetos em Java, e a maneira como os dados são armazenados dentro desse objeto pode variar de uma implementação da JVM para outra.
Por esse motivo, a noção de um buffer direto foi introduzida. Buffers diretos destinam-se à interação com canais e rotinas de E / S nativas. Eles se esforçam ao máximo para armazenar os elementos de bytes em uma área de memória que um canal pode usar para acesso direto ou bruto usando código nativo para dizer ao sistema operacional para drenar ou preencher a área de memória diretamente.
Buffers de byte direto geralmente são a melhor opção para operações de E / S. Por design, eles suportam o mecanismo de E / S mais eficiente disponível para a JVM. Buffers de bytes não indiretos podem ser transmitidos aos canais, mas isso pode resultar em uma penalidade de desempenho. Geralmente, não é possível que um buffer não direto seja o destino de uma operação de E / S nativa. Se você passar um objeto ByteBuffer não indireto para um canal para gravação, o canal poderá implicitamente fazer o seguinte em cada chamada:
- Crie um objeto ByteBuffer direto temporário.
- Copie o conteúdo do buffer não direto para o buffer temporário.
- Execute a operação de E / S de baixo nível usando o buffer temporário.
- O objeto de buffer temporário fica fora do escopo e, eventualmente, é coletado como lixo.
Isso pode resultar em cópia de buffer e rotatividade de objetos em todas as E / S, que são exatamente os tipos de coisas que gostaríamos de evitar. No entanto, dependendo da implementação, as coisas podem não ser tão ruins. O tempo de execução provavelmente armazenará em cache e reutilizará os buffers diretos ou executará outros truques inteligentes para aumentar a taxa de transferência. Se você está simplesmente criando um buffer para uso único, a diferença não é significativa. Por outro lado, se você estiver usando o buffer repetidamente em um cenário de alto desempenho, é melhor alocar buffers diretos e reutilizá-los.
Os buffers diretos são ideais para E / S, mas podem ser mais caros de criar do que os buffers de bytes não indiretos. A memória usada pelos buffers diretos é alocada através da chamada para código específico do sistema operacional nativo, ignorando o heap da JVM padrão. Configurar e remover buffers diretos pode ser significativamente mais caro que os buffers residentes em heap, dependendo do sistema operacional do host e da implementação da JVM. As áreas de armazenamento de memória dos buffers diretos não estão sujeitas à coleta de lixo porque estão fora do heap da JVM padrão.
As compensações de desempenho do uso de buffers diretos versus não-diretos podem variar amplamente de acordo com a JVM, o sistema operacional e o design do código. Ao alocar memória fora do heap, você pode sujeitar seu aplicativo a forças adicionais que a JVM desconhece. Ao colocar peças móveis adicionais em jogo, verifique se está obtendo o efeito desejado. Eu recomendo a antiga máxima do software: primeiro faça o trabalho e depois rápido. Não se preocupe muito com otimização antecipadamente; concentre-se primeiro na correção. A implementação da JVM pode ser capaz de executar o armazenamento em cache do buffer ou outras otimizações que fornecerão o desempenho necessário sem muito esforço desnecessário de sua parte.