Java[1]语言为字符串操作提供了丰富的支持,它将字符串封装在三个类中并提供多种字符串操作接口。在Java应用程序中,由于对字符串的使用量比较高,从而使得其需要消耗较大的堆空间。例如在J2EE应用服务器运行过程中,约40%的活跃堆空间被用来保存字符串数据[2]。
通过对Java中字符串操作接口的分析可以发现,随着这些操作的运行会产生较多的无用字符串,它们不再被Java类封装并且也不被任何变量引用。这些无用字符串数据将一直停留在活跃堆中,直到Java虚拟机启动垃圾收集将其回收。而由于字符串数据具有单个对象占用空间较小但总体数量很大的特征,大量的无用字符串数据不仅会影响堆空间的利用率,并且对Java虚拟机垃圾收集的性能有较大影响。
当前对Java中字符串的内存管理优化方案主要关注于字符串的使用效率上,如消除常量重复、延迟分配等技术[2],通过修改Java虚拟机对字符串分配回收的支持来提高堆中字符串的使用效率。然而这些方案无法处理堆中已经成为无用字符串的数据,只能等待垃圾收集来处理。
近期编译时的独立对象回收策略[3]则专注于在编译阶段对应用程序做分析并插入回收指令以回收无用对象空间,但是该方案对Java库函数只做保守分析从而无法回收这些无用字符串。为此,本文从对字符串操作接口的分析出发,识别各类操作对字符串的改变情况以利用独立对象回收策略中的指令插桩技术来主动回收无用字符串对象,以提高堆空间的利用率、减低垃圾回收的负担、改善Java虚拟机的性能。
1 Java中字符串的支持与分析
1.1 Java中字符串的支持
Java语言将字符串的表示和操作都封装在StringBuilder、StringBuffer和String三个类中。其中前两个类指向的字符串是可变的,String类指向的字符串是不变的。这三个类的内部结构基本上一致,以StringBuilder为例,StringBuilder在Java中的结构如图1所示。
从图1可以看出,字符串数据由StringBuilder对象指向的value域保存,在内存空间上反映为两个对象:StringBuilder对象通过value域指向字符串对象。由于该类提供常用的可变字符串操作接口且相对另一个类StringBuffer具有较高的执行效率,对字符串数据的操作在Java虚拟机中一般会将其转换为StringBuilder对象再做处理。下面以一个语句示例来说明这一点:
String s=new String(‘aa’+’bb’+’cc’);
该语句的语义是将三个字符串连接在一起并生成一个String对象,在Java语言的源程序级别上不会出现StringBuilder对象,但是经过编译器优化之后,这条语句实际被翻译为下面的字节码形式(为简化描述,本文以源语言来表示字节码的操作):
StringBuilder t=new StringBuilder(‘aa’);
t.append(‘bb’);
t.append(‘cc’);
String s=t.toString();
即Java编译器会首先创建一个StringBuilder对象,完成字符串的连接工作之后再将其转变为String对象。由于类似于这种情况的字符串操作较多地出现在输出方法和字符串创建方法中,所以可推断出StringBuilder有着较大的使用频率,故将以其为代表分析其提供的接口对字符串的影响。
1.2 无用字符串的产生
在上节的示例中,StringBuilder类提供的append()接口将会改变value域所指向的字符串,其做法是:新建长度为连接后字符串长度之和的字符数组,分段复制之后使其成为value域指向的新数组,而value域指向的原数组将被丢弃成为无用字符串。
图2为示例语句中append()接口引起的value域指向字符串变化图。
从图2可以看出,在append()接口执行过后,对象的字符数组将指向新建的字符串’aabb’,原有字符串’aa’将不被任何变量指向而成为无用字符串。
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉