Java monitora os objetos que foram gravados no fluxo e as instâncias subseqüentes são gravadas como um ID, não como um objeto serializado real.
Portanto, para o seu exemplo, se você escrever a instância "a" no fluxo, o fluxo fornecerá a esse objeto um ID exclusivo (digamos "1"). Como parte da serialização de "a", você precisa serializar "b", e o fluxo fornece outro ID ("2"). Se você escrever "b" no fluxo, a única coisa gravada é o ID, não o objeto real.
O fluxo de entrada faz a mesma coisa ao contrário: para cada objeto que lê do fluxo, atribui um número de ID usando o mesmo algoritmo que o fluxo de saída, e esse número de ID faz referência à instância do objeto em um mapa. Quando vê um objeto que foi serializado usando um ID, ele recupera a instância original do mapa.
É assim que os documentos da API a descrevem:
Várias referências a um único objeto são codificadas usando um mecanismo de compartilhamento de referência, para que os gráficos dos objetos possam ser restaurados com a mesma forma de quando o original foi gravado.
Esse comportamento pode causar problemas: como o fluxo contém uma referência rígida para cada objeto (para que ele saiba quando substituir o ID), você pode ficar sem memória se gravar muitos objetos transitórios no fluxo. Você resolve isso chamando reset()
.