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

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ê.

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.[/quote]

Por que terá que deixar janela1 null?

Nesse caso que você citou acima quando chamar abrirJanela1() a próxima janela vai automaticamente ocupar o lugar da anterior (a variável janela1 vai receber um novo objeto), e a anterior vai perder referencia, desde que vc chame dispose() ao fechar a janela (use setDefaultCloseOperation(DISPOSE_ON_CLOSE) ).

E um JInternalFrame não é uma janela de verdade, é um componente leve. E pra que ficar setando janelas como null? O melhor é deixar só invisivel mesmo, e reutilizar depois. Afinal, é uma interface gráfica. Tu só vai se livrar de janelas se for estritamente necessário.

No fim tem que cuidar do que realmente é um gargalo pra memória e que poderia gerar um memory leak.

[quote=ceklock]Por que terá que deixar janela1 null?

Nesse caso que você citou acima quando chamar abrirJanela1() a próxima janela vai automaticamente ocupar o lugar da anterior (a variável janela1 vai receber um novo objeto), e a anterior vai perder referencia, desde que vc chame dispose() ao fechar a janela (use setDefaultCloseOperation(DISPOSE_ON_CLOSE) ). [/quote]

Você deve setar como null porque aquela variável não sai do escopo. Se o menu tiver 50 janelas, e cada uma for aberta uma vez, haverá 50 janelas na memória ao final do processo, mesmo que todas tenham sido fechadas!

De qualquer forma, com o que você falou, só vai haver a coleta da memória da janela anterior, quando a nova for aberta. Ou seja, no sistema, haverá pelo menos uma janela daquele tipo ocupando memória, a partir do momento que ela for aberta a primeira vez.

E estamos falando exatamente sobre se certificar que essa memória foi coletada!

Eu até concordo que, no geral, você pode usar a memória dessa forma relaxada, pois janelas tipicamente não ocupam tanta memória assim. Mas não é disso que o tópico se trata. Estamos falando no escopo do dispose() e em como garantir que toda a memória da janela foi desocupada.

[quote]E um JInternalFrame não é uma janela de verdade, é um componente leve. E pra que ficar setando janelas como null? O melhor é deixar só invisivel mesmo, e reutilizar depois. Afinal, é uma interface gráfica. Tu só vai se livrar de janelas se for estritamente necessário.
[/quote]

Se ele é leve ou não depende dos componentes que tem dentro. No post do problema que citei, o usuário abria uma imagem, um recurso grande e pesado. O frame invisível continuará com a imagem lá. Se a janela carregou em suas variáveis internas vídeos, música ou outros recursos pesados, esses recursos vão continuar lá, ocupando memória junto.

Por isso é importante saber quando e como janelas são realmente limpas, e onde é o papel do dispose, onde é o do gc() e como garantir que os dois rodem adequadamente.

Verdade. Mas é bom saber que o que fazer caso você perceba que o leak é nas janelas fechadas. :slight_smile:

[quote=ViniGodoy]Você deve setar como null porque aquela variável não sai do escopo. Se o menu tiver 50 janelas, e cada uma for aberta uma vez, haverá 50 janelas na memória ao final do processo, mesmo que todas tenham sido fechadas!

De qualquer forma, com o que você falou, só vai haver a coleta da memória da janela anterior, quando a nova for aberta. Ou seja, no sistema, haverá pelo menos uma janela daquele tipo ocupando memória, a partir do momento que ela for aberta a primeira vez. [/quote]

Tá bom. Não vou ficar discutindo. Eu particularmente prefiro sites como o http://www.stackoverflow.com onde tem mais moderação. Ao invés de ficar discutindo devíamos mostrar mais referências/fontes e fazer testes usando profilers.

E sobre componentes leves e pesados, é esse o significado:

“A heavyweight component is one that is associated with its own native screen resource (commonly known as a peer). A lightweight component is one that “borrows” the screen resource of an ancestor (which means it has no native resource of its own – so it’s “lighter”).” - http://java.sun.com/products/jfc/tsc/articles/mixing/

Não percebi que você falou em “componente leve” se referindo a lightweight. Até porque, todos os componentes do Swing são lightweight, e não só o JInternalFrame:
http://docs.oracle.com/javase/1.4.2/docs/api/javax/swing/package-summary.html

Não entendi o que você quis dizer com o stackoverflow ter mais moderação. Nossa discussão foi estritamente técnica, e encontra-se discussões desse tipo por lá também. Eu não me senti ofendido, nem irritado, pelos seus comentários, e nem “trocamos elogios”.

De fato, eu concordo com muitos dos seus argumentos, como o fato de não ficar nulificando variáveis locais, e não ficar otimizando sem um profiler. Inclusive, já dei as mesmas dicas aqui no GUJ, para bastante gente. E sou um grande fã dos artigos do Goetz. :slight_smile:

Entretanto, eu só estava deixando claro como funciona o mecanismo de liberação das janelas, sem entrar no mérito de se isso seria ou não uma boa prática sempre. É um erro muito comum acreditar que o dispose() libera toda a memória ocupada por uma janela, mesmo aquela pertencente à VM (como se ele magicamente ampliasse as capacidades do coletor de lixo).

[quote=ViniGodoy]Não percebi que você falou em “componente leve” se referindo a lightweight. Até porque, todos os componentes do Swing são lightweight, e não só o JInternalFrame:
http://docs.oracle.com/javase/1.4.2/docs/api/javax/swing/package-summary.html

Não entendi o que você quis dizer com o stackoverflow ter mais moderação. Nossa discussão foi estritamente técnica, e encontra-se discussões desse tipo por lá também. Eu não me senti ofendido, nem irritado, pelos seus comentários, e nem “trocamos elogios”.

De fato, eu concordo com muitos dos seus argumentos, como o fato de não ficar nulificando variáveis locais, e não ficar otimizando sem um profiler. Inclusive, já dei as mesmas dicas aqui no GUJ, para bastante gente. E sou um grande fã dos artigos do Goetz. :slight_smile:

Entretanto, eu só estava deixando claro como funciona o mecanismo de liberação das janelas, sem entrar no mérito de se isso seria ou não uma boa prática sempre. É um erro muito comum acreditar que o dispose() libera toda a memória ocupada por uma janela, mesmo aquela pertencente à VM (como se ele magicamente ampliasse as capacidades do coletor de lixo). [/quote]

O que eu quero dizer é que aqui as discussões acabam ficando muito longas.

[quote=ViniGodoy]Não percebi que você falou em “componente leve” se referindo a lightweight. Até porque, todos os componentes do Swing são lightweight, e não só o JInternalFrame:
http://docs.oracle.com/javase/1.4.2/docs/api/javax/swing/package-summary.html[/quote]

Não. Não são todos os componentes do Swing que são lightweight.

[quote]The only heavyweight components used in Swing are:

swing.JFrame
swing.JDialog
swing.JWindow
swing.JApplet
All AWT components (awt.*), except those noted below.
[/quote]

fonte: http://java.sun.com/products/jfc/tsc/articles/containers/#heavyweights_and