关于`IRIS/Caché`进程内存溢出解决方案

文章目录

  • 关于`IRIS/Caché`进程内存溢出解决方案
    • 描述
    • 原因
    • 相关系统变量
      • `$ZSTORAGE`
      • `$STORAGE`
    • 什么情况下进程内存会变化?
    • 内存不足原理
    • 解决方案

关于 IRIS/Caché进程内存溢出解决方案

描述

IRIS/Caché中,进程内存溢出错误是指一个进程(例如运行中的作业JOB、应用程序或服务)在执行过程中消耗了超过其分配内存限制的内存资源,导致操作系统无法继续为其提供足够的内存空间。这可能会导致进程崩溃、运行缓慢或产生不稳定的行为。

原因

进程内存溢出错误可能由以下情况引起:

  • 内存泄漏:进程中的代码在使用完内存后未正确释放,导致内存逐渐耗尽,最终导致溢出。

  • 大量数据处理:处理大量数据时,如果未及时释放已使用的内存,可能会导致内存耗尽。

  • 递归调用:递归调用未正确终止或控制,可能导致堆栈溢出,最终影响进程的内存。

IRIS/Caché中,会提示错误。

相关系统变量

在讲原理之前需要先了解两个系统变量$STORAGE$ZSTORAGE

$ZSTORAGE

$ZSTORAGE包含进程私有内存的最大内存量(以KB为单位)。

USER>w $zs
262144

可以在Portal中更改最大每进程内存(KB)系统配置设置来更改$ZSTORAGE默认值。默认值为262144KB256M,如下截图:

关于`IRIS/Caché`进程内存溢出解决方案_第1张图片

$STORAGE

$STORAGE返回可用于当前进程中的变量存储的字节数。 $STORAGE的初始值由$ZSTORAGE的值确定,该值是该进程可用的最大内存量。 $ZSTORAGE值(以千字节为单位)越大,$STORAGE值(以字节为单位)越大。但$ZSTORAGE$STORAGE之间的关系不是绝对的1:1

USER>w $s
268313368
USER>w $s/1024
262024.7734375
USER>w $s/1024/1024
255.8835678100585938

什么情况下进程内存会变化?

  • 使用set命令,进程内存$STORAGE会减少。
ClassMethod Store()
{
	w $s,!
	s name = "yx"
	w $s,!
	s age = 18
	w $s,!
}

注:可观察到减小的数量为8的倍数。也就是说以字节为单位进行递减。

USER>d ##class(M.Store).Store()
268307096
268307088
268307080
  • 重复局部变量不会减小进程内存$STORAGE
ClassMethod Store1()
{
	w $s,!
	s name = "yx"
	w $s,!
	s age = 18
	w $s,!
	s name = "yx"
	w $s,!
	s age = 18
	w $s,!
	w name,!
	w $s,!
	w age,!
	w $s,!
}
USER>d ##class(M.Store).Store1()
268306968
268306960
268306952
268306952
268306952
yx
268306952
18
268306952
  • 局部变量名称大小对进程内存$STORAGE没有影响,并不是说变量名称越长所占内存越多。
ClassMethod Store2()
{
	w $s,!
	s name = "yx"
	w $s,!
	s namenamenamenamenamename = "yx"
	w $s,!
	s namenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamenamename = "yx"
	w $s,!
}
USER>d ##class(M.Store).Store2()
268306968
268306960
268306952
268306944
  • 局部变量所保存的值影响进程内存$STORAGE
ClassMethod Store3()
{
	w $s,!
	s name = "yx"
	w $s,!
	s name = "yxyxyxyx"
	w $s,!
	s name = "yxyxyxyxyxyxyxyx"
	w $s,!
	s name = "yxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyx"
	w $s,!
}

注:可观察到字符串越长,所占用的内存越多。

USER>d ##class(M.Store).Store3()
268306968
268306960
268306936
268306904
268306840
  • 多维数组会影响进程内存$STORAGE,下标越多占用的内存越多。
ClassMethod Store4()
{
	w $s,!
	s array(1) = 1
	w $s,!
	s array(2) = 2
	w $s,!
	s array(3) = "yxyx"
	w $s,!
	s array(4) = "yxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyx"
	w $s,!
}
USER>d ##class(M.Store).Store4()
268306968
268306896
268306888
268306680
268306552
  • 删除局部变量,多维数组,可以使进程内存$STORAGE增加或恢复。
ClassMethod Store5()
{
	w $s,!
	s name = "yx"
	w $s,!
	s name = "yxyxyxyx"
	w $s,!
	k name
	w "删除局部变量后:" _ $s,!
	
	w $s,!
	s array(1) = 1
	w $s,!
	s array(2) = 2
	w $s,!
	s array(3) = "yxyx"
	w $s,!
	s array(4) = "yxyxyxyxyxyxyxyxyxyxyxyxyxyxyxyx"
	w $s,!
	k array
	w "删除多维数组后:" _ $s,!
}
USER>d ##class(M.Store).Store5()
268306968
268306960
268306936
删除局部变量后:268306968
268306968
268306896
268306888
268306680
268306552
删除多维数组后:268306968
  • 使用NEW命令时,$STORAGE会减少。因为N会增加堆栈级别,也就意味发生堆栈级别改变的命令都会使内存减少。
ClassMethod Store6()
{
	w $s,!
	n
	w $s,!
	n
	w $s,!
	n
	w $s,!
}

注:第一次使用New命令时,减少13336,后续的New命令均减少12228

USER>d ##class(M.Store).Store6()
268306968
268293632
268281344
268269056
  • 块语法IF、ELES、FOR、TRY、CATCH命令不会使用$STORAGE会减少。
ClassMethod Store7()
{
	w $s,!
	if 1 {
		w $s,!
		if 1 {
			w $s,!
		} 
	} else {
		w $s,!
	}
	w $s,!
	
	try {
		w $s,!
	} catch {
		w $s,!
	}
	w $s,!
	
	for {
		w $s,!
		b
	}
}

USER>d ##class(M.Store).Store7()
268288144
268288144
268288144
268288144
268288144
268288144
268288144
 
  b
  ^
<BREAK>zStore7+19^M.Store.1
USER 3f2>
  • $STORAGE值不受设置的进程私有Global、与临时Global影响。
ClassMethod Store8()
{
	w $s,!
	k ^yx
	w $s,!
	k ^||yx
	w $s,!
	s ^yx("name") = "yx"
	w $s,!
	s ^yx("age") = 18
	w $s,!
	s ^||yx("address") = "china"
	w $s,!
	s ^||yx("work") = "码农"
	w $s,!
}
USER>d ##class(M.Store).Store8()
268287376
268287376
268287376
268287376
268287376
268287376
268287376
  • 局部变量字符串长度与进程内存关系, 每增加4个字符会增加8Byte内存空间大小,无论中文英文字符。
ClassMethod Store9()
{
	w "英文所占内存大小:",!
	s str = ""
	w 0 _ " :" _ str _ ":" _$s ,!
	for i = 1 : 1 : 40 {
		s str = str _ "x"
		w i _ " :" _ str _ ":" _$s ,!
	}


	w "中文所占内存大小:",!
	w $s,!
	s name = "姚"
	w $s,!
	s name = "姚鑫"
	w $s,!
	s name = "姚鑫姚"
	w $s,!
	s name = "姚鑫姚鑫"
	w $s,!
	s name = "姚鑫姚鑫姚"
	w $s,!
	s name = "姚鑫姚鑫姚鑫"
	w $s,!
	s name = "姚鑫姚鑫姚鑫姚"
	w $s,!
	s name = "姚鑫姚鑫姚鑫姚鑫"
	w $s,!
	s name = "姚鑫姚鑫姚鑫姚鑫姚鑫姚鑫"
	w $s,!
	s name = "姚鑫姚鑫姚鑫姚鑫姚鑫姚鑫姚鑫姚鑫"
	w $s,!
}
USER> d ##class(M.Store).Store9()
英文所占内存大小:
0 ::268311440
1 :x:268311432
2 :xx:268311432
3 :xxx:268311432
4 :xxxx:268311424
5 :xxxxx:268311424
6 :xxxxxx:268311424
7 :xxxxxxx:268311424
8 :xxxxxxxx:268311408
9 :xxxxxxxxx:268311408
10 :xxxxxxxxxx:268311408
11 :xxxxxxxxxxx:268311408
12 :xxxxxxxxxxxx:268311408
13 :xxxxxxxxxxxxx:268311408
14 :xxxxxxxxxxxxxx:268311408
15 :xxxxxxxxxxxxxxx:268311408
16 :xxxxxxxxxxxxxxxx:268311376
17 :xxxxxxxxxxxxxxxxx:268311376
18 :xxxxxxxxxxxxxxxxxx:268311376
19 :xxxxxxxxxxxxxxxxxxx:268311376
20 :xxxxxxxxxxxxxxxxxxxx:268311376
21 :xxxxxxxxxxxxxxxxxxxxx:268311376
22 :xxxxxxxxxxxxxxxxxxxxxx:268311376
23 :xxxxxxxxxxxxxxxxxxxxxxx:268311376
24 :xxxxxxxxxxxxxxxxxxxxxxxx:268311376
25 :xxxxxxxxxxxxxxxxxxxxxxxxx:268311376
26 :xxxxxxxxxxxxxxxxxxxxxxxxxx:268311376
27 :xxxxxxxxxxxxxxxxxxxxxxxxxxx:268311376
28 :xxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311376
29 :xxxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311376
30 :xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311376
31 :xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311376
32 :xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311312
33 :xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311312
34 :xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311312
35 :xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311312
36 :xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311312
37 :xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311312
38 :xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311312
39 :xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311312
40 :xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:268311312
中文所占内存大小:
268311312
268311304
268311304
268311304
268311296
268311296
268311296
268311296
268311280
268311280
268311248

内存不足原理

如上一节所述,我们知道了什么情况下内存会发生变化。这里我们模拟常见内存溢出的一种情况:

在处理大量数据时,使用多维数组用于分类或汇总数据,如果多维数据下标节点越来越多,那么使用的内存会越来越多。如果未及时释放已使用的内存,会导致内存耗尽。示例如下:

ClassMethod Store11()
{
	for {
		s yx($i(yx)) = $i(yx)
	}
	q $$$OK
}
USER> d ##class(M.Store).Store11()
 
  s yx($i(yx)) = $i(yx)
  ^
<STORE>zStore11+2^M.Store.1
USER 3f2>

原理:$STORAGE值可以是正数,也可以是负数。值为零并不表示没有可用存储,但表示存储极度短缺。如果$STORAGE减少到小于或接近与零,则会在某个时刻发生错误。例如,如果$STORAGE减少到40,则为另一个局部变量分配存储可能会由于错误而失败,这表明没有足够的可用存储空间来存储局部变量值或建立新的堆栈级别。

ClassMethod Store11()
{
	for {
		s yx($i(yx)) = $i(yx)
		if ($s < 1000){
			w $s,!
		}
	}
	q $$$OK
}

USER> d ##class(M.Store).Store11()
216
176
120
64
40
 
  s yx($i(yx)) = $i(yx)
  ^
<STORE>zStore11+2^M.Store.1

实际上内存错误,可以通过设置陷阱来捕获异常。但是进入陷阱时,并不会主动释放内存。示例如下:

ClassMethod Store12()
{
	s $zt = "Error"
	for {
		s yx($i(yx)) = $i(yx)
	}
	q $$$OK
Error
	s $zt = ""
	w "内存溢出时内存大小:" _ $s,!
	q $ze
}
USER> d ##class(M.Store).Store12()
内存溢出时内存大小:440

错误发生并用陷阱进行拦截时,系统会自动为进程提供1MB的额外内存,用与错误处理。系统不会更改$ZSTORAGE的大小,允许$STORAGE进一步进入负数值。示例如下:

关于`IRIS/Caché`进程内存溢出解决方案_第2张图片

当第一个错误发生时,系统在内部将进程指定为内存不足状态。而在此低内存状态下,该进程可以继续分配内存,并且$STORAGE的值可以继续减少到更低的负数。在此低内存状态下,进程可能会释放一些已分配的内存,从而导致$STORAGE的值上升。因此,$STORAGE的值可以在一个值范围内上升或下降,而不会发出额外的错误。

注:第一个错误提供了一些内存缓冲,允许进程调用诊断、执行磁盘保存、正常退出、释放内存并继续。

注:当发生第一个错误进程会消耗额外的内存。当$STORAGE的值达到-1048576时,会出现第二个错误。如果进程发生第二个错误,则没有更多的内存可供该进程使用,并且进一步的进程操作将变得不可预测。这个进程会立即终止。

注:如果在陷阱当中发生内存再次溢出,进程是直接关闭的。即使设置了$zt= "",也不管用,这种情况如果陷阱中有记录日志或有解锁操作等,将不会就即使,会发生锁无法即使释放。

如下示例,锁永远也不会得到释放。

ClassMethod Store14()
{
	s $zt = "Error"
	

	w "加锁",!
	l +^yx("node"):3
	
	for {
		s yx($i(yx)) = $i(yx)
	}
	q $$$OK
Error
	s $zt = ""
	w "内存溢出时内存大小:" _ $s,!
	for {
		s yx($i(yx)) = $i(yx)
		w $s,!
	}
	
	w "陷阱内存溢出,解锁失败",!
	l -^yx("node"):3
	q $ze
}

解决方案

要解决内存不足的问题首先要做到以下几点:

  1. 在使用多维数组时,要设置陷阱进行异常拦截。

  2. 在发生内存溢出时,在陷阱中将多维数据kill删除掉,恢复内存。

  3. 如果发生了二次内存溢出。需要讲多维数组方法声明为单独的方法。用于外层接口调用,进行陷阱二次拦截。

示例如下:

注:$SYSTEM.Process.MemoryAutoExpandStatus()方法可以提示出内存溢出的原因。

模拟二次内存溢出代码如下:

ClassMethod Store14()
{
	s $zt = "Error"
	

	w "加锁",!
	l +^yx("node"):3
	
	for {
		s yx($i(yx)) = $i(yx)
	}
	q $$$OK
Error
	s $zt = ""
	w "第一次内存溢出错误:" _ $ze,!
	w "陷阱错误代码:" _ $SYSTEM.Process.MemoryAutoExpandStatus(),!
	if ($SYSTEM.Process.MemoryAutoExpandStatus() = 1) {
		w "1——抛出错误,因为进程超过了$ZSTORAGE值,并且没有自动扩展$ZSTORAGE值。",!
	}
	b ; 陷阱
	for {
		s yx($i(yx)) = $i(yx)
		w $s,!
	}
	
	w "陷阱内存溢出,解锁失败",!
	l -^yx("node"):3
	q $ze
}

基于上述第三点,需要单独的方法去调用,用于外层接口调用,进行陷阱二次拦截。示例如下:

ClassMethod Store15()
{
	
	s $zt = "Error"
	w "开始时内存:"_ $s,!
	d ..Store14()
	
	w "结束时内存:"_ $s,!
	q $$$OK
Error
	s $zt = ""
	w "陷阱中剩余:"_ $s,!
	w "陷阱错误代码:" _ $SYSTEM.Process.MemoryAutoExpandStatus(),!
	if ($SYSTEM.Process.MemoryAutoExpandStatus() = 2) {
		w "2——抛出错误,因为进程超过了$ZSTORAGE值,并且$ZSTORAGE值被自动扩展了1M。",!
	}
	w "第二次内存溢出错误:" _ $ze,!
	l -^yx("node"):3
	w "解锁",!
	
	q $ze
}

关于`IRIS/Caché`进程内存溢出解决方案_第3张图片

根据上图可观察到,在外层陷阱进行拦截到第二次内存溢出错误时,可发现系统为我们自动清理了内存,并将内存恢复到$ZSTORAGE最大值268302712。并且也成功解锁。

注:上面示例是为了解决内存二次溢出的情况,通过这种方式防止进程直接结束,实际上应该在第一次内存溢出时,就应该将多维数组进行kill恢复内存。

注:实际上不应该在陷阱当中写入过多的业务逻辑,导致二次内存溢出。

以上是个人对IRIS/Caché进程内存溢出的一些理解,由于个人能力有限,欢迎大家提出意见,共同交流。

你可能感兴趣的:(M,使用方案,IRIS,Caché,M,内存溢出,解决方案)