There's several layers of issues in the code above.
1) The pattern you're using to just sleep the remainder of the frame has an issue: each sleep is not simply the remaining delta: you've already called "render" in the current loop, taking X milliseconds, and then you "sleep to the beignning of the next frame", which takes (16 - X) milliseconds, and then you render and "sleep to the beginning of the 3rd frame", which takes (16 - (16 - X)) milliseconds, etc. This means you are effectively rendering twice per frame. This also means that for a fast render, X alternates between <1 milliseconds and about 16 milliseconds.
2) To fix the time delta accounting bouncing around, you can make a small adjustment and avoid calculating the sleep relative to the previous frame's time: instead of
sleep(targetTime - delta), which is a measure of "how close the current frame follows the previous frame", you could use
sleep(startTime + targetTime - System.currentTimeMillis()) which is a measure of "how long until the desired next frame from the current frame."
3) There's still a few issues with running your game logic (
tick) at a variable frame rate based on the rendering speed, including drift over time due to the imprecision of Thread.sleep, and unstable behavior due to variable time steps, things can feel "higher friction" or glitch through/blow up when frame rate is too high or too low. You'll probably want to switch to a fixed-timestep loop (there are many resources on the internet, one popular one is at https://gafferongames.com/post/fix_your_timestep/) to get more stable simulation behavior.