Por que usar o método dispose() em uma janela?

Oi,

A principio, isso me parece um ponto negativo da linguagem (eu não esperava ter que dizer isso um dia).

O Java ou a linguagem deveria interpretar que determinado comando, como o dispose(), deveria remover toda a referência do objeto. A não ser que exista uma razão muito complexa para que isso não aconteça. (?!)

Tchauzin!

[quote=lina]Oi,

A principio, isso me parece um ponto negativo da linguagem (eu não esperava ter que dizer isso um dia).

O Java ou a linguagem deveria interpretar que determinado comando, como o dispose(), deveria remover toda a referência do objeto. A não ser que exista uma razão muito complexa para que isso não aconteça. (?!)

Tchauzin![/quote]

dispose() não faz isso? Pelo que pude entender até agora toda a janela e os componentes associados a ela são liberados da memória se a janela não for mais usada em nenhum outro lugar. Se não tiver mais referências à janela o Garbage Collector vai simplesmente se livrar da janela.

[quote=ceklock][quote=lina]Oi,

A principio, isso me parece um ponto negativo da linguagem (eu não esperava ter que dizer isso um dia).

O Java ou a linguagem deveria interpretar que determinado comando, como o dispose(), deveria remover toda a referência do objeto. A não ser que exista uma razão muito complexa para que isso não aconteça. (?!)

Tchauzin![/quote]

dispose() não faz isso? Pelo que pude entender até agora toda a janela e os componentes associados a ela são liberados da memória.
[/quote]

Oi,

Eu entendi ao contrário…

Exemplo:

Fechando:

Janela teste = new Janela().setVisible(true); teste.dispose();

Liberando da memoria:

Janela teste = new Janela().setVisible(true); teste.dispose(); teste = null;

Tchauzin!

De qualquer forma, é necessário lembrar que toda vez que se usa um sistema operacional como o Windows ou o Linux, você tem o conceito de objetos (“recursos”) que são solicitados ao sistema operacional e cujo ciclo de vida é controlado por ele. Entre esses objetos, estão: janelas, arquivos em disco, sockets, semáforos, memória compartilhada etc. Quando se usa bancos de dados, além disso temos as conexões.
Tais recursos são finitos e devem ser devolvidos depois de usados.
A devolução pode representar um simples fechamento (no caso de arquivos, onde o fechamento indica apenas que você não quer mais lidar com esse arquivo - talvez você também queira efetivar suas ultimas alterações) ou então a prõpria destruição do recurso (como costuma ser o caso das janelas).
No caso do AWT e do Swing, a palavra-chave usada para dispor (devolver) o recurso finito que é a janela é “dispose”.
No caso do java.io, a palavra-chave usada para devolver o recurso (arquivo) é “close”.
O “garbage collector” lida com apenas um tipo de recurso finito, que é a memória usada por seu programa. Não é atribuição dele sair limpando outros tipos de recursos.
Como certo “abuso”, pode-se entretanto abusar do “garbage collector” para que ele, de vez em quando, também limpe outros tipos de recursos (nesse caso, se o recurso estiver em uma classe que implementa o método finalize - http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Object.html#finalize() .
E é por isso que certos professores ingênuos (daqueles que largam uma toalha molhada em cima da cama e esperam que alguém vá recolhê-la automaticamente) ensinam que arquivos não precisam ser fechados, porque o “garbage collector” irá fazer isso. Isso é um abuso e nunca deve ser feito, na verdade.
Atenção: isso é realmente um “abuso” porque uma classe que implementa esse método indica ao garbage collector que objetos dessa classe que não são mais referenciados são postos em uma lista de objetos a serem “finalizados”, mas essa finalização fica a critério do garbage collector e não é determinística (ou seja, pode ser que um objeto marcado para ser finalizado nunca o seja dentro do tempo de execução de seu programa).

[quote=lina][quote=ceklock][quote=lina]Oi,

A principio, isso me parece um ponto negativo da linguagem (eu não esperava ter que dizer isso um dia).

O Java ou a linguagem deveria interpretar que determinado comando, como o dispose(), deveria remover toda a referência do objeto. A não ser que exista uma razão muito complexa para que isso não aconteça. (?!)

Tchauzin![/quote]

dispose() não faz isso? Pelo que pude entender até agora toda a janela e os componentes associados a ela são liberados da memória.
[/quote]

Oi,

Eu entendi ao contrário…

Exemplo:

Fechando:

Janela teste = new Janela().setVisible(true); teste.dispose();

Liberando da memoria:

Janela teste = new Janela().setVisible(true); teste.dispose(); teste = null;

Tchauzin![/quote]

Eu sempre fiz assim e vou continuar fazendo:

JFrame f = new JFrame(); ... f.setVisible(true); ... f.dispose();

Não precisa de f = null;

[quote=ceklock][quote=lina][quote=ceklock][quote=lina]Oi,

A principio, isso me parece um ponto negativo da linguagem (eu não esperava ter que dizer isso um dia).

O Java ou a linguagem deveria interpretar que determinado comando, como o dispose(), deveria remover toda a referência do objeto. A não ser que exista uma razão muito complexa para que isso não aconteça. (?!)

Tchauzin![/quote]

dispose() não faz isso? Pelo que pude entender até agora toda a janela e os componentes associados a ela são liberados da memória.
[/quote]

Oi,

Eu entendi ao contrário…

Exemplo:

Fechando:

Janela teste = new Janela().setVisible(true); teste.dispose();

Liberando da memoria:

Janela teste = new Janela().setVisible(true); teste.dispose(); teste = null;

Tchauzin![/quote]

Eu sempre fiz assim e vou continuar fazendo:

JFrame f = new JFrame(); ... f.setVisible(true); ... f.dispose();

Não precisa de f = null;
[/quote]

Oi,

Eu também sempre fiz dessa forma. Mas depois disso:

Como se faz com todos os objetos em java, definindo todas as variáveis que apontam para ele como null.
Atenção especial no caso de JInternalFrames, pois eles deverão ser removidos do DesktopPane também.

Em java, a única maneira de um objeto ser coletado é se não houver mais referências para ele.[/quote]

Começarei a colocar null. Né?

Tchauzin!

Mas é a ressalva que estou fazendo. Não adianta dar dispose sem limpar as referências.
Ou seja, caso haja variáveis apontando para a janela, elas tem que deixar de apontar.

Não estou falando para sair dando null em tudo. Só no que precisa ser dado (propriedades que não sairão de escopo, por exemplo), ou desregistrar a janela de um DesktopPane.
Caso contrário, ela não será coletada.

Essa é uma confusão comum: o dispose() não tem o poder mágico de fazer o garbage collector coletar o que ainda é referenciado.

Depende do escopo de sua variável f. Se ela se mantiver viva (ou seja, não for criada num método local), o frame também continuará. O dispose() só irá liberar os recursos do SO que o entanglement falou, mas os recursos do java, (dados de classes, labels, imagens, etc) continuarão vivos.

É o caso que ocorre com JInternalFrames, enquanto tiverem uma referência no seu DesktopPane.

PS: O que falei é exatamente o problema que esse usuário está tendo no tópico:
http://www.guj.com.br/java/273455-uso-de-memoria#1436336

[quote=entanglement]
O “garbage collector” lida com apenas um tipo de recurso finito, que é a memória usada por seu programa. Não é atribuição dele sair limpando outros tipos de recursos.[/quote]

Isso é claro. Recursos do sistema não são responsabilidade da JVM e devem ser devidamente encerrados quando não são mais utilizados.

Exatamente. O problema é que janelas consomem uma quantidade imensa de recursos gerenciados, além dos do SO. Então, muita gente dá um dispose() achando que a janela parou de ocupar memória (ou agora ocupa uma quantia insignificante), quando isso não ocorre.

Imagens, por exemplo, são recursos grandes e pesados que são gerenciados pela VM. As informações dos componentes, tais como altura, largura, seu texto, relações, look&feel, etc… também são gerenciadas pela VM.

Então, é errado pensar que um dispose() já eliminou a janela. Se quiser eliminar mesmo da memória, tem que eliminar as referências a ela também. Ou seja, se tiver no seu frame uma propriedade que guarda ali a instância da janela aberta, essa propriedade tem que ser setada para null. Se tiver um DesktopPane, que tambem tem a lista das janelas que ele contém, essa janela tem que ser removida de lá. Se sua janela for listener de outros componentes, você terá que desregistra-la (exceto se esses componentes forem dela mesma). E assim por diante. Só quando a janela não for apontada por ninguém, é que ela será integralmente removida da memória.

O negócio é o seguinte. Salvo alguma rara exceção, na grande maioria dos casos não é necessário setar uma variável como null. A máquina virtual sabe muito bem quando uma variável/atributo não é usado.

O dispose() serve justamente para “desamarrar” a janela. Se ela nunca mais for usada ela vai “sumir” da memória até que algum dia seja chamado um método setVisible() ou algo parecido.

E tudo que está dentro da janela pertence à janela e vai “sumir” junto com ela. Se tem um botão adicionado numa janela e eu matar a janela eu não preciso remover o botão da janela para ele ser removido da memória.

http://docs.oracle.com/javase/6/docs/api/java/awt/Window.html#dispose()

Por mim podem encerrar esse tópico. A conversa já tá mudando o foco. Minha dúvida já foi esclarecida.

Uma curiosidade.

Crie um programa que aloque 100 strings cada uma ocupando 1 MB de memória (ou seja, cada uma delas contém 500 mil caracteres). Ponha-as em um array e imprima a quantidade de memória usada pelo programa. Para garantir que é a memória mesmo, antes de imprimir, execute um System.gc, espere 2 segundos, e entáo execute outro System.gc.

A seguir, crie um outro array de 100 posicóes contendo substrings dessas strings grandes (digamos da pposiçao 1000 até a 2000). Imprima a mem[oria usada. Você deve ver que a quantidade praticamente não se alterou.

Então, limpe o primeiro array (aquele das strings grandes). Execute um System.gc e veja quanta memória está sendo usada.

Perguntas:

  1. a quantidade de memória usada caiu ou não?
  2. Por que é que isso ocorreu? (Dica: leia o fonte da classe String, método substring)
  3. Como evitar esse problema esquisito?

[quote=entanglement]Uma curiosidade.

Crie um programa que aloque 100 strings cada uma ocupando 1 MB de memória (ou seja, cada uma delas contém 500 mil caracteres). Ponha-as em um array e imprima a quantidade de memória usada pelo programa. Para garantir que é a memória mesmo, antes de imprimir, execute um System.gc, espere 2 segundos, e entáo execute outro System.gc.

A seguir, crie um outro array de 100 posicóes contendo substrings dessas strings grandes (digamos da pposiçao 1000 até a 2000). Imprima a mem[oria usada. Você deve ver que a quantidade praticamente não se alterou.

Então, limpe o primeiro array (aquele das strings grandes). Execute um System.gc e veja quanta memória está sendo usada.

Perguntas:

  1. a quantidade de memória usada caiu ou não?
  2. Por que é que isso ocorreu? (Dica: leia o fonte da classe String, método substring)
  3. Como evitar esse problema esquisito?

[/quote]

  1. a quantidade não vai cair
  2. Isso se chama “memory leak” (na verdade não é bem um memory leak, mas se a pessoa não souber o que está fazendo pode ser considerado um) e existem casos em Java onde pode acontecer sim, sendo este um exemplo. substring() mantém referência à String original, por isso vai continuar ocupando a mesma quantidade de memória.
  3. Saiba como funciona a classe String e seus métodos. Nesse caso dá pra usar new String(s.substring(…));

Enfim, esse não é o foco desse tópico, mas tudo bem.

Eu só discordo de que isso seja exceção, e mais ainda que seja raro.
Já atendi dezenas de tópicos no GUJ com exatamente esse problema.

Se você tem um JFrame assim (como o Visual Editor sugeria):

[code]public class Menu {
private Janela1 janela1;

public void abrirJanela1() {
if (janela1 == null) {
janela1 = new Janela1();
janela1.setVisible(true);
}
}
}
[/code]

Você já terá que deixar janela1 null após o dispose(). É ainda mais comum no caso de ser um JInternalPane, uma vez que ele é associado a um DesktopPane. O dispose() não remove a janela do DesktopPane e, portanto, uma referência como a acima se faz necessária. É também necessária uma atitude explícita do programador.

A nunca usada continua sim. A nunca referenciada é que não. A única regra existente é que para uma variável ser removida da memória, ela não pode ser alcançável a partir de nenhuma thread. Se ela é alcançavel, aí ferrou.

Um caso clássico é um JInternalFrame invisível, sem que haja uma referência explícita para deixa-lo visível novamente. Caso o programador não se dê ao trabalho de procurar a referência que está dentro do JDesktopPane e remove-la, ela ficará lá, sem uso e invisível para sempre, ocupando memória.

Como já foi dito, ele não retira toda a janela da memória, só parte dela. Ele só desaloca os recursos da janela associados ao SO. Se ainda existirem referências para a janela, ela continua na memória. O dispose() não cria nenhum tipo de exceção no garbage collector.

Como a própria documentação que você linkou diz:

Note que ele está falando de “native resources”. Os recursos do Java ainda tem que existir na memória, até para ele saber como recriar recursos nativos caso um setVisible(true) seja novamente dado. Esses recursos são todas as variáveis, textos de labels, imagens, enfim, toda informação necessária para reconstruir a janela no SO. E isso nem sempre representa pouca memória.

Outros erros comuns que mantém a janela ativa:

  1. Ter componentes dentro dela que são listeners de coisas fora da janela;
  2. Ter variáveis estáticas apontando para a janela ou algum de seus componentes.

Esse link fala sobre essa história de setar variáveis para null. Fonte: Java theory and practice: Garbage collection and performance http://www.ibm.com/developerworks/java/library/j-jtp01274/index.html

[quote]Explicit nulling

Explicit nulling is simply the practice of setting reference objects to null when you are finished with them. The idea behind nulling is that it assists the garbage collector by making objects unreachable earlier. Or at least that’s the theory.
There is one case where the use of explicit nulling is not only helpful, but virtually required, and that is where a reference to an object is scoped more broadly than it is used or considered valid by the program’s specification. This includes cases such as using a static or instance field to store a reference to a temporary buffer, rather than a local variable, or using an array to store references that may remain reachable by the runtime but not by the implied semantics of the program. Consider the class in Listing 3, which is an implementation of a simple bounded stack backed by an array. When pop() is called, without the explicit nulling in the example, the class could cause a memory leak (more properly called “unintentional object retention,” or sometimes called “object loitering”) because the reference stored in stack[top+1] is no longer reachable by the program, but still considered reachable by the garbage collector.

Listing 3. Avoiding object loitering in a stack implementation

[code]public class SimpleBoundedStack {
private static final int MAXLEN = 100;
private Object stack[] = new Object[MAXLEN];
private int top = -1;

public void push(Object p) { stack [++top] = p;}

public Object pop() {
Object p = stack [top];
stack [top–] = null; // explicit null
return p;
}
}
[/code]

In the September 1997 “Java Developer Connection Tech Tips” column (see Resources), Sun warned of this risk and explained how explicit nulling was needed in cases like the pop() example above. Unfortunately, programmers often take this advice too far, using explicit nulling in the hope of helping the garbage collector. But in most cases, it doesn’t help the garbage collector at all, and in some cases, it can actually hurt your program’s performance.
[/quote]

Ceklock, como falei 3 vezes nesse tópico, não estou falando de definir variáveis que saem de escopo para null. Mas variáveis que se mantém no escopo, como no exemplo que citei anteriormente.

Definir variáveis que saem de escopo para null é desnecessário, pois essas variáveis deixarão de existir.

Aliás, o texto que você linkou, fala exatamente disso:

[quote=ViniGodoy]Ceklock, como falei 3 vezes nesse tópico, não estou falando de definir variáveis que saem de escopo para null. Mas variáveis que se mantém no escopo, como no exemplo que citei anteriormente.

Definir variáveis que saem de escopo para null é desnecessário, pois essas variáveis deixarão de existir.

Aliás, o texto que você linkou, fala exatamente disso:

Esse caso que eles citam é uma exceção.

"Unfortunately, programmers often take this advice too far, using explicit nulling in the hope of helping the garbage collector. But in most cases, it doesn’t help the garbage collector at all, and in some cases, it can actually hurt your program’s performance. "

É isso o que vejo por aqui.

Com certeza, mas não é do que eu estou falando! Já repeti diversas vezes que você deve deixar nulo variáveis que estejam em escopo. E, pela forma que janelas são construídas, isso não é exceção.

Basta ver o código que postei anteriormente, que é uma das formas comuns de se fazer interfaces gráficas.
É muito comum que a janela contendo o menu da aplicação fique ativa. Se ela tiver propriedades para as janelas que ela abre, essas janelas também estarão na memória, estejam elas visíveis ou não.

O ideal é não guardar essas janelas propriedades. Mas caso você tenha um JDesktopPane, você tem um problema!
O JDesktopPane vai guardar essas referências para você.