Unknown out of memory leak

Hi,
I am working on a project with the following issue: every few times I run it, it begins to crash on startup due to an out of memory error.
Here is the error:

OutOfMemoryError: Java heap space
	at java.awt.image.DataBufferInt.<init>(DataBufferInt.java:75)
	at java.awt.image.Raster.createPackedRaster(Raster.java:467)
	at java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1032)
	at sun.awt.image.ImageRepresentation.createBufferedImage(ImageRepresentation.java:253)
	at sun.awt.image.ImageRepresentation.setPixels(ImageRepresentation.java:559)
	at sun.awt.image.ImageDecoder.setPixels(ImageDecoder.java:138)
	at sun.awt.image.JPEGImageDecoder.sendPixels(JPEGImageDecoder.java:119)
	at sun.awt.image.JPEGImageDecoder.readImage(Native Method)
	at sun.awt.image.JPEGImageDecoder.produceImage(JPEGImageDecoder.java:141)
	at sun.awt.image.InputStreamImageSource.doFetch(InputStreamImageSource.java:269)
	at sun.awt.image.ImageFetcher.fetchloop(ImageFetcher.java:205)
	at sun.awt.image.ImageFetcher.run(ImageFetcher.java:169)

And here is my code:

import os
import random
import math



class Pos(object):

    def __init__(self, x, y, w=0, h=0):
        self.x0 = self.oX = x
        self.y0 = self.oY = y
        self.w = w
        self.h = h
        self.x1 = x + w
        self.y1 = y + h
        self.anchorX, self.anchorY = mouseX, mouseY
        self.hover = True

    def get_deltas(self):
        if self.hover:
            self.dx = self.dy = 0
        else:
            self.dx = mouseX - self.anchorX
            self.dy = mouseY - self.anchorY

    def update(self):
        if mousePressed and (mouseButton == LEFT):
            self.get_deltas()
            if self.dx != 0 or self.dy != 0:
                self.x0 += self.dx
                self.y0 += self.dy
            self.anchorX, self.anchorY = mouseX, mouseY
            self.hover = False

    def contains(self, X, Y):
        return (self.x0 <= X <= self.x1) and (self.y0 <= Y <= self.y1)

    def lock(self):

        if self.x0 > self.oX:
            self.x0 = self.oX
        elif self.x0 < -self.w:
            self.x0 = -self.w
        if self.y0 > self.oY:
            self.y0 = self.oY
        elif self.y0 < -self.h:
            self.y0 = -self.h

class RadialImagePos(Pos):
    def __init__(self, img, x, y, w=0, h=0):
        super(RadialImagePos,self).__init__(x,y,w,h)
        self.img = img
        self.theta = random.random() * TWO_PI
        
    def get_deltas(self):
        if self.hover:
            self.dx = self.dy = 0
        else:
            self.dx = math.cos(self.theta) * abs(mouseX - self.anchorX)
            self.dy = math.sin(self.theta) * abs(mouseY - self.anchorY)

class floatingText(object):

    def __init__(self, txt, frame, axis='y'):
        self.theta = random.random() * TWO_PI
        self.txt = txt
        self.frame = frame

        if random.choice([True,False]):
            self.y = random.random() * (random.randint(0,frame.h))
            self.x = random.random() * (random.randint(0,frame.w))

        else:
            self.y = random.random() * (random.randint(0, frame.w))
            self.x = random.random() * (random.randint(0, frame.h))
        self.get_deltas()
        
    def get_deltas(self):
        self.dx = math.cos(self.theta) * float(random.randint(5, 15))
        self.dy = math.sin(self.theta) * float(random.randint(5, 15))

    def update(self):
        if (self.x > self.frame.x0 + self.frame.w) or (self.x < self.frame.x0) or (self.y > self.frame.y0 + self.frame.h) or (self.y < self.frame.y0):
            self.theta += PI
 
        self.get_deltas()
        self.x += self.dx
        self.y += self.dy

def run_lines():
    global lines
    for l in lines:
        l.update()
        text(l.txt, l.x, l.y)

def init_images():
    global bg1, bgPos, chosen, num_slides, all_imgs, imgs, frimgs
    bg1 = loadImage(sketchPath("vp_bg.jpg"))
    bgPos = Pos(0, 0, bg1.width, bg1.height)
    bgPos.upToY = 1234
    bgPos.frameY = 1166
    scaps = [fn for fn in os.listdir(sketchPath()) 
             if fn.split('_')[0] == 'scaps']
    sample = [sketchPath(fn) for fn 
              in random.sample(scaps, num_slides)]
    all_imgs = [loadImage(path) for path in sample]
    #imgs = [all_imgs[index]]
    imgs = [all_imgs[0]]
    frimgs = [RadialImagePos(img,0,bgPos.upToY,bgPos.w,bgPos.frameY) for img in imgs]
    
def init_strings():
    global igf, manifesto,bg1,lines
    lines = []
    #igf = createFont(sketchPath('Billabong.ttf'),30)
    # textFont(igf)
    fill(255)
    manifesto = loadStrings(sketchPath("manifesto.txt"))
    with open(sketchPath("manifesto.txt")) as f:
        mf_txt = f.read()
        mf_lines = [l for l in mf_txt.split('.') if l != '']
    txt_lines = random.sample(mf_lines, random.randint(20, len(mf_lines)))
    for txt_line in txt_lines:
        lines.append(floatingText(txt_line,bgPos))


def init_names():
    global num_slides, span
    span = 0
    #index = 0
    num_slides = 40

def setup():

    size(1920,1080,P2D)

    # state variables
    global slideshow
    global lines

    fill(0)
    init_names()

    init_images()
    init_strings()
    slideshow = True
    

def draw():
    global bg1, bgPos
    global frimgs
    global slideshow
    global frcenter
    background(0)
    image(bg1, bgPos.x0, bgPos.y0)
    bgPos.update()
    bgPos.lock()
    if slideshow:
        for frimg in frimgs:
            image(frimg.img,
                frimg.x0, frimg.y0,
                frimg.w, frimg.h
                )
            frimg.update()
        run_lines()


def slide(grow=False):
    global imgs, all_imgs, frimgs, span, bgPos
    global chosen
    #index += 1
    #index %= len(imgs)
    all_imgs = all_imgs[span:] + all_imgs[:span]
    imgs = all_imgs * (len(imgs) // len(all_imgs))
    imgs += all_imgs[:span+1]
    for i in range(span):
        frimgs[i].img = imgs[i]
    if grow:
        span += 1
        span %= len(all_imgs)
        imgs.append(all_imgs[span])
        frimgs.append(
                      RadialImagePos(imgs[span], 
                                     0,bgPos.upToY, 
                                     bgPos.w,bgPos.frameY)
                      )
                    


def mouseClicked():
    global slideshow, skip, span
    global framePos, slide,frimgs
    if slideshow:        
        
        if random.choice([True, False, False]):
            slide(True)
        else:
            slide()
            
        random.shuffle(frimgs)
        print(len(frimgs))
            
def mouseReleased():
    bgPos.hover = True
    for frimg in frimgs:
        frimg.hover = True

EDIT: i have increased my memory.

1 Like

Hi @sargent,

Without the text files (manifesto.txt) and the images (vp_bg.jpg) it is difficult to run your sketch. Would you mind providing a link to those or, better, upload a lighter version of your script without dependencies ?

Just a thought, if your init() functions are computationally expensive I would suggest to load them in a thread() first.

loaded = False

def setup():
    size(1920, 1080, P2D)
    
    thread("load")
    
    
def load():
    global loaded

    # All the things you need to compute
    init_names()
    init_images()
    init_strings()
    
    # Signaling that everything is computed
    loaded = True
  
def draw():
    global bg1, bgPos, frimgs, slideshow, frcenter
    background(0)
    
    if loaded:
        
        image(bg1, bgPos.x0, bgPos.y0)
        bgPos.update()
        bgPos.lock()
        if slideshow:
            for frimg in frimgs:
                image(frimg.img,
                    frimg.x0, frimg.y0,
                    frimg.w, frimg.h
                    )
                frimg.update()
            run_lines()
        
        
    else:
        pushStyle()
        fill(0)
        text("LOADING", 30, 30)
        popStyle()

Also, not directly related to your problem but if speed is an issue I would suggest to:

  • avoid calls to Python math module as much as possible
  • use xrange over range when iterating
math.cos(self.theta) --> cos(self.theta)

math.cos(self.theta) * float(random.randint(5, 15)) --> cos(self.theta) * random(5, 15)

random.random() * TWO_PI --> random(TAU)

if random.choice([True,False]) --> if random(1) > .5

if random.choice([True, False, False]) --> if random(1) > .66

random.random() * (random.randint(0,frame.h)) --> random(frame.h)

for i in range(span) --> for i in xrange(span)

These few changes can drastically improve the speed of your sketch in Python mode

2 Likes

Here is a zip file of my sketch folder.
Thank you for the efficiency advice.