0

EDIT1: 28/05/2014

I've tried to change the driver (mysql-connector-java-5.1.30-bin AND ojdbc7).

In both cases the memory leak in on it. For example (using Java VisualVM) using ojdbc and looking at the nearest GC i see:

oracle.djbc.driver.ClockSource$ThreadCachingBlockSource$BlockReleaser isnt "free" from contextClassLoader

Using Mysql i have the same issue (with sql driver)

ORIGINAL POST: Simply configuring spring and jpa, after restart application 3/4 times i have PermGen Out of Memory. Using Java VisualVM i see that every restart i have one more class loader (at the beginning i have 4 class loader, after restart i have 5 and so on). In my WEB-INB i have any lib. The only classes i've setup are this (any jsp and any class where i do logic, setup only spring + jpa).

Im using Tomcat8 + Java 7 SDK:

Bootstrap configuration

@SuppressWarnings("unused")
@Order(1)
public class FrameworkBootstrap implements WebApplicationInitializer
{

@Override
public void onStartup(ServletContext container)
        throws ServletException 
{
    container.getServletRegistration("default").addMapping("/resource/*");

    AnnotationConfigWebApplicationContext rootContext =
            new AnnotationConfigWebApplicationContext();
    rootContext.register(RootContextConfiguration.class);
    container.addListener(new ContextLoaderListener(rootContext));

    AnnotationConfigWebApplicationContext webContext =
            new AnnotationConfigWebApplicationContext();
    webContext.register(WebServletContextConfiguration.class);
    ServletRegistration.Dynamic dispatcher = container.addServlet(
            "springWebDispatcher", new DispatcherServlet(webContext)
    );
    dispatcher.setLoadOnStartup(1);
    dispatcher.addMapping("/");
}
}

Servlet configuration

@Configuration
@EnableWebMvc
@ComponentScan(
    basePackages = "it.dirimo.site",
    useDefaultFilters = false,
    includeFilters = @ComponentScan.Filter(WebController.class)
)
public class WebServletContextConfiguration extends WebMvcConfigurerAdapter
{
 @Inject ApplicationContext applicationContext;
 @Inject ObjectMapper objectMapper;
 @Inject Marshaller marshaller;
 @Inject Unmarshaller unmarshaller;
 @Inject SpringValidatorAdapter validator;

 @Override
    public void configureMessageConverters(
            List<HttpMessageConverter<?>> converters
    ) {
        converters.add(new ByteArrayHttpMessageConverter());
        converters.add(new StringHttpMessageConverter());
        converters.add(new FormHttpMessageConverter());
        converters.add(new SourceHttpMessageConverter<>());

        MarshallingHttpMessageConverter xmlConverter =
                new MarshallingHttpMessageConverter();
        xmlConverter.setSupportedMediaTypes(Arrays.asList(
                new MediaType("application", "xml"),
                new MediaType("text", "xml"),
                new MediaType("text", "json")
        ));
        xmlConverter.setMarshaller(this.marshaller);
        xmlConverter.setUnmarshaller(this.unmarshaller);
        converters.add(xmlConverter);

        MappingJackson2HttpMessageConverter jsonConverter =
                new MappingJackson2HttpMessageConverter();
        jsonConverter.setSupportedMediaTypes(Arrays.asList(
                new MediaType("application", "json"),
                new MediaType("text", "json")
        ));
        jsonConverter.setObjectMapper(this.objectMapper);
        converters.add(jsonConverter);
    }   

    @Override
    public void configureContentNegotiation(
            ContentNegotiationConfigurer configurer)
    {
        configurer.favorPathExtension(true).favorParameter(false)
                .parameterName("mediaType").ignoreAcceptHeader(false)
                .useJaf(false).defaultContentType(MediaType.APPLICATION_XML)
                .mediaType("xml", MediaType.APPLICATION_XML)
                .mediaType("json", MediaType.APPLICATION_JSON);
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers)
    {
        Sort defaultSort = new Sort(new Sort.Order(Sort.Direction.ASC, "id"));
        Pageable defaultPageable = new PageRequest(0, 10, defaultSort);

        SortHandlerMethodArgumentResolver sortResolver =
                new SortHandlerMethodArgumentResolver();
        sortResolver.setSortParameter("paging.sort");
        sortResolver.setFallbackSort(defaultSort);

        PageableHandlerMethodArgumentResolver pageableResolver =
                new PageableHandlerMethodArgumentResolver(sortResolver);
        pageableResolver.setMaxPageSize(100);
        pageableResolver.setOneIndexedParameters(true);
        pageableResolver.setPrefix("paging.");
        pageableResolver.setFallbackPageable(defaultPageable);

        resolvers.add(sortResolver);
        resolvers.add(pageableResolver);
    }

    @Override
    public void addFormatters(FormatterRegistry registry)
    {
        if(!(registry instanceof FormattingConversionService))
        {
            return;
        }

        DomainClassConverter<FormattingConversionService> converter =
                new DomainClassConverter<>((FormattingConversionService)registry);
        converter.setApplicationContext(this.applicationContext);
    }

    @Override
    public Validator getValidator()
    {
        return this.validator;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
        super.addInterceptors(registry);

        registry.addInterceptor(new LocaleChangeInterceptor());
    }

    @Bean
    public LocaleResolver localeResolver()
    {
        return new SessionLocaleResolver();
    }

    @Bean
    public ViewResolver viewResolver()
    {
        InternalResourceViewResolver resolver =
                new InternalResourceViewResolver();
        resolver.setViewClass(JstlView.class);
        resolver.setPrefix("/WEB-INF/jsp/view/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

    @Bean
    public RequestToViewNameTranslator viewNameTranslator()
    {
        return new DefaultRequestToViewNameTranslator();
    }
}

Root configuration

@Configuration
@EnableScheduling
@EnableLoadTimeWeaving
@EnableAsync(
    mode = AdviceMode.PROXY, proxyTargetClass = true,
    order = 1
)
@EnableTransactionManagement(
    mode = AdviceMode.PROXY, proxyTargetClass = true,
    order = 2
)
@EnableJpaRepositories(
    basePackages = "it.dirimo.site.repositories",
    entityManagerFactoryRef = "entityManagerFactoryBean",
    transactionManagerRef = "jpaTransactionManager"
)
@ComponentScan(
    basePackages = "it.dirimo.site",
    excludeFilters =
    @ComponentScan.Filter({Controller.class, ControllerAdvice.class})
)
public class RootContextConfiguration implements AsyncConfigurer, SchedulingConfigurer
{
//private static final Logger log = LogManager.getLogger();

@Inject LoadTimeWeaver loadTimeWeaver;

@Bean
public MessageSource messageSource()
{
    ReloadableResourceBundleMessageSource messageSource =
            new ReloadableResourceBundleMessageSource();
    messageSource.setCacheSeconds(-1);
    messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
    messageSource.setBasenames(
            "/WEB-INF/i18n/titles", "/WEB-INF/i18n/messages",
            "/WEB-INF/i18n/errors", "/WEB-INF/i18n/validation"
    );
    return messageSource;
}

@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean()
{
    LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
    validator.setValidationMessageSource(this.messageSource());
    return validator;
}

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor()
{
    MethodValidationPostProcessor processor =
            new MethodValidationPostProcessor();
    processor.setValidator(this.localValidatorFactoryBean());
    return processor;
}

@Bean
public ObjectMapper objectMapper()
{
    ObjectMapper mapper = new ObjectMapper();
    mapper.findAndRegisterModules();
    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    mapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE,
            false);
    return mapper;
}

@Bean
public Jaxb2Marshaller jaxb2Marshaller()
{
    Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    marshaller.setPackagesToScan(new String[] { "it.dirimo.site" });
    return marshaller;
}

@Bean
public DataSource fileSearchDataSource()
{
    JndiDataSourceLookup lookup = new JndiDataSourceLookup();
    return lookup.getDataSource("jdbc/DIR");
}

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean()
{
    Map<String, Object> properties = new Hashtable<>();
    properties.put("javax.persistence.schema-generation.database.action",
            "none");
    properties.put("hibernate.ejb.use_class_enhancer", "true");

    HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
    adapter.setDatabasePlatform("org.hibernate.dialect.Oracle10gDialect");

    LocalContainerEntityManagerFactoryBean factory =
            new LocalContainerEntityManagerFactoryBean();
    factory.setJpaVendorAdapter(adapter);
    factory.setDataSource(this.fileSearchDataSource());
    factory.setPackagesToScan("it.dirimo.site.entities");
    factory.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE);
    factory.setValidationMode(ValidationMode.NONE);
    factory.setLoadTimeWeaver(this.loadTimeWeaver);
    factory.setJpaPropertyMap(properties);
    return factory;
}

@Bean
public PlatformTransactionManager jpaTransactionManager()
{
    return new JpaTransactionManager(
            this.entityManagerFactoryBean().getObject()
    );
}

@Bean
public ThreadPoolTaskScheduler taskScheduler()
{
    ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
    scheduler.setPoolSize(20);
    scheduler.setThreadNamePrefix("task-");
    scheduler.setAwaitTerminationSeconds(60);
    scheduler.setWaitForTasksToCompleteOnShutdown(true);
    return scheduler;
}

@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
    TaskScheduler scheduler = this.taskScheduler();
    registrar.setTaskScheduler(scheduler);
}

@Override
public Executor getAsyncExecutor() {
    Executor executor = this.taskScheduler();
    return executor;
}
}
Mistre83
  • 2,677
  • 6
  • 40
  • 77
  • Sounds like it might be a bug in Tomcat 8. – CodeChimp May 27 '14 at 14:40
  • I've published it on Tomcat 7 and removed @EnableLoadTimeWeaving. After startup (without any other application installesd). Perm Gen is approx 37 MB. After look it, i simply reload the application and Perm Gen go to 54.63. At the third reload Perm Gen is at 72 MB (as said, application is totally empty, no pages or services.) – Mistre83 May 27 '14 at 15:10
  • With Java VisualVM if i do "Show nearest GC" on the instance that isnt started i see: classLoader -> assertionLock (loop to this.. – Mistre83 May 27 '14 at 15:27

1 Answers1

2

This is a common problem with Tomcat/Spring/Hibernate, and there is often no definitive answer that applies to all scenarios. Most likely, the general underlying problem is that dynamic classes are getting created on every redeploy, stored in permgen, and the class definitions are never garbage collected. This often not directly a bug of Spring/Hibernate, but rather "features" of CGLIB and/or Tomcat's classloader handling.

Some common suggestions:

  • Add -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled to your tomcat startup options.

  • Move away from CGLIB if thats cause of problem. CGLIB is only needed for certain types of proxying.

  • Put your JDBC jar files in {tomcat}/lib, not WEB-INF/lib.

There are many other ideas here and on the internet, but generally, if you are unable to solve with the above, then the only recourse is to increase your permgen and plan on restarting tomcat every so many redeploys.

Anecdotally (and in my own experience), this problem is much less an issue with latest library/server/jdk versions.

Community
  • 1
  • 1
kaliatech
  • 17,579
  • 5
  • 72
  • 84
  • any of this method dont solve the problem :( But ok, i suppose that often i restart the application server. But, there are another problem. Every time i call a method that do a database query, the PermGen increase and never decrease. For every request (DB connection->search) i have an increase and never a decrease... this is normal.. ? – Mistre83 May 28 '14 at 19:00
  • Deploying it on Tomcat 8 + Java 8 and using the lastest library (with CMSClassUnloadingEnabled and mysql/ojdbc driver in tomcat/lib) do not solve the problem. – Mistre83 May 28 '14 at 20:08