As part of the series of short articles by BellSoft, we will look into various exceptions, their root causes, and elimination.
Our first candidate is a specific issue causing multiple NullPointerExceptions (NPEs) in JavaFX applications, i.e., insufficient video memory.
Problem
A working JavaFX application starts throwing multiple NPEs but doesn’t fail.
For instance, take a look at these stack traces:
java.lang.NullPointerException: Cannot invoke "com.sun.prism.RTTexture.contentsUseful()" because "this.txt" is null
at javafx.web@19-internal/com.sun.javafx.webkit.prism.RTImage.getTexture(RTImage.java:99)
at javafx.web@19-internal/com.sun.javafx.webkit.prism.RTImage.getGraphics(RTImage.java:74)
at javafx.web@19-internal/com.sun.javafx.webkit.prism.WCBufferedContext.getGraphics(WCBufferedContext.java:65)
at javafx.web@19-internal/com.sun.javafx.webkit.prism.WCGraphicsPrismContext.getPlatformGraphics(WCGraphicsPrismContext.java:127)
at javafx.web@19-internal/com.sun.javafx.webkit.prism.WCGraphicsPrismContext.isValid(WCGraphicsPrismContext.java:132)
at javafx.web@19-internal/com.sun.webkit.graphics.WCRenderQueue.decode(WCRenderQueue.java:107)
at javafx.web@19-internal/com.sun.javafx.webkit.prism.WCRenderQueueImpl.lambda$flush$0(WCRenderQueueImpl.java:46)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
at javafx.graphics@19-internal/com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at javafx.graphics@19-internal/com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:126)
at java.base/java.lang.Thread.run(Thread.java:833)
and
java.lang.NullPointerException: Cannot invoke "com.sun.prism.RTTexture.makePermanent()" because "rtt" is null
at javafx.web@19-internal/com.sun.javafx.webkit.prism.WCGraphicsPrismContext$Layer.<init>(WCGraphicsPrismContext.java:1370)
at javafx.web@19-internal/com.sun.javafx.webkit.prism.WCGraphicsPrismContext$ClipLayer.<init>(WCGraphicsPrismContext.java:1449)
at javafx.web@19-internal/com.sun.javafx.webkit.prism.WCGraphicsPrismContext.setClip(WCGraphicsPrismContext.java:344)
at javafx.web@19-internal/com.sun.webkit.graphics.GraphicsDecoder.decode(GraphicsDecoder.java:230)
at javafx.web@19-internal/com.sun.webkit.graphics.WCRenderQueue.decode(WCRenderQueue.java:97)
at javafx.web@19-internal/com.sun.webkit.graphics.WCRenderQueue.decode(WCRenderQueue.java:111)
at javafx.web@19-internal/com.sun.javafx.webkit.prism.WCRenderQueueImpl.lambda$flush$0(WCRenderQueueImpl.java:46)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
at javafx.graphics@19-internal/com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at javafx.graphics@19-internal/com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:126)
at java.base/java.lang.Thread.run(Thread.java:833)
Root cause
At first glance it may be difficult to understand that the problem is caused by video memory because NPE is a versatile exception. But there’s one distinctive feature, namely textures mentioned in the message (Texture, RTTexture). It means that NPEs are caused by insufficient video memory like in the cases above. The application continues functioning and allocating objects as the heap is big enough, but there are no more resources for textures. Such objects don’t get initialized, and so when the app tries to access them at runtime, we get NullPointerExceptions.
Solution
The solution is quite simple, i.e., you need to allocate more VRAM to the application. VRAM or video RAM is a memory type used for storing the data, which is processed by the GPU to render images on display. How to check the amount of VRAM available on your machine?
- For Windows 10+ users. Go to Settings > System > Display. Select Advanced Display, then click on Display Adapter Properties. You will find the available VRAM under the Dedicated Video Memory.
- For Mac users. Check the Graphics/Displays box under System information.
By default, the allocated max video memory in JVM is 512MB. But you can customize the JVM settings with the -Dprism.maxvram parameter by using a command-line flag
java -Dprism.maxvram=2G -jar yourApp.jar
Just don’t exceed the physical video RAM size, or else by solving one issue you’ll get a bunch of new ones. For instance, if you set the target VRAM value too high, it may overpower the system and cause the application to shutdown.
Furthermore, consider using integrated graphics cards. They are extremely convenient as they keep the textures in the standard memory, where you can set the value for the allocated memory pool as high as you need.
If the VRAM volume is still not enough, you can’t do much to increase it except for upgrading the graphics card because VRAM is an integral part of the GPU. Alternatively, use CPU rendering, but it is not nearly as good as GPU rendering because CPU is not specialized for graphics processing.