Game running slower with many objects

Hello, Im making a simple asteroids 2d game, and Im having a problem when I have many objects in the game, for example when I destroy and asteroid, I create an explosion object(when the animation is over I remove the object from the linkedlist), the problem is whenever I destroy many asteroids in a short period of time, the game simply lags and slows down, at first I thought its because of the gameloop I used, which was a “free wheeling loop” with no fps control/cap, and whenever the FPS changed, the animation speed changes, so I changed the game loop to a fixed fps game loop, and the fps is now constant on 144 fps(I have 144 hz screen, so I’ve set the fps to 144), but even on 60 fps or higher than 144 fps, the game still lags even tho the FPS stays constant, I thought maybe adding more threads to the game would help, but Im not quite sure about it and what I should do, and I would like to get some assistance with that.

A little explanation about the code, I have the main game class, which controls the game loop, update method and rendering method, I have an abstract class which all the objects in the game are inherited from, and I have a class that handles all the objects in the game and are stored in a linkedlist, the class updates and renders all the objects(the actual method is running through the main game class ofc).

I’ll add some code so it will be easier to understand it.

This is the main game class with the game loop:

package com.asteroids.controller;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;

import com.asteroids.model.*;
import com.asteroids.view.*;

public class Game extends Canvas implements Runnable {

	private static final long serialVersionUID = -8921419424614180143L;
	public static final int WIDTH = 1152, HEIGHT = WIDTH / 8 * 5;

	private Thread thread;
	private boolean isRunning;
	LoadImages loadImages = new LoadImages();
	private Player player = new Player();
	private AllObjects objects;
	private KeyInput keyInput;
	private long delay = 80;
	private long currentTime = System.currentTimeMillis();
	private long expectedTime = currentTime + delay;
	public static BufferedImage test;
	private int fps = 0;

	public Game() {
		new Window(WIDTH, HEIGHT, "Asteroids!", this);
		objects = new AllObjects();
		objects.addObject(player);
		for (int i = 0; i < 100; i++) {
			objects.addObject(new Rock((int) (Math.random() * (Game.WIDTH - 64) + 1),
					(int) (Math.random() * (Game.HEIGHT - 64) + 1)));
		}
		keyInput = new KeyInput(player);
		this.addKeyListener(keyInput);
	}

	public void run() {

		int ups = 144;
		int fps = 144;

		long initialTime = System.nanoTime();
		final double timeU = 1000000000 / ups;
		final double timeF = 1000000000 / fps;
		double deltaU = 0, deltaF = 0;
		// int frames = 0, ticks = 0;
		long timer = System.currentTimeMillis();

		while (isRunning) {
			destroyBullets();
			destroyAsteroids();
			// used to set delay between every bullet(milliseconds)
			currentTime = System.currentTimeMillis();
			if (KeyInput.shoot && currentTime >= expectedTime) {

				// calculates the accurate position of the x,y on the "circumference" of the
				// player
				float matchedX = player.getX() + 1 + (float) ((player.getRadius() + 32) * Math.cos(player.getRadian()));
				float matchedY = player.getY() - 7 + (float) ((player.getRadius() + 32) * Math.sin(player.getRadian()));
				objects.addObject(new Bullet(matchedX, matchedY, player));
				expectedTime = currentTime + delay;
			}
			long currentTime = System.nanoTime();
			deltaU += (currentTime - initialTime) / timeU;
			deltaF += (currentTime - initialTime) / timeF;
			initialTime = currentTime;

			if (deltaU >= 1) {
				tick();
				deltaU--;
			}

			if (deltaF >= 1) {
				render();
				deltaF--;
			}

			if (System.currentTimeMillis() - timer > 1000) {
				this.fps = fps;
				System.out.println("FPS: " + fps);
				timer += 1000;
			}
		}
		
		stop();
		System.exit(1);

	}

	private void stop() {
		try {
			thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.exit(1);

	}

	private void render() {
		BufferStrategy bs = this.getBufferStrategy();
		if (bs == null) {
			this.createBufferStrategy(3);
			return;
		}

		Graphics g = bs.getDrawGraphics();
		g.drawImage(LoadImages.getbackground(), 0, 0, getWidth(), getHeight(), this);
		objects.render(g);
		player.render(g);
		g.setColor(Color.red);
		g.drawString("FPS: " + fps, 5, 10);
		g.dispose();
		bs.show();

	}

	private void tick() {
		player.tick();
		objects.tick();
	}

	// starting thread and game loop.
	public void start() {
		thread = new Thread(this);
		thread.start();
		isRunning = true;
	}

	// minimum and maximum possible position for object.
	public static float Bounds(float value, float min, float max) {
		if (value >= max) {
			return value = max;
		}
		if (value <= min) {
			return value = min;
		} else {
			return value;
		}

	}

	// detects collision between two objects
	public boolean collision(GameObject a, GameObject b) {
		return (b.getX() - a.getX() + 10) * (b.getX() - a.getX() + 10)
				+ (b.getY() - a.getY() + 10) * (b.getY() - a.getY() + 10) < (a.getRadius() + b.getRadius())
						* (a.getRadius() + b.getRadius());
	}

	// destroys bullets once they go out of the screen
	public void destroyBullets() {
		for (int i = 0; i < objects.getSize(); i++) {
			if (objects.get(i).getId() == ID.BULLET) {
				GameObject bullet = objects.get(i);
				if (bullet.getX() > Game.WIDTH || bullet.getX() < 0 || bullet.getY() > Game.HEIGHT
						|| bullet.getY() < 0) {
					objects.removeObject(bullet);
				}
			}
		}
	}

	// whenever a collision between an asteroid and a bullet occurs, the asteroid is
	// destroyed
	public void destroyAsteroids() {
		GameObject bullet = null;
		GameObject bigRock = null;
		for (int i = 0; i < objects.getSize(); i++) {
			if (objects.get(i).getId() == ID.BULLET) {
				bullet = (Bullet) objects.get(i);
				for (int q = 0; q < objects.getSize(); q++) {
					if (objects.get(q).getId() == ID.BIGROCK) {
						bigRock = objects.get(q);
						if (bullet != null && bigRock != null) {
							if (collision(bigRock, bullet)) {
								objects.addObject(new Explosion(bigRock.getX(), bigRock.getY(), objects));
								objects.removeObject(bigRock);
								objects.removeObject(bullet);
							}
						}
					}
				}
			}
		}
	}
}

This is the explosion class for example:

package com.asteroids.model;

import java.awt.Graphics;
import java.awt.Image;

import com.asteroids.controller.*;
import com.asteroids.view.LoadImages;


public class Explosion extends GameObject {

	private AllObjects objects;
	private Image explosion;
	private float frame = 0;
	private float animSpeed = 0.4f;
	private int frameCount = 48;

	public Explosion(float x, float y, AllObjects objects) {
		super(x, y, ID.EXPLOSION, 1);
		this.objects = objects;
	}

	public void render(Graphics g) {
		explosion(g);
	}

	public void explosion(Graphics g) {
		frame += animSpeed;
		if (frame > frameCount) {
			frame -= frameCount;
		}
		explosion = LoadImages.getExplosion().getSubimage((int) frame * 256, 0, 256, 256);
		g.drawImage(explosion, (int) x, (int) y, 110, 110, null);
		if (frame >= 47f) {
			objects.removeObject(this);
		}
	}

	public void tick() {
		
	}

	public void setAnimSpeed(float animSpeed) {
		this.animSpeed = animSpeed;
	}
}

I don’t want to add too much code, because I think it just makes it more “clumsy” and inapprehensible, but if more code is needed let me know and I’ll update the post. Thanks for the help!