Issue
I have a basic understanding of memory management in Kotlin i.e. local variables are stored in the stack and are managed by the OS and the objects are created on the heap and JVM manages them. Moreover, objects are passed as copy of reference and primitive types are passed as copy in functions.
In the below code, I have created a TextEditOperation function that creates an EditText programmatically and sets its properties using an instruction object and added a TextWatcher to it and at last added the edittext view to parent relative layout.
public fun TextEditOperation (pInstructionSetIndex: Int, pInstructionIndex: Int): View?
{
val instruction: InstructionKernelAndroid
val context: Context?
val activity: Activity
val textedit: EditText
val parent_view: RelativeLayout
// fetch the instruction
instruction = InstructionSetMgr.uAndroidOSInstructionSetList[pInstructionSetIndex][pInstructionIndex]
// get activity's Context
context = ProcessStates.GetMainActivityContext ()
// cast activity's context to activity to use it's method
activity = context as Activity
// get parent view
parent_view = activity.findViewById (instruction.GetParentID ()) as RelativeLayout
// create view
textedit = EditText (context)
// set view properties
textedit.id = instruction.GetElementID ()
textedit.hint = instruction.GetHintText ()
textedit.layoutParams = RelativeLayout.LayoutParams (
instruction.GetWidth (),
instruction.GetHeight ()
)
textedit.x = instruction.GetX ()
textedit.y = instruction.GetY ()
textedit.setBackgroundColor (Color.parseColor (instruction.GetBackgroundColor ()))
textedit.setTextColor (Color.parseColor (instruction.GetTextColor ()))
// create and add a TextWatcher to the EditText
val text_watcher: TextWatcher
text_watcher = object : TextWatcher {
override fun beforeTextChanged (s: CharSequence, start: Int, count: Int, after: Int)
{
}
override fun onTextChanged (s: CharSequence, start: Int, before: Int, count: Int)
{
TextChanged (pInstructionSetIndex + 1, textedit.length (), textedit.id)
}
override fun afterTextChanged (s: Editable)
{
}
}
// add the textwatcher
textedit.addTextChangedListener (text_watcher)
// add view to parent view
parent_view.addView (textedit);
return textedit;
}
Now, what I understand is if the text_watcher object wouldn't have been there, then for line
textedit = EditText (context)
EditText(context) object would have been created on the heap(managed by JVM) and variable textedit (managed by OS) refering the object would have been on stack. So the textedit variable would have been cleared from the stack memory by the OS once the function scope ends.
However, now as I am using the textedit variable inside the OnTextChanged function of the text_watcher:
override fun onTextChanged (s: CharSequence, start: Int, before: Int, count: Int)
{
TextChanged (pInstructionSetIndex + 1, textedit.length (), textedit.id)
}
And the onTextChanged function will get called much after the TextEditOperation's function scope has ended still How I'm able to access the length and id of the EditText using textedit variable which should have been removed by the OS by now?
Is it because of closures in kotlin, than I'm able to access the varible that's present in the outer scope and hence it not getting free by the OS thus being a memory leak, if yes how to fix it or is there any other reason behind it?
Solution
Disclaimer: This answer focuses on Kotlin when targeting the JVM. The general concepts should be applicable to other platforms, but the specifics are likely to differ.
Closures & Variable Capturing
When a closure makes use of a variable from the enclosing scope, that closure "captures" the value of the variable at the time the closure is created.
In Kotlin, this is implemented by implicitly adding a class property to the class generated for the closure object. The property is initialized with the value of the captured variable during instantiation of the closure. Within the closure's scope, any time it looks like you are using the captured variable you are actually using the property. All this is done for you by the compiler behind the scenes.
So, using textedit
inside your TextWatcher
implementation is perfectly okay.
The value of that variable is captured when the
TextWatcher
is created, not when the variable is first "used" in the implementation.All references to
textedit
within the scope of theTextWatcher
implementation are actually referencing a hidden, implicitly defined property of said implementation. Meaning there is no attempt to access a local variable after it has been popped off the stack.
Note that when a closure references a class property of its enclosing class, the closure actually captures the instance of the enclosing class (i.e., this
) instead of the value of the property directly. Also, unlike Java, Kotlin is capable of capturing mutable local variables (i.e., var
), which it accomplishes by using a hidden "wrapper class" that wraps the actual value, then captures that wrapper object instead.
Conceptual Example
Here is some code demonstrating the concept of what the compiler is doing. This is not necessarily exactly how variable capturing is implemented, but it should hopefully help with understanding the idea.
If you have something like this:
interface Printer {
fun print()
}
fun createPrinter(): Printer {
val message = "Hello, World" // local variable
return object : Printer {
override fun print() = println(message)
}
}
After compilation, it would be as if you wrote the following:
interface Printer {
fun print()
}
fun createPrinter() : Printer {
val message = "Hello, World" // local variable
class AnonymousPrinterImpl(
private val message: String // class property
): Printer {
// Here, 'message' is referring to the class property,
// not the local variable.
override fun print() = println(message)
}
return AnonymousPrinterImpl(message)
}
If you want to see what the compiler actually generates then you can inspect the Java byte-code via the javap
tool. Note that the implementation for an anonymous object is different than for a functional interface or lambda expression, but the end result is the same: A class property is implicitly added to the closure's class.
Memory Management
In Kotlin, heap memory is managed by a garbage collector. At a high level, the way a garbage collector works is by periodically traversing the current object graph, starting at designated root objects. Any object that cannot be reached from those root objects is eligible for garbage collection. In languages with a garbage collector, a "memory leak" occurs when an object remains strongly referenced after the program no longer needs it. The strong reference prevents the unneeded object from being collected by the garbage collector.
You seem to be worried the TextWatcher
is erroneously keeping the TextEdit
object in memory after your function returns. But you are returning the TextEdit
object from the function and adding it to some "parent view". Both those things strongly indicate the TextEdit
object is still needed by your program after the function returns.
Additionally, the only strong reference to the TextWatcher
after your function returns is by the TextEdit
. In this case, it is not the TextWatcher
keeping the TextEdit
in memory, but rather the other way around. When the TextEdit
becomes eligible for garbage collection, so will the TextWatcher
. The circular reference between the two does not change that.
In short, there is no memory leak in your code; or at least not one caused by the TextWatcher
based on what you have shown us.
Answered By - Slaw
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.