/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.kernel.memory; import com.liferay.portal.kernel.memory.FinalizeManager.ReferenceFactory; import com.liferay.portal.kernel.test.GCUtil; import com.liferay.portal.kernel.test.ReflectionTestUtil; import com.liferay.portal.kernel.test.rule.AggregateTestRule; import com.liferay.portal.kernel.test.rule.CodeCoverageAssertor; import com.liferay.portal.kernel.test.rule.NewEnv; import com.liferay.portal.kernel.test.rule.NewEnvTestRule; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.ThreadUtil; import java.lang.ref.Reference; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import org.junit.After; import org.junit.Assert; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; /** * @author Shuyang Zhou */ @NewEnv(type = NewEnv.Type.CLASSLOADER) public class FinalizeManagerTest { @ClassRule @Rule public static final AggregateTestRule aggregateTestRule = new AggregateTestRule( CodeCoverageAssertor.INSTANCE, NewEnvTestRule.INSTANCE); @After public void tearDown() { System.clearProperty(_THREAD_ENABLED_KEY); } @NewEnv(type = NewEnv.Type.NONE) @Test public void testBadFinalizeAction() { final RuntimeException runtimeException = new RuntimeException(); Reference<Object> reference = FinalizeManager.register( new Object(), new FinalizeAction() { @Override public void doFinalize(Reference<?> reference) { Assert.assertNotNull(_getReferent(reference)); throw runtimeException; } }, FinalizeManager.PHANTOM_REFERENCE_FACTORY); Assert.assertNotNull(_getReferent(reference)); reference.enqueue(); try { ReflectionTestUtil.invoke( FinalizeManager.class, "_pollingCleanup", new Class<?>[0]); Assert.fail(); } catch (Exception e) { Assert.assertSame(runtimeException, e); } Assert.assertNull(_getReferent(reference)); } @NewEnv(type = NewEnv.Type.NONE) @Test public void testConstructor() { new FinalizeManager(); } @Test public void testManualClear() throws InterruptedException { System.setProperty(_THREAD_ENABLED_KEY, StringPool.FALSE); Object object = new Object(); MarkFinalizeAction markFinalizeAction = new MarkFinalizeAction(); Reference<Object> reference = FinalizeManager.register( object, markFinalizeAction, FinalizeManager.WEAK_REFERENCE_FACTORY); Map<Reference<?>, FinalizeAction> finalizeActions = ReflectionTestUtil.getFieldValue( FinalizeManager.class, "_finalizeActions"); Assert.assertEquals(markFinalizeAction, finalizeActions.get(reference)); reference.clear(); Assert.assertNull(finalizeActions.get(reference)); object = null; GCUtil.gc(true); ReflectionTestUtil.invoke( FinalizeManager.class, "_pollingCleanup", new Class<?>[0]); Assert.assertFalse(markFinalizeAction.isMarked()); ReflectionTestUtil.invoke( FinalizeManager.class, "_finalizeReference", new Class<?>[] {Reference.class}, reference); Assert.assertFalse(markFinalizeAction.isMarked()); } @Test public void testRegisterationIdentity() { System.setProperty(_THREAD_ENABLED_KEY, StringPool.FALSE); String testString = new String("testString"); MarkFinalizeAction markFinalizeAction = new MarkFinalizeAction(); Reference<?> reference1 = FinalizeManager.register( testString, markFinalizeAction, FinalizeManager.SOFT_REFERENCE_FACTORY); Map<Reference<?>, FinalizeAction> finalizeActions = ReflectionTestUtil.getFieldValue( FinalizeManager.class, "_finalizeActions"); Assert.assertEquals( finalizeActions.toString(), 1, finalizeActions.size()); Assert.assertTrue(finalizeActions.containsKey(reference1)); Reference<?> reference2 = FinalizeManager.register( testString, markFinalizeAction, FinalizeManager.SOFT_REFERENCE_FACTORY); Assert.assertEquals(reference1, reference2); Assert.assertNotSame(reference1, reference2); Assert.assertEquals( finalizeActions.toString(), 2, finalizeActions.size()); Assert.assertTrue(finalizeActions.containsKey(reference1)); Assert.assertTrue(finalizeActions.containsKey(reference2)); reference2.clear(); Assert.assertEquals( finalizeActions.toString(), 1, finalizeActions.size()); Assert.assertTrue(finalizeActions.containsKey(reference1)); reference2 = FinalizeManager.register( new String(testString), markFinalizeAction, FinalizeManager.SOFT_REFERENCE_FACTORY); Assert.assertEquals( finalizeActions.toString(), 2, finalizeActions.size()); Assert.assertTrue(finalizeActions.containsKey(reference1)); Assert.assertTrue(finalizeActions.containsKey(reference2)); reference2.clear(); Assert.assertEquals( finalizeActions.toString(), 1, finalizeActions.size()); Assert.assertTrue(finalizeActions.containsKey(reference1)); reference1.clear(); Assert.assertTrue(finalizeActions.isEmpty()); } @Test public void testRegisterPhantomWithoutThread() throws InterruptedException { doTestRegister(false, ReferenceType.PHANTOM); } @Test public void testRegisterPhantomWithThread() throws InterruptedException { doTestRegister(true, ReferenceType.PHANTOM); } @Test public void testRegisterSoftWithoutThread() throws InterruptedException { doTestRegister(false, ReferenceType.SOFT); } @Test public void testRegisterSoftWithThread() throws InterruptedException { doTestRegister(true, ReferenceType.SOFT); } @Test public void testRegisterWeakWithoutThread() throws InterruptedException { doTestRegister(false, ReferenceType.WEAK); } @Test public void testRegisterWeakWithThread() throws InterruptedException { doTestRegister(true, ReferenceType.WEAK); } protected void doTestRegister( boolean threadEnabled, ReferenceType referenceType) throws InterruptedException { System.setProperty( _THREAD_ENABLED_KEY, Boolean.toString(threadEnabled)); String id = "testObject"; FinalizeRecorder finalizeRecorder = new FinalizeRecorder(id); MarkFinalizeAction markFinalizeAction = new MarkFinalizeAction(); ReferenceFactory referenceFactory = FinalizeManager.PHANTOM_REFERENCE_FACTORY; if (referenceType == ReferenceType.WEAK) { referenceFactory = FinalizeManager.WEAK_REFERENCE_FACTORY; } else if (referenceType == ReferenceType.SOFT) { referenceFactory = FinalizeManager.SOFT_REFERENCE_FACTORY; } Reference<FinalizeRecorder> reference = FinalizeManager.register( finalizeRecorder, markFinalizeAction, referenceFactory); Assert.assertFalse(markFinalizeAction.isMarked()); finalizeRecorder = null; // First GC to trigger Object#finalize if (referenceType == ReferenceType.PHANTOM) { GCUtil.gc(false); } else if (referenceType == ReferenceType.SOFT) { GCUtil.fullGC(true); } else { GCUtil.gc(true); } Assert.assertEquals(id, _finalizedIds.take()); if (referenceType == ReferenceType.PHANTOM) { Assert.assertFalse(markFinalizeAction.isMarked()); // Second GC to trigger ReferenceQueue#enqueue GCUtil.gc(false); } if (threadEnabled) { _waitUntilMarked(markFinalizeAction); } else { ReflectionTestUtil.invoke( FinalizeManager.class, "_pollingCleanup", new Class<?>[0]); } Assert.assertTrue(markFinalizeAction.isMarked()); if (referenceType == ReferenceType.PHANTOM) { Assert.assertEquals(id, markFinalizeAction.getId()); } else { Assert.assertNull(markFinalizeAction.getId()); } if (!threadEnabled || (referenceType != ReferenceType.PHANTOM)) { Assert.assertNull(_getReferent(reference)); } if (threadEnabled) { _checkThreadState(); } } private void _checkThreadState() { Thread finalizeThread = null; for (Thread thread : ThreadUtil.getThreads()) { String name = thread.getName(); if (name.equals("Finalize Thread")) { finalizeThread = thread; break; } } Assert.assertNotNull(finalizeThread); // First waiting state long startTime = System.currentTimeMillis(); while (finalizeThread.getState() != Thread.State.WAITING) { Assert.assertTrue( "Timeout on waiting finialize thread to enter waiting state", (System.currentTimeMillis() - startTime) <= 10000); } // Interrupt to wake up finalizeThread.interrupt(); // Second waiting state while (finalizeThread.getState() != Thread.State.WAITING) { Assert.assertTrue( "Timeout on waiting finialize thread to enter waiting state", (System.currentTimeMillis() - startTime) <= 10000); } } private <T> T _getReferent(Reference<T> reference) { return ReflectionTestUtil.getFieldValue(reference, "referent"); } private void _waitUntilMarked(MarkFinalizeAction markFinalizeAction) throws InterruptedException { long startTime = System.currentTimeMillis(); while (!markFinalizeAction.isMarked() && ((System.currentTimeMillis() - startTime) < 10000)) { Thread.sleep(1); } Assert.assertTrue(markFinalizeAction.isMarked()); } private static final String _THREAD_ENABLED_KEY = FinalizeManager.class.getName() + ".thread.enabled"; private final BlockingQueue<String> _finalizedIds = new LinkedBlockingDeque<>(); private static enum ReferenceType { SOFT, PHANTOM, WEAK } private class FinalizeRecorder { public FinalizeRecorder(String id) { _id = id; } @Override protected void finalize() { _finalizedIds.offer(_id); } private final String _id; } private class MarkFinalizeAction implements FinalizeAction { @Override public void doFinalize(Reference<?> reference) { Object referent = _getReferent(reference); if (referent instanceof FinalizeRecorder) { FinalizeRecorder finalizeRecorder = (FinalizeRecorder)referent; _id = finalizeRecorder._id; } _marked = true; } public String getId() { return _id; } public boolean isMarked() { return _marked; } private volatile String _id; private volatile boolean _marked; } }