React-native: Pseudo memory leak after removing many Views from screen (ios)

Created on 2 Oct 2018  路  10Comments  路  Source: facebook/react-native

Environment

  React Native Environment Info:
    System:
      OS: macOS High Sierra 10.13.6
      CPU: x64 Intel(R) Core(TM) i5-6360U CPU @ 2.00GHz
      Memory: 84.61 MB / 8.00 GB
      Shell: 3.2.57 - /bin/bash
    Binaries:
      Node: 9.11.2 - ~/.nvm/versions/node/v9.11.2/bin/node
      Yarn: 1.5.1 - /usr/local/bin/yarn
      npm: 5.6.0 - ~/.nvm/versions/node/v9.11.2/bin/npm
      Watchman: 4.7.0 - /usr/local/bin/watchman
    SDKs:
      iOS SDK:
        Platforms: iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0
      Android SDK:
        Build Tools: 23.0.1, 23.0.3, 24.0.0, 24.0.1, 25.0.0, 25.0.1, 25.0.2, 25.0.3, 26.0.1, 26.0.2, 27.0.0, 27.0.3
        API Levels: 23, 24, 25, 26, 27
    IDEs:
      Android Studio: 3.1 AI-173.4670197
      Xcode: 10.0/10A255 - /usr/bin/xcodebuild
    npmPackages:
      react: 16.5.0 => 16.5.0 
      react-native: 0.57.1 => 0.57.1 
    npmGlobalPackages:
      create-react-native-app: 2.0.2

Description

Disclaimer: Yet not totally sure if the behavior is an issue, but as it's causing a somewhat large amount of crashes in my app I'm considering it to be a bug.

The issue is that after performing operations expected to have no impact in memory consumption the total memory usage is higher then the previous baseline. Trying to isolate the cause I ended up with a fairly simple app (shown below) that adds and removes views from the screen and have this __pseudo memory leak__.

XCode tools to track memory indicate that: (all tests made in production scheme)

1. Adding some views and then removing them memory consumption increases
screen shot 2018-09-29 at 10 25 17

2. Add and remove the same amount of views again doesn't increase the memory consumption, but if a larger number of views are added some more memory leak
screen shot 2018-09-29 at 10 56 05

Note that the second peak in the graph is lower than the first. Also, from time to time, some of the retained memory is freed (not in the graph), but I couldn't grasp the possible trigger. Considering this pattern I'm inclined to think this is some kind of cache and not a leak, but I couldn't find anything about it.

In my production app a similar pattern can be repeated until the device runs out of memory. This was tested in iPhone5, but there are similar reports on Crashlytics for other devices.

I was worried that this could be a simple to answer question, so I tried StackOverflow without success so far.

Reproducible Demo

The issue is reproducible by react-native init and changing the App.js to be

```lang=js
import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';

export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}

render() {
const { count } = this.state;
const extraContent = new Array(200 * count).fill().map((_, index) => (
Line {index}
));

return (                                                                                                                                                                                           
  <View style={styles.container}>                                                                                                                                                                  
    <View style={styles.actions}>                                                                                                                                                                  
      <TouchableOpacity onPress={() => this.setState({ count: count + 1})}>                                                                                                                        
        <View style={styles.button}>                                                                                                                                                               
          <Text style={styles.buttonText}>Add</Text>                                                                                                                                               
        </View>                                                                                                                                                                                    
      </TouchableOpacity>                                                                                                                                                                          
      <TouchableOpacity onPress={() => count > 0 && this.setState({ count: count - 1})}>                                                                                                           
        <View style={styles.button}>                                                                                                                                                         
          <Text style={styles.buttonText}>Remove</Text>                                                                                                                                            
        </View>                                                                                                                                                                                    
      </TouchableOpacity>                                                                                                                                                                          
    </View>                                                                                                                                                                                        
    <View>                                                                                                                                                                                         
      <Text>Current count: {count}</Text>                                                                                                                                                          
      <View>{extraContent}</View>                                                                                                                                                                  
    </View>                                                                                                                                                                                        
  </View>                                                                                                                                                                                          
);

}
}

const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 50,
width: '100%',
},
actions: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
},
buttonText: {
color: '#ffffff',
},
button: {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#95afe5',
height: 50,
width: 100,
marginBottom: 5,
borderRadius: 5,
},
});
```

Bug Author Provided Repro JavaScript iOS Stale

Most helpful comment

Hey @leonardocarvalho thank you so much for the detailed investigation, I'll show this to the Core since it's a pretty important issue IMHO.

All 10 comments

Hey thanks for the report, it indeed seems to be a memory leak issue 馃

Have you also tried to investigate it following the suggestions from this article?

Do you have a similar effect on Android too?

Hey @kelset, thanks for your response.

I've tried everything js related in the article, with no success. The example doesn't have many timers or closures to retain the objects.

I was particularly excited about the setState anywhere in the tree, but it had no effect in the repro.

I'll try to profile on Android to check if the same behavior is happening, but in the production app only ios reported OOM issues.

@kelset, some impressions after profiling on Android:

  1. Memory increase for each Add in the repro app seems lower on android
  2. Add/remove views seem to have a similar behavior at first, but when GC runs memory is freed and goes back to the original baseline

I was not able to force GC in ios, and given that it holds the memory until the OOM crash I'm not sure it would be helpful anyway. For this reason I'm assuming that the issue is in ios, or at least it's more severe in this platform.

Thanks again for you attention on the matter.

Hey @leonardocarvalho thank you so much for the detailed investigation, I'll show this to the Core since it's a pretty important issue IMHO.

Hi @kelset ,

Did the Core have time to take a look on this issue? If there's anything I could do to help just let me know.

Hey Leonardo, thanks for the ping - sadly noone was able to check it last time I linked it, I'll try again next week since this week there is ReactConf and the JSI is landing too, so a lot is moving.

Up

Up

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information.

Was this page helpful?
0 / 5 - 0 ratings