android 图片转BASE64上传提示java.lang.OutOfMemoryError

android 图片转BASE64上传提示java.lang.OutOfMemoryError,第1张

本篇文章我们讲解缓存区 ByteBuf 八大主要类型中两种,未池化堆缓冲区 UnpooledHeapByteBuf 和 未池化不完全堆缓冲区 UnpooledUnsafeHeapByteBuf

UnpooledHeapByteBuf 是 java 堆缓冲区的实现,而且它推荐使用 UnpooledByteBufAllocatorheapBuffer(int, int) , Unpooledbuffer(int) 和 UnpooledwrappedBuffer(byte[]) 方式创建 UnpooledHeapByteBuf ,而不是直接调用它的构造方法 new 出来。

有三个成员属性:

它有两个构造方法,一个是创建的时候没有内容,一个创建的时候就带有内容数据。

分配新的字节数组。

替换缓存区的字节数组 array ,必须将 tmpNioBuf 设置为 null 。

你会发现最后都是调用 HeapByteBufUtil 对应方法,这个类 HeapByteBufUtil 我们后面再说。

根据目标缓存区 dst 类型不同,使用的方式也不同。

FileChannel 就是 GatheringByteChannel 的子类。

都是调用 HeapByteBufUtil 对应方法,这个类 HeapByteBufUtil 我们后面再说。

我们知道这个方法是在 AbstractReferenceCountedByteBuf 类中定义的,当引用计数变成 0 的时候,就会调用这个 deallocate() 方法,释放持有的资源。

仔细阅读 UnpooledUnsafeHeapByteBuf 源码,你会发现这个类很简单,它是 UnpooledHeapByteBuf 的子类,与 UnpooledHeapByteBuf 区别就两个方面。

UnpooledUnsafeHeapByteBuf 是通过 PlatformDependentallocateUninitializedArray 创建数组,利用 Unsafe 来加快数据的访问。

UnpooledUnsafeHeapByteBuf 是通过 UnsafeByteBufUtil 工具类获取基本数据类型的数据。

先明确一个概念,什么是大端,什么是小端。

例如获取 short

就是通过右移位运算和 或 | 位运算,实现数的拼接。

利用左移位运算,将高位数据转换成 byte 类型存储;再使用 (byte) 类型强转,只保留低八位的数据存储。

本篇文章我们讲解缓存区 ByteBuf 八大主要类型中两种,未池化直接缓冲区 UnpooledDirectByteBuf 和 未池化不安全直接缓冲区 UnpooledUnsafeDirectByteBuf 。

UnpooledDirectByteBuf 一个基于 NIO ByteBuffer 的缓冲区。

建议使用 UnpooledByteBufAllocatordirectBuffer(int, int) , UnpooleddirectBuffer(int) 和 UnpooledwrappedBuffer(ByteBuffer) ;而不是显式调用构造函数。

有四个成员属性:

通过 allocateDirect(initialCapacity) 方法创建一个新的 NIO 缓存区实例来初始化此缓存区对象。

利用现有的 NIO 缓存区创建此缓存区。

通过 NIO 缓存区 buffer 对应方法获取基本数据类型数据。

根据目标缓存区 dst 类型不同,处理的方式也不同。

你会发现这些方法都是获取此缓存区对应 NIO 缓存区 ByteBuffer 对象,调用 ByteBuffer 对象的方法,与 IO 流的交互,进行数据传输

和 get 系列方法一样, set 系列的实现也是靠 NIO 缓存区 ByteBuffer 对应方法。

剩余方法也几乎都是和 NIO 缓存区 ByteBuffer 有关,而且也不难,就不做过多介绍了。

UnpooledDirectByteBuf 主要是通过 NIO 缓存区 buffer 来存储数据。而它获取和设置数据,也都是通过 NIO 缓存区对应方法实现的。

光看介绍,和 UnpooledDirectByteBuf 没有任何区别。它也是 UnpooledDirectByteBuf 的子类。

那么 UnpooledUnsafeDirectByteBuf 和 UnpooledDirectByteBuf 不同处在那里呢

通过复习 setByteBuffer 方法,获取 NIO 缓存区 buffer 对应的直接内存地址。

通过 UnsafeByteBufUtil 对应方法,直接从内存地址获取对应基本类型数据。

通过 UnsafeByteBufUtil 对应方法,直接向内存地址设置对应基本类型数据。

只有这个类型 hasMemoryAddress() 方法才会返回 true 。

UnpooledUnsafeDirectByteBuf 就是通过直接从内存地址中获取和设置数据的方式,提高性能。

用while语句求 。

用传统流程图和N-S结构流程图表示算法,见图:

main()

{

int i,sum=0;

i=1;

while(i<=100)

{

sum=sum+i;

i++;

}

printf("%d\n",sum);

}

ByteBuf底层是一个字节数组,内部维护了两个索引:readerIndex与writerIndex。其中0 --> readerIndex部分为可丢弃字节,表示已被读取过,readerIndex --> writerIndex部分为可读字节,writerIndex --> capacity部分为可写字节。ByteBuf支持动态扩容,在实例化时会传入maxCapacity,当writerIndex达到capacity且capacity小于maxCapacity时会进行自动扩容。

ByteBuf子类可以按照以下三个纬度进行分类:

在进入内存分配核心逻辑前,我们先对Netty内存分配相关概念做下了解。Netty内存管理借鉴jemalloc思想,为了提高内存利用率,根据不同内存规格使用不同的分配策略,并且使用缓存提高内存分配效率。

Netty有四种内存规格,tiny表示16B ~ 512B之间的内存块,samll表示512B ~ 8K之间的内存块,normal表示8K ~ 16M的内存块,Huge表示大于16M的内存块。

Chunk是Netty向 *** 作系统申请内存的单位,默认一次向 *** 作系统申请16M内存,Netty内部将Chunk按照Page大小划分为2048块。我们申请内存时如果大于16M,则Netty会直接向 *** 作系统申请对应大小内存,如果申请内存在8k到16M之间则会分配对应个数Page进行使用。如果申请内存远小于8K,那么直接使用一个Page会造成内存浪费,SubPage就是对Page进行再次分配,减少内存浪费。

如果申请内存小于8K,会对Page进行再次划分为SubPage,SubPage大小为Page大小/申请内存大小。SubPage又划分为tiny与small两种。

负责管理从 *** 作系统中申请到的内存块,Netty为了减少多线程竞争arena,采用多arena设计,arena数量默认为2倍CPU核心数。线程与arena关系如下:

线程本地缓存,负责创建线程缓存PoolThreadCache。PoolThreadCache中会初始化三种类型MemoryRegionCache数组,用以缓存线程中不同规格的内存块,分别为:tiny、small、normal。tiny类型数组缓存的内存块大小为16B ~ 512B之间,samll类型数组缓存的内存块大小为512B ~ 8K之间的内存块,normal类型数组缓存的内存块大小受DEFAULT_MAX_CACHED_BUFFER_CAPACITY配置影响,默认只缓存8K、16K、32K三种类型内存块。

内存块缓存容器,负责缓存tiny、small、normal三种内存块。其内部维护一个队列,用于缓存同种内存大小的内存块。

负责管理从 *** 作系统申请的内存,内部采用伙伴算法以Page为单位进行内存的分配与管理。

负责管理Chunk列表,根据内存使用率,分为:qInit、q000、q025、q050、q075、q100六种。每个PoolChunkList中存储内存使用率相同的Chunk,Chunk以双向链表进行关联,同时不同使用率的PoolChunkList也以双向列表进行关联。这样做的目的是因为随着内存的分配,Chunk使用率会发生变化,以链表形式方便Chunk在不同使用率列表进行移动。

PoolSubpage负责tiny、small类型内存的管理与分配,实现基于SLAB内存分配算法。PoolArena中有两种PoolSubpage类型数组,分别为:tinySubpagePools、smallSubpagePools。tinySubpagePools负责管理tiny类型内存,数组大小为512/16=32种。smallSubpagePools负责管理small类型内存,数组大小为4。

PoolSubpage数组中存储不同内存大小的PoolSubpage节点,相同大小节点以链表进行关联。PoolSubpage内部使用位图数组记录内存分配情况。

Netty通过ByteBufAllocator进行内存分配,ByteBufAllocator有两个实现类:PooledByteBufAllocator与UnpooledByteBufAllocator,其中,是否在堆内存或者直接内存分配与是否使用unsafe进行读写 *** 作都封装在其实现类中。

我们先看下ByteBufAllocator类图:

PooledByteBufAllocator与UnpooledByteBufAllocator内存分配类似,可以通过newHeapBuffer与newDirectBuffer进行分配内存,我们以PooledByteBufAllocator为例分析下内存分配流程:

以PooledByteBufAllocator为例来分析下内存分配器实例化过程。首先调用PooledByteBufAllocator#DEFAULT方法实例化PooledByteBufAllocator

PooledByteBufAllocator实例化时会初始化几个比较重要的属性:

最终会调用PooledByteBufAllocator如下构造方法:

PooledByteBufAllocator构造方法主要做了两件事情,一是:初始化PoolThreadLocalCache属性,二是:初始化堆内存与直接内存类型PoolArena数组,我们进入PoolArenaDirectArena构造方法,来分析下PoolArena初始化时主要做了哪些事情:

DirectArena构造方法会调用其父类PoolArena构造方法,在PoolArena构造方法中会初始化tiny类型与small类型PoolSubpage数组,并初始化六种不同内存使用率的PoolChunkList,每个PoolChunkList以双向链表进行关联。

以分配直接内存为例,分析内存分配的主要流程:

PooledByteBufAllocator#directBuffer方法最终会调用如下构造方法,其中maxCapacity为IntegerMAX_VALUE:

该方法主要分三步,第一步:获取线程缓存,第二步:分配内存,第三步:将ByteBuf转为具有内存泄漏检测功能的ByteBuf,我们来分析下每一步具体做了哪些事情:

1获取线程缓存,从PoolThreadLocalCache中获取PoolThreadCache,首次调用会先进行进行初始化,并将结果缓存下来:

初始化方法在PoolThreadLocalCache中,首先会循环找到使用最少的PoolArena,然后调用PoolThreadCache构造方法创建PoolThreadCache:

PoolThreadCache构造方法中会初始化tinySubPageDirectCaches、smallSubPageDirectCaches、normalDirectCaches这三种MemoryRegionCache数组:

createSubPageCaches方法中会创建并初始化MemoryRegionCache数组,其中tiny类型数组大小为32,small类型数组大小为4,normal类型数组大小为3:

最终会调用MemoryRegionCache构造方法进行创建,我们看下MemoryRegionCache结构:

2分配内存,首先会获取PooledByteBuf,然后进行内存分配:

newByteBuf方法会尝试从对象池里面获取pooledByteBuf,如果没有则进行创建。allocate方法为内存分配核心逻辑,主要分为两种分配方式:page级别内存分配(8k 16M)、subPage级别内存分配(0 8K)、huge级别内存分配(>16M)。page与subPage级别内存分配首先会尝试从缓存上进行内存分配,如果分配失败则重新申请内存。huge级别内存分配不会通过缓存进行分配。我们看下allocate方法主要流程:

首先尝试从缓存中进行分配:

cacheForTiney方法先根据分配内存大小定位到对应的tinySubPageDirectCaches数组中MemoryRegionCache,如果没有定位到则不能在缓存中进行分配。如果有则从MemoryRegionCache对应的队列中d出一个PooledByteBuf对象进行初始化,同时为了复用PooledByteBuf对象,会将其缓存下来。

如果从缓存中分配不成功,则会从对应的PoolSubpage数组上进行分配,如果PoolSubpage数组对应的内存大小下标中有可分配空间则进行分配,并对PooledByteBuf进行初始化。

如果在PoolSubpage数组上分配不成功,则表示没有可以用来分配的SubPage,则会尝试从Page上进行分配。先尝试从不同内存使用率的ChunkList进行分配,如果仍分配不成功,则表示没有可以用来分配的Chunk,此时会创建新的Chunk进行内存分配。

进入PoolChunk#allocate方法看下分配流程:

allocateRun方法用来分配大于等于8K的内存,allocateSubpage用来分配小于8K的内存,进入allocateSubpage方法:

内存分配成功后会调用initBuf方法初始化PoolByteBuf:

Page级别内存分配和SubPage级别类似,同样是先从缓存中进行分配,分配不成功则尝试从不同内存使用率的ChunkList进行分配,如果仍分配不成功,则表示没有可以用来分配的Chunk,此时会创建新的Chunk进行内存分配,不同点在allocate方法中:

因为大于16M的内存分配Netty不会进行缓存,所以Huge级别内存分配会直接申请内存并进行初始化:

调用ByteBuf#release方法会进行内存释放,方法中会判断当前byteBuf 是否被引用,如果没有被引用, 则调用deallocate方法进行释放:

进入deallocate方法看下内存释放流程:

free方法会把释放的内存加入到缓存,如果加入缓存不成功则会标记这段内存为未使用:

recycle方法会将PoolByteBuf对象放入到对象池中:

Connection reset by peer的常见原因:

1)服务器的并发连接数超过了其承载量,服务器会将其中一些连接关闭;

如果知道实际连接服务器的并发客户数没有超过服务器的承载量,则有可能是中了病毒或者木马,引起网络流量异常。可以使用netstat -an查看网络连接情况。

2)客户关掉了浏览器,而服务器还在给客户端发送数据;

3)浏览器端按了Stop;

这两种情况一般不会影响服务器。但是如果对异常信息没有特别处理,有可能在服务器的日志文件中,重复出现该异常,造成服务器日志文件过大,影响服务器的运行。可以对引起异常的部分,使用try…catch捕获该异常,然后不输出或者只输出一句提示信息,避免使用eprintStackTrace();输出全部异常信息。

4)防火墙的问题;

如果网络连接通过防火墙,而防火墙一般都会有超时的机制,在网络连接长时间不传输数据时,会关闭这个TCP的会话,关闭后在读写,就会导致异常。 如果关闭防火墙,解决了问题,需要重新配置防火墙,或者自己编写程序实现TCP的长连接。实现TCP的长连接,需要自己定义心跳协议,每隔一段时间,发送一次心跳协议,双方维持连接。

5)JSP的buffer问题。

JSP页面缺省缓存为8k,当JSP页面数据比较大的时候,有可能JSP没有完全传递给浏览器。这时可以适当调整buffer的大小。

以上就是关于android 图片转BASE64上传提示java.lang.OutOfMemoryError全部的内容,包括:android 图片转BASE64上传提示java.lang.OutOfMemoryError、Netty--handler的执行顺序、Netty源码_UnpooledHeapByteBuf详解等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/9285669.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-26
下一篇2023-04-26

发表评论

登录后才能评论

评论列表(0条)

    保存