B4J Question UI app eating up memory

TomDuncan

Active Member
Licensed User
Longtime User
Hi all,
I have a UI app that uses a FileWatcher to catch any new images copied to a folder.
These images then need to be cropped and resized down to a usable size.
I have set-up the app using the b4J-Bridge to a Ubunto x86 machine.
On every new image that has been resized I loose up yo 10meg of ram each time.
Do I need to free the images used in the subs?
Here is my code. Note, this is just the resize area.

B4X:
Public Sub CopyImageToFolder(Camera,sourcefolder,Filename As String)
    ' Check times and copy
    DateTime.TimeFormat="HH:mm"
    Dim local As String = DateTime.time(DateTime.Now)
    Dim lt  As Int = (local.SubString2(0,2) * 60) + local.SubString2(3,5)

    Dim a_t As Int = (Main.Sunrise.SubString2(0,2) * 60) + Main.Sunrise.SubString2(3,5)
    Dim b_t As Int = (Main.Sunset.SubString2(0,2) * 60) + Main.Sunset.SubString2(3,5)

    'Log(sourcefolder & " " & Filename)
    If File.Exists(sourcefolder,Filename) Then
        If ((lt > a_t) And (lt < b_t)) Then
            Try
                wait for (File.CopyAsync(sourcefolder,Filename,File.DirApp,"www/images/webcam.jpg"))  complete(success As Boolean)
                If success = True Then
'                    showLog("new " & local & " from " & Camera)
                    showLog("new image at " & local & " from " & Camera)
                    Dim lm As String = File.LastModified(sourcefolder,Filename)
                    Process_thumbnail("www/images", "webcam.jpg", Camera  & ".jpg",Camera)
                    Show_Images
                End If
            Catch
                showLog("folder " & LastException)
            End Try
        Else
            Log("after sunset")
        End If
    End If
End Sub


Public Sub Show_Images
    Dim cv As String
    For i = 1 To 6
        Dim Cam As String = "cam_" & NumberFormat(i,2,0)
        If File.Exists(File.DirApp, "www/images/" & Cam & ".jpg") Then
            If i > 1 Then cv = cv & "<hr>"
            cv = cv & $"<img src="${WebViewAssetFile("www/images/" & Cam & ".jpg")}?="$ & DateTime.Now & $"" style="width:100%" />"$
        End If
    Next

    Try
        Main.WebV.LoadHtml(cv)
    Catch
        showLog("Cameras " & LastException)
    End Try
End Sub

Public Sub CopyFolder(Source As String, targetFolder As String)
    If File.Exists(targetFolder, "") = False Then File.MakeDir(targetFolder, "")
    For Each f As String In File.ListFiles(Source)
        If File.IsDirectory(Source, f) Then
            CopyFolder(File.Combine(Source, f), File.Combine(targetFolder, f))
            Continue
        End If
        File.Copy(Source, f, targetFolder, f)
    Next
End Sub


Sub Process_thumbnail(Folder, srcImage, dstImage,camera As String)
    Try
        Dim bImage As Image
'        Log("process " & Folder & " / " & srcImage)
        bImage.Initialize(Folder, srcImage)
        DateTime.DateFormat="HH:mm"
        Select camera
            Case "cam_01"
                Dim c() As Byte = Crop_ImageToJPEGByteArray(bImage,90,60,1280,550,80)
                Main.lbCam_01.Text = "cam 1 " &  DateTime.date(DateTime.Now)
            Case "cam_06"
                Dim c() As Byte = Crop_ImageToJPEGByteArray(bImage,0,130,1800,650,80)
                Main.lbCam_06.Text = "cam 6 " &  DateTime.date(DateTime.Now)
        End Select

        BytesToFile(Folder, dstImage, c)

        DateTime.DateFormat="E, dd MMM YYYY HH:mm a"

        Dim JO As JavaObject
        JO.InitializeStatic("b4j.example.main")
        JO.RunMethod("DrawText", Array(File.Combine(Folder, dstImage), DateTime.date(DateTime.Now), 10, 25, _
                                   File.Combine(Folder, dstImage)))
    Catch
        showLog("thumbnail " & LastException)
    End Try

End Sub


Sub BytesToFile (Dir As String, FileName As String, Data() As Byte)
    Dim out As OutputStream = File.OpenOutput(Dir, FileName, False)
    out.WriteBytes(Data, 0, Data.Length)
    out.Close
End Sub


Sub Crop_ImageToJPEGByteArray(aImage As Object, x,y,w,h As Int, quality As Int) As Byte()
    'Sanity check incoming image
    Try
    Dim image As B4XBitmap = aImage
    Catch
        showLog("crop " & LastException)
        Return Array As Byte ()
    End Try

    image = image.Crop(x, y, w , h)


    'Convert image to JPEG byte array
    Dim out As OutputStream
    out.InitializeToBytesArray(0)
    image.WriteToStream(out, quality, "JPEG")
    out.Close
    'Done
    Return out.ToBytesArray
End Sub
 

TomDuncan

Active Member
Licensed User
Longtime User
I Erel, I have done some tests.
Taken out the call to the webview.
Then had 160 images uploaded into the folder 20 secs apart.
During that time I lost 60 megs on the Linux machine.
Will keep on going with the test. Do I need to free any objects in the image resizing etc?
Tom
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
Do I need to free the images used in the subs?
Probably not. This is probably a 'feature' that has been apparent in Java and .NET for years. The image is made up of a small amount of managed memory referencing a large amount of unmanaged memory. The Java garbage collector is only aware of the small amount of managed memory and sees no need to collect the image object, though collecting it would free the large amount of unmanaged memory. You could try with this code and see what happens, although you probably have a perceived problem rather than an actual one.

 
Upvote 0

TomDuncan

Active Member
Licensed User
Longtime User
Hi Erel and Agraham,
I have left my ip camera to capture an image every 20 secs. It is up to 330 now and the memory seems to be about the same.
Maybe it has something to do with fragmentation. I will check out the Garbage collection in the morning.

Have had a brief look at the garbage routine. This is a UI app does it make a difference?
I get an error with "threadName = getcurrentThreadName"
also with commented out it compiles but with this error after a mnute
"java.lang.IllegalArgumentException: object is not an instance of declaring class"
on "Log("Total designated memory " & jo.RunMethod("getTotal",Null))"

Tom
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
This should be all you need to test it.
B4X:
Sub process_gc
 
   Dim jo As JavaObject = Me
   log("Total designated memory " & jo.RunMethod("getTotal",Null))
   log("Before GC, Java heap currently used by instantiated objects " & jo.RunMethod("getUsed",Null))
   log("Before GC, Current allocated free memory " & jo.RunMethod("getAllocatedFree",Null))
   jo.RunMethod("do_garbage_collection",Null)
   log("After GC, Current allocated free memory " & jo.RunMethod("getAllocatedFree",Null))
   log("After GC, Java heap currently used by instantiated objects " & jo.RunMethod("getUsed",Null))
End Sub

#if java

   import java.util.*;
 
   public void do_garbage_collection()   {         
       Runtime rs = Runtime.getRuntime();
       rs.gc();
     }
    
   public long getAllocatedFree() {
       Runtime rs = Runtime.getRuntime();
        return rs.freeMemory();
    }
 
   public long getAllocatedTotal() {
       Runtime rs = Runtime.getRuntime();
        return rs.totalMemory();
    }
 
   public long getUsed() {
       Runtime rs = Runtime.getRuntime();
        return rs.totalMemory() - rs.freeMemory();
    }
 
   public long getTotal() {
       Runtime rs = Runtime.getRuntime();
        return rs.maxMemory();
    }
 
#End If
 
Upvote 0

TomDuncan

Active Member
Licensed User
Longtime User
After 1000 or so images loaded b4j came with an error.
B4X:
Error: User limit of inotify instances reached or too many open files
Do I need to close any files after I resize them?
This has something to do the Filewatcher.

Also during that time I have lost 330 megs of memory.
I will now test the garbage collection.
Tom
 
Upvote 0

TomDuncan

Active Member
Licensed User
Longtime User
This should be all you need to test it.
B4X:
Sub process_gc

   Dim jo As JavaObject = Me
   log("Total designated memory " & jo.RunMethod("getTotal",Null))
   log("Before GC, Java heap currently used by instantiated objects " & jo.RunMethod("getUsed",Null))
   log("Before GC, Current allocated free memory " & jo.RunMethod("getAllocatedFree",Null))
   jo.RunMethod("do_garbage_collection",Null)
   log("After GC, Current allocated free memory " & jo.RunMethod("getAllocatedFree",Null))
   log("After GC, Java heap currently used by instantiated objects " & jo.RunMethod("getUsed",Null))
End Sub

#if java

   import java.util.*;

   public void do_garbage_collection()   {        
       Runtime rs = Runtime.getRuntime();
       rs.gc();
     }
   
   public long getAllocatedFree() {
       Runtime rs = Runtime.getRuntime();
        return rs.freeMemory();
    }

   public long getAllocatedTotal() {
       Runtime rs = Runtime.getRuntime();
        return rs.totalMemory();
    }

   public long getUsed() {
       Runtime rs = Runtime.getRuntime();
        return rs.totalMemory() - rs.freeMemory();
    }

   public long getTotal() {
       Runtime rs = Runtime.getRuntime();
        return rs.maxMemory();
    }

#End If
Still came up with an error on the first jo.runmethod
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
Needs to be in a class - not sure why
B4X:
Sub Class_Globals

End Sub

Public Sub Initialize
 
End Sub

Sub process_gc
   Dim jo As JavaObject = Me
   Log("Total designated memory " & jo.RunMethod("getTotal",Null))
   Log("Before GC, Java heap currently used by instantiated objects " & jo.RunMethod("getUsed",Null))
   Log("Before GC, Current allocated free memory " & jo.RunMethod("getAllocatedFree",Null))
   jo.RunMethod("do_garbage_collection",Null)
   Log("After GC, Current allocated free memory " & jo.RunMethod("getAllocatedFree",Null))
   Log("After GC, Java heap currently used by instantiated objects " & jo.RunMethod("getUsed",Null))
End Sub

#if java
   import java.util.*;
 
   public void do_garbage_collection()   {         
       Runtime rs = Runtime.getRuntime();
       rs.gc();
     }
    
   public long getAllocatedFree() {
       Runtime rs = Runtime.getRuntime();
        return rs.freeMemory();
    }
 
   public long getAllocatedTotal() {
       Runtime rs = Runtime.getRuntime();
        return rs.totalMemory();
    }
 
   public long getUsed() {
       Runtime rs = Runtime.getRuntime();
        return rs.totalMemory() - rs.freeMemory();
    }
 
   public long getTotal() {
       Runtime rs = Runtime.getRuntime();
        return rs.maxMemory();
    }
 
#End If
Instantiating an instance and calling it now works
B4X:
    Dim GC1 As GC
    GC1.Initialize
    GC1.process_gc
 
Upvote 0
Top