-6

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;
    }
runelynx
  • 403
  • 2
  • 4
  • 17
  • 3
    Could you also share how do you use them? It will be better if you could write a small code to replicate the behavior, It will help you to understand the behavior. – Mangat Rai Modi Dec 12 '16 at 04:27
  • 6
    Assuming that your static maps belong to a class which remains loaded throughout the life of your program, then no, they should be not be getting garbage collected. What is perhaps more likely is that your own code is erasing something. – Tim Biegeleisen Dec 12 '16 at 04:28
  • 2
    As long as you have a variable somewhere that refers to your `HashMap` (or any other object), it won't get garbage collected. Garbage collection doesn't get rid of objects and change things that reference them to `null` (aside from "weak references", but if you're using those you would know it). So your gut is wrong. – ajb Dec 12 '16 at 04:42
  • I've added the error and code that generates the NPE. I'd appreciate if anyone could help remove the downvotes. Just because someone can't answer the question in 5 seconds to earn status doesn't mean I posted an awful question. – runelynx Dec 12 '16 at 04:56
  • Hmmm I see I also tried to make this fix itself by doing the isEmpty check just above the line that threw the error. Apparently that isn't fixing the problem (i'd imagine it's not being called - i.e. the hashmap isnt empty.). Because if I force that resync method it works fine from then on. – runelynx Dec 12 '16 at 04:59
  • 3
    Not a downvoter, but I assume the downvotes are because you (originally) asked an unanswerable question based on a fallacious presumption. – shmosel Dec 12 '16 at 05:21

1 Answers1

1
for (Location l : RunicParadise.explorerLocations.values()) {
        **[NPE THROWN HERE] if (l.getWorld().getName().equals(loc.getWorld().getName())) {**

So the one thing that wasn't null here was the HashMap: explorerLocations. Otherwise you would have got the NPE on the previous line where you called HashMap.values().

Any of l, l.getWorld(), l.getWorld().getName(), loc, or loc.getWorld() could be null.

You're barking up the wrong tree.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Thanks. Adding a null check for these 3 elements and some self-correction if they're found null. – runelynx Dec 12 '16 at 05:15
  • NB There is something seriously wrong with your design if you have a hashed data structure and you have to search it sequentially. – user207421 Dec 12 '16 at 05:27
  • How else would you go about it? I have to search for a hashed entry that holding location coordinates closest to the one in-hand... It's not possible to have any kind of key because I'm not looking for a specific entry. I'm trying to find if the user is near any of them. Vague negative comments are not very constructive, if you'd like some feedback yourself. – runelynx Dec 12 '16 at 15:13