优化ScriptRuntime和TopLevelScope的资源回收

This commit is contained in:
TonyJiangWJ 2022-01-08 02:15:14 +08:00
parent 2205fd2f7a
commit 58ba1ff71a
6 changed files with 93 additions and 18 deletions

View File

@ -38,6 +38,7 @@ import com.stardust.autojs.core.ui.widget.JsSpinner;
import com.stardust.autojs.core.ui.widget.JsTabLayout;
import com.stardust.autojs.core.ui.widget.JsToolbar;
import com.stardust.autojs.core.ui.xml.XmlConverter;
import com.stardust.autojs.runtime.ScriptRuntime;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
@ -70,6 +71,7 @@ public class DynamicLayoutInflater {
private LinkedList<WeakReference<View>> createdViews = new LinkedList<>();
private Context mContext;
private ResourceParser mResourceParser;
private WeakReference<ScriptRuntime> mRuntime;
@NonNull
private LayoutInflaterDelegate mLayoutInflaterDelegate = LayoutInflaterDelegate.NO_OP;
private int mInflateFlags;
@ -87,6 +89,10 @@ public class DynamicLayoutInflater {
this.mViewCreators = new HashMap<>(inflater.mViewCreators);
}
public void setRuntime(ScriptRuntime runtime) {
this.mRuntime = new WeakReference<>(runtime);
}
public int getInflateFlags() {
return mInflateFlags;
}
@ -188,6 +194,9 @@ public class DynamicLayoutInflater {
try {
return mLayoutInflaterDelegate.afterConvertXml(context, XmlConverter.convertToAndroidLayout(xml));
} catch (Exception e) {
if (mRuntime != null) {
mRuntime.get().exit();
}
throw new InflateException(e);
}
}

View File

@ -36,6 +36,7 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co
private val context: WeakReference<Context>
private val mScriptable: WeakReference<TopLevelScope>
private var unsealed: Boolean = false
lateinit var threadRef: WeakReference<Thread>
private val initScript: Script
@ -66,6 +67,7 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co
init {
this.context = WeakReference(enterContext())
mScriptable = this.context.get()?.let { createScope(it) }!!
this.context.get()?.seal(mScriptable)
}
override fun put(name: String, value: Any?) {
@ -161,6 +163,10 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co
protected fun createScope(context: Context): WeakReference<TopLevelScope> {
val topLevelScope = TopLevelScope()
topLevelScope.initStandardObjects(context, false)
if (unsealed) {
// FIXME 会导致无法正常被回收 存在内存泄露可能
topLevelScope.setNoRecycle()
}
return WeakReference(topLevelScope)
}
@ -181,14 +187,16 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co
}
protected fun setupContext(context: Context) {
context.optimizationLevel = -1
context.languageVersion = Context.VERSION_ES6
context.locale = Locale.getDefault()
context.wrapFactory = WrapFactory()
}
protected fun removeContext(context: Context) {
context.wrapFactory = null
// FIXME 同时跑多个UI脚本时会共用同一个Context 此时的WrapFactory会被覆盖
if (context.isSealed) {
unsealed = true
context.unseal(sContextEngineMap[context]?.mScriptable)
} else {
context.optimizationLevel = -1
context.languageVersion = Context.VERSION_ES6
context.locale = Locale.getDefault()
context.wrapFactory = WrapFactory()
}
}
private inner class WrapFactory : org.mozilla.javascript.WrapFactory() {

View File

@ -25,6 +25,8 @@ import com.stardust.autojs.script.ScriptSource;
import org.mozilla.javascript.ContinuationPending;
import java.lang.ref.WeakReference;
/**
* Created by Stardust on 2017/2/5.
*/
@ -39,7 +41,7 @@ public class ScriptExecuteActivity extends AppCompatActivity {
private ScriptExecutionListener mExecutionListener;
private ScriptSource mScriptSource;
private ActivityScriptExecution mScriptExecution;
private ScriptRuntime mRuntime;
private WeakReference<ScriptRuntime> mRuntime;
private EventEmitter mEventEmitter;
@ -72,8 +74,8 @@ public class ScriptExecuteActivity extends AppCompatActivity {
mScriptSource = mScriptExecution.getSource();
mScriptEngine = mScriptExecution.createEngine(this);
mExecutionListener = mScriptExecution.getListener();
mRuntime = ((JavaScriptEngine) mScriptEngine).getRuntime();
mEventEmitter = new EventEmitter(mRuntime.bridges);
mRuntime = new WeakReference<> (((JavaScriptEngine) mScriptEngine).getRuntime());
mEventEmitter = new EventEmitter(mRuntime.get().bridges);
runScript();
emit("create", savedInstanceState);
}
@ -228,7 +230,7 @@ public class ScriptExecuteActivity extends AppCompatActivity {
try {
mEventEmitter.emit(event, (Object[]) args);
} catch (Exception e) {
mRuntime.exit(e);
mRuntime.get().exit(e);
}
}

View File

@ -1,7 +1,6 @@
package com.stardust.autojs.rhino;
import org.apache.log4j.Logger;
import org.mozilla.javascript.EmbeddedSlotMap;
import org.mozilla.javascript.IdScriptableObject;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.ScriptableObject;
@ -9,38 +8,69 @@ import org.mozilla.javascript.TopLevel;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TopLevelScope extends ImporterTopLevel {
private static final Logger logger = Logger.getLogger(TopLevelScope.class);
private static final ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1, r -> {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setName("TopLevelScope recycle");
return t;
});
private final long createdStamp;
private long releasedStamp;
private long recycledStamp;
private String engineSource;
private boolean noRecycle;
public TopLevelScope() {
super();
this.createdStamp = System.currentTimeMillis();
}
/* 不需要覆写 直接交给回收线程1分钟后自动回收
@Override
public void finalize() throws Throwable {
logger.debug("回收TopLevelScope资源[" + engineSource + "], 存活总时间:"
+ (System.currentTimeMillis() - this.createdStamp)
+ "ms 释放后经过:" + (System.currentTimeMillis() - releasedStamp) + "ms");
// recycle();
+ "ms 释放后经过:" + (System.currentTimeMillis() - releasedStamp) + "ms"
+ (recycledStamp > 0 ? " 资源已经在[" + recycledStamp + "]被完全释放" : "")
);
super.finalize();
recycle();
}
*/
public void markReleased(String engineSource) {
this.engineSource = engineSource;
releasedStamp = System.currentTimeMillis();
logger.debug("标记TopLevelScope资源已不再使用[" + engineSource + "], 存活总时间:"
+ (releasedStamp - this.createdStamp) + "ms");
if (noRecycle) {
return;
}
// 1分钟后强制回收
scheduledExecutor.schedule(this::recycle, 1, TimeUnit.MINUTES);
}
/**
* 回收资源验证下来发现不是很必要 暂时不处理
*/
private void recycle() {
if (recycledStamp > 0) {
return;
}
long start = System.currentTimeMillis();
try {
Field importedPackagesField = ImporterTopLevel.class.getDeclaredField("importedPackages");
importedPackagesField.setAccessible(true);
importedPackagesField.set(this, null);
} catch (Exception e) {
// ...
}
try {
Field associatedValuesField = ScriptableObject.class.getDeclaredField("associatedValues");
associatedValuesField.setAccessible(true);
@ -64,7 +94,7 @@ public class TopLevelScope extends ImporterTopLevel {
try {
Field slotMap = ScriptableObject.class.getDeclaredField("slotMap");
slotMap.setAccessible(true);
slotMap.set(this, new EmbeddedSlotMap());
slotMap.set(this, null);
} catch (Exception e) {
//
}
@ -91,5 +121,18 @@ public class TopLevelScope extends ImporterTopLevel {
// ...
}
setPrototype(null);
logger.debug("彻底回收[" + engineSource + "]资源 释放后经过:"
+ (System.currentTimeMillis() - releasedStamp)
+ "ms 耗时:" + (System.currentTimeMillis() - start) + "ms");
recycledStamp = System.currentTimeMillis();
}
/**
* 标记当前脚本可以持续运行 不被主动回收 否则十分钟后必备回收
* 适用于new java.lang.Thread 之类的 后台线程 但实际上不应该有这种操作 很容易内存泄露
* 这里保留这么个入口
*/
public void setNoRecycle() {
noRecycle = true;
}
}

View File

@ -250,6 +250,14 @@ public class ScriptRuntime {
mTopLevelScope = new WeakReference<>(topLevelScope);
}
/**
* 使脚本可以在脚本引擎结束后 持续运行 不被回收
* 最好不要使用如果不能准时回收 可能导致内存泄露问题
*/
public void setNoRecycle() {
mTopLevelScope.get().setNoRecycle();
}
public static void setApplicationContext(Context context) {
applicationContext = new WeakReference<>(context);
}
@ -384,6 +392,9 @@ public class ScriptRuntime {
}
public void exit(Throwable e) {
if (stop) {
return;
}
engines.myEngine().uncaughtException(e);
exit();
}

View File

@ -131,8 +131,10 @@ public class VMBridge_custom extends VMBridge_jdk18 {
private Object call(ContextFactory cf, ContextAction action) {
Context cx = Context.enter(null, cf);
//TODO null check
cx.setWrapFactory(mCallerContext.getWrapFactory());
if (!cx.isSealed()) {
// TODO null check
cx.setWrapFactory(mCallerContext.getWrapFactory());
}
try {
return action.run(cx);
} finally {