Im an amateur programming ambitious to learn, but I've encountered a new type of problem that I don't even know where to begin to look - memory leaks in java. I've searched around and can't really find anything that would help me. I used Tomcat v9.0 and Java 1.8. I don't even know what code you need to see in order to help.
I get this warning when im trying to send a request to my REST api
VARNING: The web application [School] appears to have started a thread named [pool-2-thread-1] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.parkNanos(Unknown Source)
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(Unknown Source)
java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
java.lang.Thread.run(Unknown Source)
The server can work for one or two requests then it just stops. Since im new to this type of problem I have no idea what might cause it, and searching around didn't really help me in my amateurish ways. I'm guessing however that im creating threads in some way but they're not being closed.
The controller i tried to reach with a get method
package se.consys.controllers;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.persistence.NoResultException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.PATCH;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import se.consys.Entities.Course;
import se.consys.Entities.Lecture;
import se.consys.Entities.Student;
import se.consys.Entities.Teacher;
import se.consys.Utilities.HibernateUtility;
import se.consys.dataaccess.DaoGenericHibernateImpl;
import se.consys.params.LocalDateParam;
import se.consys.params.LocalDateTimeParam;
import se.consys.params.MapHelper;
import se.consys.services.GenericService;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
@SuppressWarnings("rawtypes, unchecked")
@Path("courses")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class CourseController {
private GenericService courseService = GenericService.getGenericService(new DaoGenericHibernateImpl<>(Course.class));
private GenericService teacherService = GenericService.getGenericService(new DaoGenericHibernateImpl<>(Teacher.class));
private GenericService studentService = GenericService.getGenericService(new DaoGenericHibernateImpl<>(Student.class));
private String noCourseFoundMsg = "No course found with the specified id.";
@GET
public Response getAll() {
List<Course> courses = courseService.findAll();
return Response.status(200).build();
}
@GET
@Path("/{id}")
public Response getById(@PathParam("id") int id) {
try {
Course course = (Course) courseService.findById(id);
return Response.ok().entity(course).build();
} catch (NoResultException e) {
System.out.println(noCourseFoundMsg);
return Response.status(204).build();
}
}
@SuppressWarnings("unchecked")
@POST
public Response create(Course entity) {
courseService.create(entity);
return Response.status(201).entity(entity).build();
}
@PATCH
@Path("/{id}")
public Response partialUpdate(@DefaultValue("0") @PathParam("id") int id,
@DefaultValue("null") @QueryParam("name") String courseName,
@DefaultValue("-1") @QueryParam("duration") int durationInMonths,
@DefaultValue("") @QueryParam("end") LocalDateParam endDate,
@DefaultValue("") @QueryParam("start") LocalDateParam startDate,
@DefaultValue("") @QueryParam("timestamp") LocalDateTimeParam timeStamp,
@DefaultValue("-1") @QueryParam("supervisor") int supervisor)
{
Course courseToBeUpdated = (Course) courseService.findById(id);
System.out.println(courseName);
if (courseName != null) courseToBeUpdated.setCourseName(courseName);
if (durationInMonths != -1) courseToBeUpdated.setDurationInMonths(durationInMonths);
if (endDate != null && !endDate.getLocalDate().equals(LocalDate.MIN)) courseToBeUpdated.setEndDate(endDate.getLocalDate());
if (startDate != null && !startDate.getLocalDate().equals(LocalDate.MIN)) courseToBeUpdated.setStartDate(startDate.getLocalDate());
if (timeStamp != null && !timeStamp.getLocalDateTime().equals(LocalDateTime.MIN)) courseToBeUpdated.setTimeStamp(timeStamp.getLocalDateTime());
if (supervisor != -1) courseToBeUpdated.setSupervisor((Teacher) teacherService.findById(supervisor));
courseService.update(courseToBeUpdated);
return Response.status(200).build();
}
@PATCH
@Path("/{id}/students")
public Response partialUpdateOnStudents(
@DefaultValue("0") @PathParam("id") int id,
@DefaultValue("null") @QueryParam("update") String studentString) {
String[] seperatedIds = studentString.split("-");
List<Integer> studentIds = new ArrayList<Integer>();
for (int i = 0; i < seperatedIds.length; i++) {
studentIds.add((int) Integer.parseInt(seperatedIds[i]));
}
List<Student> allStudents = studentService.findAll();
List<Student> StudentsToAddIntoCourse = new ArrayList<Student>();
for (int i = 0; i < allStudents.size(); i++) {
for(int j = 0; j < studentIds.size(); j++) {
if (allStudents.get(i).getId() == studentIds.get(j)) {
StudentsToAddIntoCourse.add(allStudents.get(i));
}
}
}
Course courseToBeUpdated = (Course) courseService.findById(id);
if (studentString != null) courseToBeUpdated.setStudents(StudentsToAddIntoCourse);
courseService.update(courseToBeUpdated);
return Response.status(200).build();
}
@PUT
@Path("/{id}")
public Response update(@DefaultValue("0") @PathParam("id") int id, Course entity) {
try {
Course courseToBeUpdated = (Course) courseService.findById(id);
courseToBeUpdated.setCourseName(entity.getCourseName());
courseToBeUpdated.setDurationInMonths(entity.getDurationInMonths());
courseToBeUpdated.setEndDate(entity.getEndDate());
courseToBeUpdated.setScheduledLectures(entity.getScheduledLectures());
courseToBeUpdated.setStartDate(entity.getStartDate());
courseToBeUpdated.setStudents(entity.getStudents());
courseToBeUpdated.setSupervisor(entity.getSupervisor());
courseToBeUpdated.setTimeStamp(entity.getTimeStamp());
courseService.update(courseToBeUpdated);
return Response.status(200).entity(entity).build();
} catch (NoResultException e) {
System.out.println(noCourseFoundMsg);
return Response.ok().status(204).build();
}
}
@DELETE
@Path("/{id}")
public Response delete(@DefaultValue("0") @PathParam("id") int id) {
try {
Course courseToBeDeleted = (Course) courseService.findById(id);
courseService.delete(courseToBeDeleted);
return Response.status(200).build();
} catch (NoResultException e) {
System.out.println(noCourseFoundMsg);
return Response.status(204).build();
}
}
}
I have a suspicion that the problem actually is my generic dao and dao service that probably is completely wrong but works on paper. Im new to generics and managed to throw something together.
DaoGenericHibernateImpl
package se.consys.dataaccess;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import org.hibernate.Session;
import se.consys.Utilities.Helper;
import se.consys.Utilities.HibernateUtility;
import se.consys.services.GenericService;
public class DaoGenericHibernateImpl<T extends Serializable> implements IGenericDao<T> {
Session session = HibernateUtility.getSessionFactory().openSession();
private String activeClassName;
private String wrongClassError = "ERROR: Wrong class used on the established service.";
public DaoGenericHibernateImpl(Class<T> type) {
activeClassName = type.getSimpleName();
}
@Override
public void create(T entity) {
if (entity.getClass().getSimpleName().equals(activeClassName)) {
session.beginTransaction();
session.persist(entity);
session.getTransaction().commit();
} else {
System.out.println(wrongClassError + " Entity has not been saved to the database. "
+ "Class used: " + entity.getClass().getSimpleName() + ". "
+ "Class expected: " + activeClassName + ".");
}
}
@Override
public T update(T entity) {
if (entity.getClass().getSimpleName().equals(activeClassName)) {
session.beginTransaction();
session.merge(entity);
session.getTransaction().commit();
return entity;
} else {
System.out.println(wrongClassError + " Entity has not been updated. "
+ "Class used: " + entity.getClass().getSimpleName() + ". "
+ "Class expected: " + activeClassName + ".");
}
return entity;
}
@Override
public void delete(T entity) {
if (entity.getClass().getSimpleName().equals(activeClassName)) {
session.beginTransaction();
//session.update(entity);
session.delete(entity);
session.getTransaction().commit();
} else {
System.out.println(wrongClassError + " Entity has not been deleted. "
+ "Class used: " + entity.getClass().getSimpleName() + ". "
+ "Class expected: " + activeClassName + ".");
}
}
@Override
public T findById(int id) {
final String HQL_BY_ID = "FROM " + activeClassName + " WHERE id=:id";
@SuppressWarnings("unchecked")
T result = (T) session.createQuery(HQL_BY_ID)
.setParameter("id", id)
.setMaxResults(1)
.getSingleResult();
return result;
}
@Override
public List<T> findAll() {
String HQL_FIND_ALL = "FROM " + activeClassName;
@SuppressWarnings("unchecked")
List<T> result = (List<T>) session.createQuery(HQL_FIND_ALL)
.getResultList();
return result;
}
@Override
public void removeReference(T entity, Class<?> reference) {
Method setter = Helper.findSetter(entity, reference);
try {
setter.invoke(entity, null);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e.getMessage());
}
}
}
I feel like i can just keep adding codeblocks so ill add a link to my github where everything is, hope that is ok.
Thanks for the help.