转载地址:https://cloud.tencent.com/developer/article/1685029
Java和ABAP的垃圾回收机制(Garbage Collection)比较
Java 和 ABAP 都可以支持自动垃圾回收。 作为应用程序开发人员,GC 过程对我们来说是完全透明的,并且在我们的应用程序代码中,大多数时候我们不应该调用 Java 或 ABAP 提供的 GC API。 然而,如果我们对垃圾收集的触发点有一些基本的了解,它可以帮助我们编写更健壮的代码,并且内存消耗相对较低。
Java中的垃圾收集
使用下面这个小代码进行测试,它每 100 毫秒分配 2MB 内存。
package memoryTest;
import java.util.Timer;
import java.util.TimerTask;
class MyTask extends TimerTask{
static final int MB = * ;
@Override
public void run() {
byte[] a1 = new byte[ * MB];
a1[] = ;
Runtime runtime = Runtime.getRuntime();
System.out.print("total :" + (runtime.totalMemory() / )+ " KB\n");
long free = runtime.freeMemory() / ;
System.out.print("free:" + free+ " KB\n");
}
}
public class MemoryAllocationTest {
static public void main(String[] arg){
Timer timer = new Timer();
timer.schedule(new MyTask(), , );
}
}
Java 中的次要垃圾收集
使用以下 VM 参数执行程序。 选项 PrintGCDetails 可以打印出 GC 执行细节,Xmx40m 表示允许的最大堆分配大小为 40MB。
执行时,您可以在控制台中找到以下输出:
总计:39424 KB 免费:35720 KB 总计:39424 KB 免费:33672 KB 总计:39424 KB 免费:31624 KB 总计:39424 KB 免费:29576 KB GC (分配失败) [PSYoungGen: 9847K->744K(11776K)] 9847K->752K(39424K), 0.0022270 secs 总计:39424 KB 免费:36457 KB 总计:39424 KB 免费:34409 KB 总计:39424 KB 免费:32361 KB 总计:39424 KB 免费:30313 KB GC (分配失败) [PSYoungGen: 9102K->616K(11776K)] 9110K->632K(39424K), 0.0018849 secs 总计:39424 KB 免费:36564 KB GC 日志清楚地表明,每次 JVM 在年轻代中找不到合适的内存区域时,都会自动启动一次 GC 操作。
我们分析一下日志:[GC(分配失败)[PSYoungGen: 9847K->744K(11776K)] 9847K->752K(39424K), 0.0022270 secs]
了解这个日志的一些知识
从 Java 官网上我们知道 Hotspot 中的 Heap(一种 JVM)由年轻代、老年代和永久代组成。 (提醒: Java 8 中移除了永久生成,而引入了元空间 )
年轻代是所有新对象被分配和老化的地方。 当年轻代填满时,这会导致次要垃圾收集。 在我们的日志中可以观察到这次 GC,一旦 GC 完成,年轻代的可用空间大小会大大增加。
所有次要垃圾收集都是“停止世界”事件。 这意味着所有应用程序线程都将停止,直到操作完成。 在 GC 上花费的时间也会打印在日志中,在我们的示例中:
Java 中的完整垃圾回收
现在让我们对源代码进行一些更改。 这次我分配了 1GB。
VM 参数相应地更改为 -Xmx6000m。
日志:
Java中的最后一次测试
现在我将选项 Xmx6000m 更改为 Xmx900m,并且按预期引发了 OutOfMemoryError 异常。 从日志中我们知道,GC 仍然尽最大努力寻找可用内存进行分配,但失败了。
ABAP 中的垃圾收集
执行以下程序两次,第一次使用手动 clear = abap_false,第二次使用 abap_true。
PARAMETERS: clear TYPE c as CHECKBOX DEFAULT abap_false.
TYPES: tt_table TYPE TABLE OF tadir WITH KEY pgmid object.
DATA: lt_result TYPE TABLE OF tadir,
lt_total TYPE TABLE OF tadir,
lr_result TYPE REF TO tt_table.
DATA: c1 TYPE cursor.
OPEN CURSOR @c1 FOR SELECT * FROM tadir.
DO.
WRITE: / |Round: { sy-index } | COLOR COL_NEGATIVE.
CREATE DATA lr_result.
FETCH NEXT CURSOR @c1 INTO TABLE @lr_result->* PACKAGE SIZE 800000.
IF sy-subrc <> 0.
EXIT.
ENDIF.
APPEND LINES OF lr_result->* TO lt_total.
cl_abap_memory_utilities=>get_memory_size_of_object( EXPORTING object = lt_total
IMPORTING
bound_size_alloc = DATA(bound_alloc)
bound_size_used = DATA(bound_used) ).
WRITE: / 'bound alloc:' , bound_alloc.
WRITE: / 'bound used:' , bound_used.
cl_abap_memory_utilities=>get_total_used_size( IMPORTING size = DATA(lv_before_size) ).
WRITE: / |Total size before GC: { lv_before_size }| COLOR COL_POSITIVE.
IF clear = abap_true.
CLEAR: lr_result->*, lt_result, lr_result.
ENDIF.
cl_abap_memory_utilities=>do_garbage_collection( ).
cl_abap_memory_utilities=>get_total_used_size( IMPORTING size = DATA(lv_after_size) ).
WRITE: / |Total size after GC: { lv_after_size }| COLOR COL_GROUP.
DATA(rate) = ( lv_before_size - lv_after_size ) * 100 / lv_before_size.
WRITE: / |Freed rate: { rate }%| COLOR COL_TOTAL.
ENDDO.
WRITE: / lines( lt_total ).
比较两次执行的输出,我们发现最终填充的结果 lt_total 对于两次执行都是相等的。 而用于存储每次迭代时从数据库表中查询到的数据的临时变量lr_result->*、lt_result和lr_result,一旦内容附加到最终结果内表后就没有用了,原则上可以手动清除。 比较表明,如果在一个循环中你有一个局部变量,它保存了大量的数据,这些数据只在当前循环中处理,那么你可以在下一轮循环开始释放内存之前手动清除它。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vehtBAFO-1598236863158)(https://upload-images.jianshu.io/upload_images/2085791-f48a0fb39770eaa8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
您可能会争辩说,下面的行在比较过程中真的生效了吗?
cl_abap_memory_utilities=>do_garbage_collection()。
好问题! 如果它被注释掉并使用手动 clear = abap_true 执行报告,仍然会打印出与注释掉之前相同的输出。 那么让我们看另一个例子:
REPORT z.
PARAMETERS: call_gc type char1 as CHECKBOX DEFAULT abap_false.
class lcl_memory_test DEFINITION.
public SECTION.
methods: constructor.
PRIVATE SECTION.
data: mt_data type TABLE OF tadir.
endclass.
class lcl_memory_test IMPLEMENTATION.
method: constructor.
SELECT * INTO TABLE mt_data FROM tadir.
endmethod.
endclass.
START-OF-SELECTION.
data(lo_class) = new lcl_memory_test( ).
cl_abap_memory_utilities=>get_total_used_size( IMPORTING size = DATA(lv_before_size) ).
WRITE: / |Total size before GC: { lv_before_size }| COLOR COL_POSITIVE.
CLEAR: lo_class.
IF call_gc = abap_true.
cl_abap_memory_utilities=>do_garbage_collection( ).
ENDIF.
cl_abap_memory_utilities=>get_total_used_size( IMPORTING size = DATA(lv_after_size) ).
WRITE: / |Total size after GC: { lv_after_size }| COLOR COL_GROUP.
DATA(rate) = ( lv_before_size - lv_after_size ) * 100 / lv_before_size.
WRITE: / |Freed rate: { rate }%| COLOR COL_TOTAL.
这次我将代码包装起来,使用开关 call_gc 手动触发 GC:
先用 call_gc = abap_false 执行,结果显示即使手动清除了 lo_class 的引用,仍然没有释放内存。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OoCoEmUZ-1598236863159)(https://upload-images.jianshu.io/upload_images/2085791-3f0498690e66df0a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
第二次 call_gc = true。 GC 处理器做得很好!
最后但同样重要的是,这个 ABAP 示例的目的不是要求您在应用程序代码中手动调用 GC API,相反,您应该始终避免这样做。