Parece que é muita coisa, mas é simples, prometo! 
A minha dúvida é com relação à remoção de um nó no meio de uma lista duplamente encadeada. O nó possui um elemento, uma referência para o nó seguinte e outra para o nó anterior.
u v w
Para remover v, eu faço, basicamente, u apontar para w e w apontar para u. Para liberar a memória que v ocupa, eu faria com que as referências dentro do nó v referenciassem NULL e eu atribuiria NULL também para o nó v. Porém, no livro de Estrutura de dados e Algorítmos em Java - 4º edição, o autor não se preocupou em atribuir NULL ao nó v. Olhem as imagens:

Ele simplesmente faz:
v.setPrev(null) //faz o ponteiro previous de v apontar para null. Antes apontava para u.
v.setNext(null) //faz o ponteiro next de v apontar para null. Antes apontava para w.
A próxima imagem ilustra que , para o autor, o garbage collector iria limpar o nó (que é um objeto com um elemento, uma variável de referência que referencia o elemento anterior e outra variável de referência, que referencia o elemento posterior) apenas com os comandos que ilustrei.

Duas perguntas: (1) eu não tenho que atribuir null à variável de referência v para que o GC limpe o objeto por ele referenciado?
Ex:
V v = new V(); //v tem a referência do objeto V
v = null; //ninguém mais referencia V. É um candidato ao GC.
(2) Somente essa atribuição não seria suficiente para limpar os objetos “ponteiros” dentro de v?
O GC coleta objetos que nao estejam sendo referenciados. Portanto, não é necessário atribuir null para que um objeto seja “pego” pelo GC.
Como informado anteriormente, basta que o objeto não esteja sendo referenciado por ninguém.
Então, se eu tenho uma variável de referência v que guarda a referência para V (V v = new V()), se eu atribuir v = null, o objeto V não terá mais ninguém referenciando ele. Se eu não fizer isso, tenho que esperar o programa terminar para depois o GC entrar em ação.
O exemplo abaixo deve ajudar a entender como funciona o GC:
public class Main {
public static void main( final String[] args ){
//Abaixo consta 1 variável e 1 objeto.
Object o1 = new NewClass();
Main.print( o1 );
//A variável o1 passa a apontar para um novo objeto
o1 = new NewClass();
Main.print( o1 );
//O GC, caso execute, irá liberar a memória utilizada pelo primeiro objeto que foi criado.
System.gc();
}
private static void print( final Object... objs ){
for( int index = 0; index < objs.length; index++ ){
final Object obj = objs[ index ];
System.out.print( index + ": " + ( obj == null ? ( "null; " ) : ( "#" + Integer.toHexString( obj.hashCode() ) + "; " ) ) );
}
System.out.print( "\n" );
}
public static class NewClass{
public NewClass(){
System.out.println( "Criando o objeto #" + Integer.toHexString( this.hashCode() ) );
}
public void finalize(){
System.err.println( "//GC - Finalizando o objeto #" + Integer.toHexString( this.hashCode() ) );
}
}
}
Obtive a seguinte saída na console:
run:
Criando o objeto #addbf1
0: #addbf1;
Criando o objeto #42e816
0: #42e816;
//GC - Finalizando o objeto #addbf1
CONSTRUÍDO COM SUCESSO (tempo total: 0 segundos)
Note que quando o GC executou, ele liberou a memória utilizada pelo objeto #addbf1, pois este não estava sendo referenciado por nenhuma variável.
Não é necessário esperar o programa terminar. Quando o método terminar, a variável v sairá de escopo e deixará de existir. Então, o garbage collector irá entrar em ação.