Обнаружение утечки памяти в Java Java - PullRequest
0 голосов
/ 28 апреля 2018

Я любитель программирования, амбициозный в изучении, но я столкнулся с проблемой нового типа, которую даже не знаю, где начать искать - утечки памяти в Java. Я искал вокруг и не могу найти ничего, что могло бы мне помочь. Я использовал Tomcat v9.0 и Java 1.8. Я даже не знаю, какой код нужно увидеть, чтобы помочь.

Я получаю это предупреждение, когда я пытаюсь отправить запрос в мой 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)

Сервер может работать на один или два запроса, после чего он просто останавливается. Так как я новичок в этом типе проблемы, я понятия не имею, что могло бы вызвать это, и поиск вокруг действительно не помог мне в моих любительских отношениях. Однако я предполагаю, что я каким-то образом создаю потоки, но они не закрываются.

Контроллер, к которому я пытался обратиться методом get

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();
        } 
    }
}

У меня есть подозрение, что проблема на самом деле заключается в моем общем сервисе дао и дао, который, вероятно, совершенно неверен, но работает на бумаге. Я новичок в дженериках и смог бросить что-то вместе.

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());
        }
    }
}

Я чувствую, что могу просто продолжать добавлять кодовые блоки, поэтому плохо добавляю ссылку на мой github , где все есть, надеюсь, что все в порядке.

Спасибо за помощь.

1 Ответ

0 голосов
/ 29 апреля 2018

В общем, первое, о чем вам следует подумать в этом случае, это то, что, если вы не можете рассуждать о безопасности памяти и / или поточной безопасности вашего кода, то вам действительно нужно решить две проблемы.

С точки зрения того, что вы на самом деле видите, вам нужны стандартные инструменты для определения того, какие потоки работают и какая память используется. Вы можете узнать, какие потоки выполняются с помощью такого инструмента, как jconsole (GUI) или jstack (CLI). Они оба включены как стандартная часть JDK и дадут вам трассировку стека всех потоков, работающих в системе. Они не обязательно сообщат вам , почему эти потоки существуют, но сами трассировки стека могут помочь вам определить, откуда они берутся.

Проблема, которую вы описываете, звучит как бегущая нить, хотя она может также быть связана с памятью, и в сообщении об ошибке указывается, что это является вероятной основной причиной, поэтому также может быть полезно заглянуть в память использование. Для этого вы также можете использовать jconsole (раздел профиля) или jhat & jmap (если вы являетесь поклонником CLI). Любой из них скажет вам, какие объекты существуют в куче виртуальных машин и какого они типа. Эта информация может быть невероятно полезной, но и невероятно отвлекающей - в большинстве куч преобладают строки, карты и списки, потому что именно из этого построено большинство программ. Тем не менее, вы часто можете получить полезную информацию, сравнивая разницу между двумя профилями, один из которых взят, когда «все выглядит хорошо», и один, когда «он перестал работать».

Эти виды инструментов могут помочь вам определить проблему в работающей системе. Как только вы это сделаете, вы, вероятно, захотите посмотреть, почему из текста программы неясно, что будет с пулом потоков или проблемой использования памяти. Такие вещи, как использование карты в качестве кэша, очень важны. Или реализация equals / hashcode в терминах изменяемых полей. Что касается потоков, все, что не может прерваться, может довольно быстро заблокировать пул потоков, например, заблокировать ввод-вывод на веб-сервере.

...