In Java, is there a way to know that a StackOverflow
error or OutOfMemory
exception may happen soon?
The OutOfMemory
exception m
One useful thing you can do is use SoftReference
s for caches. That will give you a gradual performance slide as you run out of memory. Just don't use WeakReference
even in a WeakHashMap
because it will annoy me when your application dies nastily on me.
I don't know anything about working this out at run time, or what you might be able to do to avoid it once you predict it is going to happen. Better to try and avoid it occurring in the first place.
1) You could use Findbugs which may indicate some StackOverFlow errors occurring from inadvertently calling the same method from itself.
2) You could store data likely to cause you to run out of memory with a SoftReference and have a null check on accessing it so it can be reloaded if it's been garbage collected.
If either of these things are actually issues for you then the solution probably isn't in detecting it happening but architecting your solution differently to avoid them occuring if at all possible.
For StackOverflowError:
To know the current depth, usually it's either:
Knowing the depth it will occur is difficult. There are several factors:
Why not try it out using something like this?
public static void main(String[] args) {
try {
recurs();
} catch (Throwable t) {
// not a good idea in production code....
}
}
static int depth = 0;
static void recurs() {
System.out.println(depth++);
recurs();
}
Run it several times. Also try adding dummy variables. It can be seen that even the same code may halt at different depths and adding more variables cause it to end earlier. So yeah, pretty much it's unpredictable.
I suppose that besides rewriting the algorithm the only option would be to increase the stack space with the -Xss option.
For OutOfMemoryError, there's the -Xmx option
You should never see a StackOverflow
exception if your application is designed and implemented correctly!
Generally, if you get a StackOverflow
exception then it's a sign that there's a bug in your recursion code.
Implementation example using MemoryPoolMXBean
import static java.lang.management.ManagementFactory.getMemoryMXBean;
import static java.lang.management.ManagementFactory.getMemoryPoolMXBeans;
import static java.lang.management.MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED;
import static java.lang.management.MemoryType.HEAP;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MemoryWatcher {
public interface Listener {
void memoryUsageLow(long usedMemory, long maxMemory);
}
private static final MemoryPoolMXBean tenuredGenPool = findTenuredGenPool();
private static final Logger logger = LoggerFactory.getLogger(MemoryWatcher.class);
private static MemoryPoolMXBean findTenuredGenPool() {
for (MemoryPoolMXBean pool : getMemoryPoolMXBeans())
if (pool.getType() == HEAP && pool.isUsageThresholdSupported())
return pool;
return null;
}
public static MemoryPoolMXBean getTenuredGenPool() {
return tenuredGenPool;
}
private final Collection<Listener> listeners = new CopyOnWriteArrayList<>();
public MemoryWatcher(double usageThresholdPercent) {
if (tenuredGenPool == null) {
logger.warn("Tenured pool is not used");
return;
}
if (tenuredGenPool.getUsageThreshold() != 0)
logger.warn("Overriding tenured usage threshold {} with {}", tenuredGenPool.getUsage().getMax() / (double) tenuredGenPool.getUsageThreshold(), usageThresholdPercent);
tenuredGenPool.setUsageThreshold((long) (tenuredGenPool.getUsage().getMax() * usageThresholdPercent));
NotificationEmitter emitter = (NotificationEmitter) getMemoryMXBean();
emitter.addNotificationListener((Notification n, Object hb) -> {
MemoryUsage usage = tenuredGenPool.getUsage();
if (n.getType().equals(MEMORY_THRESHOLD_EXCEEDED) && usage.getMax() == usage.getCommitted())
listeners.forEach(listener -> listener.memoryUsageLow(usage.getUsed(), usage.getMax()));
}, null, null);
}
public boolean addListener(Listener listener) {
return listeners.add(listener);
}
public boolean removeListener(Listener listener) {
return listeners.remove(listener);
}
}