I'm having issues with hashmaps disappearing after a time. That is to say - when a method goes to access one of them, I get an NPE. If I run my "refresh" method to reload the hashmaps, everything works fine again. My gut tells me that garbage collection is sweeping these away because they go unused for hours, potentially even days, at a time.
This is how I initialize them:
public static HashMap<Integer, Location> explorerLocations = new HashMap<Integer, Location>();
public static HashMap<Integer, Integer> explorerDifficulties = new HashMap<Integer, Integer>();
public static HashMap<Integer, Integer> explorerRewards = new HashMap<Integer, Integer>();
public static LinkedHashMap<Location, Integer> explorerLocationsReversed = new LinkedHashMap<Location, Integer>();
public static HashMap<Integer, String> explorerIDs = new HashMap<Integer, String>();
public static HashMap<Integer, Integer> explorerPrereqs = new HashMap<Integer, Integer>();
and this is how I load them into memory:
public static void syncExplorerLocations() {
int locCount = 0;
// reset the hashmap
RunicParadise.explorerLocations.clear();
RunicParadise.explorerDifficulties.clear();
RunicParadise.explorerRewards.clear();
RunicParadise.explorerLocationsReversed.clear();
RunicParadise.explorerIDs.clear();
RunicParadise.explorerPrereqs.clear();
// retrieve updated Explorer data
final Plugin instance = RunicParadise.getInstance();
MySQL MySQL = new MySQL(instance, instance.getConfig().getString("dbHost"),
instance.getConfig().getString("dbPort"), instance.getConfig().getString("dbDatabase"),
instance.getConfig().getString("dbUser"), instance.getConfig().getString("dbPassword"));
try {
final Connection d = MySQL.openConnection();
Statement dStmt = d.createStatement();
ResultSet explorerLocData = dStmt.executeQuery(
"SELECT * FROM rp_ExplorerLocations WHERE Status != 'Disabled' ORDER BY `Order` ASC;");
// if (!playerData.first() && !playerData.next()) {
if (!explorerLocData.isBeforeFirst()) {
// No results
// do nothing
d.close();
return;
} else {
// results found!
while (explorerLocData.next()) {
String[] locParts = explorerLocData.getString("Location").split("[\\x2E]");
Location newLoc = new Location(Bukkit.getWorld(locParts[0]), Integer.parseInt(locParts[1]),
Integer.parseInt(locParts[2]), Integer.parseInt(locParts[3]));
RunicParadise.explorerLocations.put(explorerLocData.getInt("ID"), newLoc);
RunicParadise.explorerLocationsReversed.put(newLoc, explorerLocData.getInt("ID"));
RunicParadise.explorerDifficulties.put(explorerLocData.getInt("ID"),
explorerLocData.getInt("DifficultyRadius"));
RunicParadise.explorerRewards.put(explorerLocData.getInt("ID"),
explorerLocData.getInt("TokenReward"));
RunicParadise.explorerPrereqs.put(explorerLocData.getInt("ID"), explorerLocData.getInt("PreReq"));
RunicParadise.explorerIDs.put(explorerLocData.getInt("ID"),
explorerLocData.getString("LocationName"));
locCount++;
}
d.close();
Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"sc " + locCount + " explorer locs loaded into memory!");
}
} catch (SQLException z) {
Bukkit.getLogger().log(Level.SEVERE, "Failed map sync for explorer locs cuz " + z.getMessage());
}
}
So, to the experts here -- is my gut correct, and the lack of usage is causing garbage collection to eat these up? If so, what's the best way to ensure they stay put? All I can think of is periodically refreshing them but that sounds like a workaround.
Thanks
====
Adding additional info. Seriously, 3 downvotes?
[03:43:27] [ERROR]: null
org.bukkit.command.CommandException: Unhandled exception executing command 'explore' in plugin RunicParadise v0.1
at org.bukkit.command.PluginCommand.execute(PluginCommand.java:46) ~[spigot.jar:git-Spigot-f950f8e-1c07d5c]
at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:141) ~[spigot.jar:git-Spigot-f950f8e-1c07d5c]
at org.bukkit.craftbukkit.v1_11_R1.CraftServer.dispatchCommand(CraftServer.java:649) ~[spigot.jar:git-Spigot-f950f8e-1c07d5c]
at net.minecraft.server.v1_11_R1.PlayerConnection.handleCommand(PlayerConnection.java:1335) [spigot.jar:git-Spigot-f950f8e-1c07d5c]
at net.minecraft.server.v1_11_R1.PlayerConnection.a(PlayerConnection.java:1170) [spigot.jar:git-Spigot-f950f8e-1c07d5c]
at net.minecraft.server.v1_11_R1.PacketPlayInChat.a(PacketPlayInChat.java:45) [spigot.jar:git-Spigot-f950f8e-1c07d5c]
at net.minecraft.server.v1_11_R1.PacketPlayInChat.a(PacketPlayInChat.java:1) [spigot.jar:git-Spigot-f950f8e-1c07d5c]
at net.minecraft.server.v1_11_R1.PlayerConnectionUtils$1.run(SourceFile:13) [spigot.jar:git-Spigot-f950f8e-1c07d5c]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_66]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_66]
at net.minecraft.server.v1_11_R1.SystemUtils.a(SourceFile:46) [spigot.jar:git-Spigot-f950f8e-1c07d5c]
at net.minecraft.server.v1_11_R1.MinecraftServer.D(MinecraftServer.java:739) [spigot.jar:git-Spigot-f950f8e-1c07d5c]
at net.minecraft.server.v1_11_R1.DedicatedServer.D(DedicatedServer.java:399) [spigot.jar:git-Spigot-f950f8e-1c07d5c]
at net.minecraft.server.v1_11_R1.MinecraftServer.C(MinecraftServer.java:675) [spigot.jar:git-Spigot-f950f8e-1c07d5c]
at net.minecraft.server.v1_11_R1.MinecraftServer.run(MinecraftServer.java:574) [spigot.jar:git-Spigot-f950f8e-1c07d5c]
at java.lang.Thread.run(Thread.java:745) [?:1.8.0_66]
Caused by: java.lang.NullPointerException
at io.github.runelynx.runicparadise.Commands.searchExplorerLocation(Commands.java:3752) ~[?:?]
at io.github.runelynx.runicparadise.Commands.onCommand(Commands.java:264) ~[?:?]
at org.bukkit.command.PluginCommand.execute(PluginCommand.java:44) ~[spigot.jar:git-Spigot-f950f8e-1c07d5c]
... 15 more
...and the method and line it refers to [line 3752]....
public static Boolean searchExplorerLocation(Location loc, Player p) {
int targetID = 0;
int distance = -1;
Boolean noneFound = false;
int greenWarmthMultiplier = 2;
int yellowWarmthMultiplier = 4;
int redWarmthMultiplier = 6;
if (RunicParadise.explorerLocations.isEmpty()) {
// Just in case the map is empty -- load it up! This happened on
// 8/10/16 somehow. :-/
Commands.syncExplorerLocations();
}
for (Location l : RunicParadise.explorerLocations.values()) {
**[NPE THROWN HERE] if (l.getWorld().getName().equals(loc.getWorld().getName())) {**
// Make sure worlds match before taking distance
if ((distance != -1 && loc.distance(l) < distance) || (distance == -1)) {
// Compare with last distance. The idea is to only retain
// the closest distance loc via this loop.
distance = (int) loc.distance(l);
targetID = RunicParadise.explorerLocationsReversed.get(l);
}
}
}
if (targetID != 0) {
// A distance & loc were found, so let's run our logic.
int difficulty = RunicParadise.explorerDifficulties.get(targetID);
if (distance <= difficulty) {
// found location!
completePlayerExploration(targetID, p);
playNBS(p, "ZeldaSecret");
} else if (distance <= greenWarmthMultiplier * difficulty) {
// Green OK!
TitleAPI.sendFullTitle(p, 1, 2, 1, ChatColor.GREEN + "✸ ✸ ✸",
ChatColor.DARK_GREEN + "You are very close to a secret spot!");
p.sendMessage(ChatColor.DARK_GREEN + "You are very close to a secret spot!");
} else if (distance <= yellowWarmthMultiplier * difficulty) {
// Yellow OK!}
TitleAPI.sendFullTitle(p, 1, 2, 1, ChatColor.YELLOW + "✸ ✸ ✸",
ChatColor.GOLD + "You are kinda close to a secret spot!");
p.sendMessage(ChatColor.GOLD + "You are kinda close to a secret spot!");
} else if (distance <= redWarmthMultiplier * difficulty) {
// Red OK!
TitleAPI.sendFullTitle(p, 1, 2, 1, ChatColor.RED + "✸ ✸ ✸",
ChatColor.DARK_RED + "There is a secret spot in your general area!");
p.sendMessage(ChatColor.DARK_RED + "There is a secret spot in your general area!");
} else {
noneFound = true;
}
} else {
noneFound = true;
}
if (noneFound) {
TitleAPI.sendFullTitle(p, 1, 2, 1, ChatColor.AQUA + "✕ ✕ ✕",
ChatColor.DARK_AQUA + "There are no secret spots nearby!");
p.sendMessage(ChatColor.DARK_AQUA + "There are no secret spots nearby!");
}
return false;
}