关于
IRIS/Caché
进程内存溢出解决方案
IRIS/Caché
进程内存溢出解决方案
描述
在IRIS/Caché
中,进程内存溢出错误是指一个进程(例如运行中的作业JOB
、应用程序或服务)在执行过程中消耗了超过其分配内存限制的内存资源,导致操作系统无法继续为其提供足够的内存空间。这可能会导致进程崩溃、运行缓慢或产生不稳定的行为。
原因
进程内存溢出错误可能由以下情况引起:
-
内存泄漏:进程中的代码在使用完内存后未正确释放,导致内存逐渐耗尽,最终导致溢出。
-
大量数据处理:处理大量数据时,如果未及时释放已使用的内存,可能会导致内存耗尽。
-
递归调用:递归调用未正确终止或控制,可能导致堆栈溢出,最终影响进程的内存。
在IRIS/Caché
中,会提示<STORE>
错误。
相关系统变量
在讲原理之前需要先了解两个系统变量$STORAGE
与$ZSTORAGE
$ZSTORAGE
$ZSTORAGE
包含进程私有内存的最大内存量(以KB
为单位)。
USER>w $zs
262144
可以在Portal
中更改最大每进程内存(K
B)系统配置设置来更改$ZSTORAGE
默认值。默认值为262144KB
,256M
,如下截图:
$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
减少到小于或接近与零,则会在某个时刻发生<store>
错误。例如,如果$STORAGE
减少到40
,则为另一个局部变量分配存储可能会由于<store>
错误而失败,这表明没有足够的可用存储空间来存储局部变量值或建立新的堆栈级别。
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
实际上<STORE>
内存错误,可以通过设置陷阱来捕获异常。但是进入陷阱时,并不会主动释放内存。示例如下:
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
当<store>
错误发生并用陷阱进行拦截时,系统会自动为进程提供1MB
的额外内存,用与错误处理。系统不会更改$ZSTORAGE
的大小,允许$STORAGE
进一步进入负数值。示例如下:
当第一个<store>
错误发生时,系统在内部将进程指定为内存不足状态。而在此低内存状态下,该进程可以继续分配内存,并且$STORAGE
的值可以继续减少到更低的负数。在此低内存状态下,进程可能会释放一些已分配的内存,从而导致$STORAGE
的值上升。因此,$STORAGE
的值可以在一个值范围内上升或下降,而不会发出额外的<store>
错误。
注:第一个<store>
错误提供了一些内存缓冲,允许进程调用诊断、执行磁盘保存、正常退出、释放内存并继续。
注:当发生第一个<store>
错误进程会消耗额外的内存。当$STORAGE
的值达到-1048576
时,会出现第二个<STORE>
错误。如果进程发生第二个<STORE>
错误,则没有更多的内存可供该进程使用,并且进一步的进程操作将变得不可预测。这个进程会立即终止。
注:如果在陷阱当中发生内存再次溢出,进程是直接关闭的。即使设置了$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
}
解决方案
要解决内存不足的问题首先要做到以下几点:
-
在使用多维数组时,要设置陷阱进行异常拦截。
-
在发生内存溢出时,在陷阱中将多维数据
kill
删除掉,恢复内存。 -
如果发生了二次内存溢出。需要讲多维数组方法声明为单独的方法。用于外层接口调用,进行陷阱二次拦截。
示例如下:
注:$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——抛出<STORE>错误,因为进程超过了$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——抛出<STORE>错误,因为进程超过了$ZSTORAGE值,并且$ZSTORAGE值被自动扩展了1M。",!
}
w "第二次内存溢出错误:" _ $ze,!
l -^yx("node"):3
w "解锁",!
q $ze
}
根据上图可观察到,在外层陷阱进行拦截到第二次内存溢出错误时,可发现系统为我们自动清理了内存,并将内存恢复到$ZSTORAGE
最大值268302712
。并且也成功解锁。
注:上面示例是为了解决内存二次溢出的情况,通过这种方式防止进程直接结束,实际上应该在第一次内存溢出时,就应该将多维数组进行kill
恢复内存。
注:实际上不应该在陷阱当中写入过多的业务逻辑,导致二次内存溢出。
以上是个人对IRIS/Caché
进程内存溢出的一些理解,由于个人能力有限,欢迎大家提出意见,共同交流。