Properly releasing memory to avoid memory pileup/crash
Hi, I'm new to java, but I am working on the startings of a speed reading application... The application currently inputs a text file, displays it in a jtextpane, highlights the first sentence letter by letter in a for loop until a period is found, then truncates that sentence from the jtextpane, finally starting the highlighting process over with the next sentence as the new first sentence of the jtextpane. Every time the application restarts highlighting a sentence it paints the background of the jtextpane with an image (successively painting a new image from a list of images stored in a folder on my hard drive).
So you kind of get an automated reading experience, that keeps highlighting and chopping off a sentence, to avoid the need for scrolling through the jtextpane, all the while repainting images behind the text in the jtextpane.
Apparently I don't have a clue what I'm doing when it comes to allowing java to release either the graphics cache, or the image cache, or perhaps the jtextpane paint cache, because every time the application repaints the jtextpane with a new image, the memory usage goes up and the last image cache is never released from memory.
The only image related sections of the code are: the public static declarations of (graphics g1, image img);
Code :
27. public static Image img;
28.
29. public static Graphics g1;
The overridden: protected void paintComponent(Graphics g);
Code :
176. private static class MyTextPane extends JTextPane
177. {
178. public MyTextPane()
179. {
180. super();
181. setText("Hello World");
182. setOpaque(false);
183. setBackground(new Color(0,0,0,0));
184. }
185.
186. @Override
187. protected void paintComponent(Graphics g)
188. {
189. if(g1 == null)
190. {
191. g1 = g;
192. }
193. img = null;
194. img = Toolkit.getDefaultToolkit().getImage("C:\\images\\" + i1 + ".jpg");
195. g.drawImage(img, 0, 0, this);
196. super.paintComponent(g);
197. }
198. }
and a section in the main that makes intermittent calls to the overridden paintComponent method and increments the image file name place holder
Code :
125. jtp.paintComponent(g1);
126. if(i1 < 600)
127. i1++;
128. else
129. i1 = 100;
The entire 200 lines of code
Code :
1. import java.net.MalformedURLException;
2. import java.net.URL;
3. import java.awt.Color;
4. import java.awt.Dimension;
5. import java.awt.Graphics;
6. import java.awt.Image;
7. import java.awt.Toolkit;
8. import java.awt.image.BufferedImage;
9. import java.io.File;
10. import java.io.IOException;
11. import javax.imageio.ImageIO;
12. import javax.swing.ImageIcon;
13. import javax.swing.JFileChooser;
14. import javax.swing.JFrame;
15. import javax.swing.JLabel;
16. import javax.swing.JPanel;
17. import javax.swing.JScrollPane;
18. import javax.swing.JTextPane;
19. import javax.swing.text.BadLocationException;
20. import javax.swing.text.DefaultHighlighter;
21.
22. import org.apache.commons.io.FileUtils;
23.
24.
25. public class storybook
26. {
27. public static Image img;
28.
29. public static Graphics g1;
30. public static int i1 = 100;
31.
32. public static void main( String args[] )
33. {
34.
35. JFileChooser fileChooser = new JFileChooser();
36.
37. // show open file dialog
38. int result = fileChooser.showOpenDialog( null );
39.
40. if ( result == JFileChooser.APPROVE_OPTION ) // user chose a file
41. {
42. URL mediaURL = null;
43.
44. try
45. {
46. // get the file as URL
47. mediaURL = fileChooser.getSelectedFile().toURI().toURL();
48. } // end try
49. catch ( MalformedURLException malformedURLException )
50. {
51. System.err.println( "Could not create URL for the file" );
52. } // end catch
53.
54. if ( mediaURL != null ) // only display if there is a valid URL
55. {
56. JLabel jl1 = new JLabel("Label");
57. FileUtils fu = new FileUtils();
58. File f = new File (mediaURL.getFile());
59. String t = new String();
60. try
61. {
62. t = FileUtils.readFileToString(f);
63. }
64. catch (IOException e)
65. {
66. // TODO Auto-generated catch block
67. e.printStackTrace();
68. }
69. JFrame jf = new JFrame("Text");
70. JPanel jp = new JPanel();
71. MyTextPane jtp = new MyTextPane();
72. MyTextPane jtp2 = new MyTextPane();
73. jtp2.setSize(1024,768);
74. jtp.setSize(1024,768);
75.
76. jtp.setText(t);
77. jtp2.setText(t);
78.
79. JScrollPane slider = new JScrollPane(jtp);
80. slider.setVisible(true);
81. slider.setPreferredSize(new Dimension(1024, 768));
82.
83. jp.add(slider);
84. JPanel jp1 = new JPanel();
85. BufferedImage myPicture = null;
86.
87. try
88. {
89. myPicture = ImageIO.read(new File("C:\\Lighthouse.jpg"));
90. }
91. catch (IOException e1)
92. {
93. // TODO Auto-generated catch block
94. e1.printStackTrace();
95. }
96.
97. jf.add(jp);
98. jf.setSize(1024,768);
99. jf.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
100. jf.setIconImage(Toolkit.getDefaultToolkit().getImage("C:\\Lighthouse.jpg"));
101.
102. jf.setVisible(true);
103.
104. char space;
105. String periodstring = ".";
106. char period = periodstring.charAt(0);
107. String spacestring = " ";
108. space = spacestring.charAt(0);
109. int nz = 0;
110. DefaultHighlighter.DefaultHighlightPainter highlightPainter = new DefaultHighlighter.DefaultHighlightPainter(Color.YELLOW);
111. t.replaceAll("\r", "");
112. t.replaceAll("\n", "");
113.
114. for(int n = 0; n <= jtp.getText().length(); n++)
115. {
116. int length = jtp.getDocument().getLength();
117.
118. try {
119.
120. if(jtp.getDocument().getText(0, length).toString().charAt(n) == period)
121. {
122.
123.
124.
125. jtp.paintComponent(g1);
126. if(i1 < 600)
127. i1++;
128. else
129. i1 = 100;
130.
131.
132.
133. try
134. {
135. jtp.getHighlighter().addHighlight(nz, n, highlightPainter);
136. }
137. catch (BadLocationException e)
138. {
139. // TODO Auto-generated catch block
140. e.printStackTrace();
141. }
142. nz = n + 1;
143. n++;
144. jtp.getHighlighter().removeAllHighlights();
145. length = jtp.getDocument().getLength();
146. String tempText = jtp.getDocument().getText(0, length).toString().substring(n,length);
147. jtp.setText(tempText);
148. n = 0;
149. }
150. else
151. {
152.
153.
154. try
155. {
156. jtp.getHighlighter().addHighlight(0, n, highlightPainter);
157. }
158. catch (BadLocationException e)
159. {
160. // TODO Auto-generated catch block
161. e.printStackTrace();
162. }
163.
164. }
165. }
166. catch (BadLocationException e) {
167. // TODO Auto-generated catch block
168. e.printStackTrace();
169. }
170. }
171. }
172. }
173.
174.
175. }
176. private static class MyTextPane extends JTextPane
177. {
178. public MyTextPane()
179. {
180. super();
181. setText("Hello World");
182. setOpaque(false);
183. setBackground(new Color(0,0,0,0));
184. }
185.
186. @Override
187. protected void paintComponent(Graphics g)
188. {
189. if(g1 == null)
190. {
191. g1 = g;
192. }
193. img = null;
194. img = Toolkit.getDefaultToolkit().getImage("C:\\images\\" + i1 + ".jpg");
195. g.drawImage(img, 0, 0, this);
196. super.paintComponent(g);
197. }
198. }
199. }
How should I go about altering this code to allow java to release image/graphic/paintComponent cache from memory once a new image is painted?
Re: Properly releasing memory to avoid memory pileup/crash
Quote:
the memory usage goes up and the last image cache is never released from memory
My recommendation: study the basics of java garbage collection - in short, worrying about memory is often futile. Objects aren't freed from memory immediately as the garbage collector will run when it needs to. If you truly feel there is a memory leak - for instance if an Exception is thrown because of it, which can happen if you hold onto references to object - then place your application through a profiler and study the object allocations. And please, when posting code, just post the code - those line numbers prevent anyone from trying to test your code (let alone try to read it)
Re: Properly releasing memory to avoid memory pileup/crash
I agree with copeg. Java was designed, in part, to free the programmer from having to perform various circus tricks like memory juggling or tightrope pointer arithmetic where one false step could spell disaster. Generally speaking the runtime will perform garbage collection, not the program as such. You only need worry if there is a problem (as demonstrated by the runtime performance for instance). And then you have to deal with what is an unusual circumstance by measuring and testing with a profiler.
That said, garbage will only be collected if it *is* garbage.
Code :
img = Toolkit.getDefaultToolkit().getImage("C:\\images\\" + i1 + ".jpg");
The API docs for getImage() say, in part "The underlying toolkit attempts to resolve multiple requests with the same filename to the same returned Image. Since the mechanism required to facilitate this sharing of Image objects may continue to hold onto images that are no longer in use for an indefinite period of time, developers are encouraged to implement their own caching of images by using the createImage variant wherever available".
Your code suggests there may be quite a number of these images (<500). If they are not intended to be reused the toolkit's caching mechanism would inappropriately hang on to unwanted data. If they are intended to be reused you should think about the advice that you implement your own caching strategy. (Elsewhere in the program you obtain images using ImageIO which also has a caching mechanism you should be aware of.)
I'm not saying getImage() will cause a memory problem. For one thing the specific caching mechanism isn't specified and, for another, I doubt Oracle would be silly enough to use one that caused memory problems. But it probably will cause memory usage to grow long after an image has ceased to play any role in the program. And advice given in the API docs should be followed - at least, in my limited experience, its proven to be a good idea to do so.
-----
Concerning the code as a whole, the class should be named Storybook in line with Java coding conventions.
You use static stuff in the code: variables (including the possibly problematic i2) and a class. I haven't read the code closely enough to see if this is wrong, so I'll stick with my prejudice that it very probably is wrong. main() must be static and mostly likely little else should be. Where exceptions exist (like static final "constants") you should have a clear idea why you are making them static: keeping the compiler quite when it alerts you to problems with a "static context", or "ease of access" from elsewhere in the code aren't sufficient reasons.
The main() method is far too long. Consider using multiple classes each representing, in code, one specific type of thing. A "specific type of thing" in programming terms is expressible in terms of the statement: "It's something that can ...". In other words it is defined in terms of its behaviour.
Oracle's Tutorial offers detailed examples and explanations about how to use the Swing gui API. Perhaps it would be worth consulting, and its patterns followed. Or others may be able to suggest resources that describe overall Swing usage without being quite as comprehensive as Oracle's Tutorial - because you often find yourself having to skip around a bit. In any case the use of a single "God class" which does everything isn't the right way to go.
-----
Sorry that my advice is more or less along the lines that you should step back from the program and look at overall class structure, Swing painting methods etc. But such it is: take it or leave it. Either way, good luck! It sounds like an interesting program.
Re: Properly releasing memory to avoid memory pileup/crash
Thank you for the advice. I will familiarize myself with profiling and test the code after I first make some improvements. In regards to the basics of garbage collection in java I do recall that it is automated and should typically free memory without any leaks occurring. However I have done a really good job at thwarting its ability with the code that I put together here, going on an all nighter.
When you say I should be breaking things down into subclasses, I get the feeling that this is the only real solution to the problem I'm having. With the current code, the application's memory usage explodes to 750mb before finally crashing... I was thinking I could test out my application idea by whipping up a quick big main with public variables, but clearly it is falling far short of being worth testing this way.
From what I remember reading on garbage collection in java other than it being automated, is that items stored in cache are typically marked for garbage collection when they meet a certain criteria: an example was something along the lines of datatypes that are declared in a method are marked for garbage collection when the method completes and returns a value, the completion and return being proof that the encapsulated datatypes have finished their primary and only objective.
Considering that I did not break down my code into subclasses and methods, I gave the environment little hope for collecting garbage intelligently, because the datatypes I declared never satisfy termination requirements from an enclosed type returning to a finished state.
With that said, and hopefully correct enough, I am now going to divide the important sections of my main into appropriated methods, and I will post my code when I have found success, or perhaps any interesting scuffles along the way.
Thanks again!
Re: Properly releasing memory to avoid memory pileup/crash
Just a quick note: As was mentioned, java was designed to free you from worry of garbage collecion. As such, if you are not getting that old memory collected, then you have not released all pointers to it or something like that. This suggests a problem in your code no matter how you reformat what you have. You have to be sure to set all pointers pointing at the old image to point at a new image or null for that memory to be garbage.
Re: Properly releasing memory to avoid memory pileup/crash
fickletrick,
you declare 2 static objects....Static object affects the memory management behavior of JVM (auto Garbage Collector) because static object is an instance itself.
Re: Properly releasing memory to avoid memory pileup/crash
One final point, which is a huge point that I overlooked given the improper formatting of your code but now have had time to look closer: there are many things wrong with how you are controlling your GUI. First, swing is single threaded. Meaning, do NOT try and call Swing methods from a different method (in this case the main method) unless you know what you are doing. Second, you are trying to call paintComponent, holding onto a reference to a Graphics object from the paintComponent method..this is a creative way to try and paint, but it is also incorrect. Third, you are loading an image from the paintComponent each time, this is not just inefficient but requires lots of system resources and references, and you do not know when paintComponent may be called. There are probably many more things that I won't get into.
Start from the basics - learn how to draw Java2D the correct way.
Trail: 2D Graphics (The Java™ Tutorials)