小心 substring 的 memory leak

探讨 Java 中 String 的 substring 方法可能导致的内存泄漏问题。当从大型 String 对象中提取较小部分时,原始对象占用的内存未被释放,导致内存泄漏。
public class SubstringMemoryLeak {

private String str = new String(new byte[10000]);

public String substring() {
return this.str.substring(0, 2);
}
/**
* @param args
*/
public static void main(String[] args) {
List<String> substringList = new ArrayList<String>(10000);
for (int i=0, n=10000; i<n; i++) {
substringList.add(new SubstringMemoryLeak().substring());
}
}

}


如果執行上面的程式碼,你就會出現
 
Exception:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.lang.StringCoding$StringDecoder.decode(StringCoding.java:133)
at java.lang.StringCoding.decode(StringCoding.java:173)
at java.lang.StringCoding.decode(StringCoding.java:185)
at java.lang.String.(String.java:570)
at java.lang.String.(String.java:593)
at silver8250.tools.SubstringMemoryLeak.(SubstringMemoryLeak.java:8)
at silver8250.tools.SubstringMemoryLeak.main(SubstringMemoryLeak.java:19)

但是如果將 substring() method 修改成:


public String substring() {
return new String(this.str.substring(0, 2));
}

神奇的事情發生了!程式竟然可以如願的執行!問題出現在哪?上面的程式碼最主要是建立一堆很佔記憶體的 String 物件,然後取其中的一小段!重點就在於 String 的 substring() method 的實做方式。
首先,String 物件在記憶體中會以 char array 方式呈現,當我們每次建立一個 String 時,記憶體就會長出一塊 char array 來存放。String class 有一個 non-public constructor:

String (int offset, int count, char[] value)

當我們使用 substring 時,實際是用這個 constructor 來完成,也就是原先建立很佔記憶體的 String 物件並不會縮小,而是保持原來的 char array 所佔的大小。所以 Java 的 GC 就無法對此 char array 進行回收的動作。所以使用 substring() method 對字串內容來說,我們看到的是部份的,但是在記憶體中卻是佔有原先的大小!

後來,我們改用 new String() constructor 來包裝 substring 的內容,這就會讓 Java 重新建一個 char array 來放置 substring 的內容,相對的,所佔得記憶體就小了,而且原先較大的 char array 就沒有任何 reference ,所以 GC 就可以直接回收了!

老實說,這種情況可能不太常見,至少對我來說啦!我對於 String 的建立都會盡量改用 StringBuffer 物件,這樣不僅可以提供較好得效能,對於記憶體的利用也比較不會有問題!

與大家分享之~


ref:http://silver8250.blogspot.com/2010/01/java-substring-memory-leak.html
help me correct the code import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; import './hydroponics.dart'; import './soccer.dart'; class Talk2Device extends StatefulWidget { final BluetoothDevice server; Talk2Device({required this.server}); @override State<Talk2Device> createState() => _Talk2DeviceState(); } class _Talk2DeviceState extends State<Talk2Device> { // static final clientID = 0; var connection; //BluetoothConnection String _messageBuffer = ''; final TextEditingController textEditingController = TextEditingController(); final ScrollController listScrollController = ScrollController(); bool isConnecting = true; bool isDisconnecting = false; String deviceType = "unknown"; List infoFromDevice = []; List<int> _imageBuffer = []; // 存储接收到的字节数据 Uint8List? _currentImage; // 当前显示的图像数据 bool _isLoading = false; // 加载状态 @override void initState() { super.initState(); BluetoothConnection.toAddress(widget.server.address).then((_connection) { // print('Connected to the device'); connection = _connection; setState(() { isConnecting = false; isDisconnecting = false; }); // if(isConnected()){ // print("sending probe"); // _sendMessage("who are you "+DateFormat('kk:mm:ss \n EEE d MMM').format(DateTime.now())); // }else{ // print("not connected"); // } connection.input.listen(_onDataReceived).onDone(() { // Example: Detect which side closed the connection // There should be `isDisconnecting` flag to show are we are (locally) // in middle of disconnecting process, should be set before calling // `dispose`, `finish` or `close`, which all causes to disconnect. // If we except the disconnection, `onDone` should be fired as result. // If we didn't except this (no flag set), it means closing by remote. // if (isDisconnecting) { // print('Disconnecting locally!'); // } else { // print('Disconnected remotely!'); // } if (this.mounted) { setState(() {}); } }); }).catchError((error) { // print('Cannot connect, exception occured'); // print(error); }); } @override void dispose() { // Avoid memory leak (`setState` after dispose) and disconnect if (isConnected()) { isDisconnecting = true; connection.dispose(); connection = null; } super.dispose(); } void sendCmd(String cmd) { // print("command: " + cmd); } @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( body: deviceType == "hydroponics" ? hydroponics(infoFromDevice: infoFromDevice) : deviceType == "soccer" ? Soccer(sendCmd: sendMessage, infoFromDevice: infoFromDevice) : SimpleDialog( title: const Text( 'Choose device type', textAlign: TextAlign.center, // style: TextStyle( // fontSize: 20.0, // fontWeight: FontWeight.bold, // letterSpacing: 1.3, // ), ), children: <Widget>[ SimpleDialogOption( child: const Column( children: <Widget>[ if (_isLoading) const CircularProgressIndicator(), if (_currentImage != null) Expanded( child: InteractiveViewer( minScale: 0.1, maxScale: 4.0, child: Image.memory( _currentImage!, fit: BoxFit.contain, gaplessPlayback: true, // 避免刷新时闪烁 ), ), ) else const Text('Waiting for image data...'), ,SizedBox( height: 10, ), // Optional to give some extra space Text('Robot PILA'), ], ), onPressed: () { setState(() { // print("before "+deviceType); deviceType = "soccer"; // print("after "+deviceType); }); }, ), SimpleDialogOption( child: const Column( children: <Widget>[ SizedBox( height: 10, ), Text('STEM Hydroponics System'), ], ), onPressed: () { setState(() { // print("before "+deviceType); deviceType = "hydroponics"; // print("after "+deviceType); }); }, ), ], ) , // floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, // for switching device type // floatingActionButton: FloatingActionButton( // onPressed: () { // setState(() { // // print("before "+deviceType); // deviceType = "unknown"; // // print("after "+deviceType); // }); // }, // child: Icon(Icons.transgender), // ) ), ); } void _onDataReceived(Uint8List data) { setState(() => _isLoading = true); // 1. 将新数据追加到缓冲区 _imageBuffer.addAll(data); // 2. 检查JPEG结束标记(0xFFD9) final endMarker = _findJpegEndMarker(); if (endMarker != -1) { // 3. 提取完整图像数据 final imageData = Uint8List.fromList(_imageBuffer.sublist(0, endMarker + 2)); // 4. 更新当前图像并重置缓冲区 setState(() { _currentImage = imageData; _imageBuffer = _imageBuffer.sublist(endMarker + 2); // 保留剩余数据 _isLoading = false; }); } // 防止缓冲区过大(100KB图片 + 20%容差) if (_imageBuffer.length > 120 * 1024) { _imageBuffer.clear(); } } int _findJpegEndMarker() { for (int i = _imageBuffer.length - 2; i >= 0; i--) { if (_imageBuffer[i] == 0xFF && _imageBuffer[i + 1] == 0xD9) { return i; // 返回结束标记起始位置 } } return -1; // 未找到 } // Create message if there is new line character String dataString = String.fromCharCodes(buffer); // print("received something, decoding"); // print("raw data: "+buffer.toString()); int index = buffer.indexOf(13); if (~index != 0) { setState(() { String msg = backspacesCounter > 0 ? _messageBuffer.substring( 0, _messageBuffer.length - backspacesCounter) : _messageBuffer + dataString.substring(0, index); _messageBuffer = dataString.substring(index); // print("decoded message: " + msg); // print("before "+msg); msg = msg.replaceAll( RegExp(r'[^A-Za-z0-9().,;:\-?]'), ''); // remove \n or \r List split = msg.split(','); // each item is separated by a comma // print("after"+split.toString()); if (split[0] == deviceType) { // make sure the data receive match the format split.removeAt(0); // format matches, remove the device identifier // for (int i = 0;i < split.length;i++) // print("["+i.toString()+"] "+split[i]); infoFromDevice = List.from(split); // print(infoFromDevice); } }); } else { _messageBuffer = (backspacesCounter > 0 ? _messageBuffer.substring( 0, _messageBuffer.length - backspacesCounter) : _messageBuffer + dataString); } } void sendMessage(String text) async { text = text.trim(); textEditingController.clear(); if (text.isNotEmpty) { try { connection.output.add(utf8.encode(text)); // + "\n" await connection.output.allSent; // setState(() { // // messages.add(_Message(clientID, text)); // print("sending message"); // print(text); // }); // Future.delayed(Duration(milliseconds: 333)).then((_) { // listScrollController.animateTo( // listScrollController.position.maxScrollExtent, // duration: Duration(milliseconds: 333), // curve: Curves.easeOut); // }); } catch (e) { // Ignore error, but notify state // setState(() {}); } } } bool isConnected() { return connection != null && connection.isConnected; } } help me fix the code
07-26
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值