Da minha experiência - e a razão pela qual não usamos logrotate com Log4j é que a maneira como o logrotate funciona é que ele renomeia os arquivos, então instrui o programa a fechar seus logs e reabri-los com o nome antigo do arquivo (o que não existe mais), normalmente usando o sinal HUP.
Mas Log4j não pode ser instruído a reabrir seus arquivos de log, então eu vejo você usar copytruncate
para copiar os arquivos - o problema é que Log4j usa escritores em buffer que controlam a posição atual do arquivo que está sendo escrito e quando você trunca o arquivo de log, log4j continua escrevendo de onde parou de gravar antes do truncamento. Dependendo da sua implementação do sistema de arquivos, isso deve criar "arquivos com falhas" - ou seja, os caracteres NULL que você vê não existem - o arquivo é tão grande quanto os dados reais e o caractere NULL é o modo como o visualizador representa o arquivo buraco. Alguns sistemas de arquivos, por outro lado, não suportam falhas e, de fato, preenchem o arquivo com caracteres NULL quando o Log4j retoma a gravação.
Sugiro - não use logrotate, encontre uma maneira de girar os arquivos dentro do Log4j usando um RollingFileAppender (que suporta a remoção de arquivos antigos) ou usando o DailyRollingFileAppender e um cronjob que remova arquivos antigos externamente (como era para ser feito).