wmproxy

wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子

项目地址

国内: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

关于栈Stack

Stack可以被认为是一堆书。当我们添加更多的书时,我们将它们添加到栈的顶部。当我们需要一本书时,我们从上面拿一本。

  • 添加数据称为压入栈
  • 移除数据称为弹出栈

    这种现象在编程中被称为后进先出(LIFO)。

    存储在栈上的数据在编译时必须具有固定的大小。默认情况下,Rust在栈上为原始类型分配内存。所有存储在堆栈上的数据必须具有已知的固定大小。未知数据编译时的大小或可能更改的大小必须存储在堆中而不是栈中。

关于堆Heap

与栈相反,大多数情况下,我们需要将变量(内存)传递给不同的函数,并使它们保持比单个函数执行更长的时间。这就是我们可以使用heap的时候。

堆的组织性较差:当您将数据放在堆上时,您会请求一个一定的空间。内存分配器在堆中找到一个空位这是足够大的,标志着它正在使用,并返回一个指针,就是那个地方的地址此过程称为在堆,有时缩写为分配(将值推到堆栈不被认为是分配的)。因为指向堆的指针是已知的,固定大小的,你可以把指针存储在堆栈上,但是当你想要的时候,实际数据,您必须遵循指针。想象一下坐在一个餐厅当你进入时,你说明你的小组人数,主人会找到一张适合所有人的空桌子,然后把你带到那里。如果如果你的团队中有人迟到了,他们可以问你坐在哪里,找到你。

栈与堆对比

  • 分配到栈比在堆上分配更快,因为分配器永远不必搜索存储新数据的位置;该位置总是在栈的顶部。相比之下,在堆上分配空间需要更多的工作,因为分配器必须首先找到足够大的空间,保存数据,然后进行簿记,为下一次配置。
  • 在堆中访问数据比访问栈上的数据慢,因为你得跟着指示牌走。因为访问堆需要得到相应的指示牌,然后再根据相应的指示牌去寻找相应的位置,然后还要确定位置所占的大小。
statck栈 heap堆
在栈中存储数据的速度更快。 在堆中存储数据的速度较慢。
管理栈中的内存是可预测的,也是微不足道的。 管理堆的内存(任意大小)是非常重要的。
Rust堆栈默认分配。 Box用于分配到堆。
函数的基元类型和局部变量在栈上分配。 大小动态的数据类型,如StringVectorHashMapBox等,在heap上分配。

栈与堆的分配示例

让我们通过一个例子来直观地了解内存是如何在堆栈上分配和释放的。

fn foo() {
let y = 999;
let z = 333;
} fn main() {
let x = 111; foo();
}

在上面的例子中,我们首先调用函数main()main()函数有一个变量绑定x

Address地址 Name名称 Value值
0 x 111

在表中,“地址”列指的是RAM的内存地址。它从0开始,并转到您的计算机有多少RAM(字节数)。“名称”列是指变量,“值”列是指变量的值。

foo()被调用时,一个新的栈帧被分配。foo()函数有两个变量绑定,yz

Address地址 Name名称 Value值
2 z 333
1 y 999
0 x 111

数字0、1和2不使用计算机实际使用的地址值。实际上,地址根据值由一定数量的字节分隔。

foo()完成后,其栈帧被释放。

Address地址 Name名称 Value值
0 x 111

main()完成后,其栈帧被释放。Rust自动在堆栈中分配和释放内存。

与堆栈相反,大多数情况下,我们需要将变量(内存)传递给不同的函数,并使它们保持比单个函数执行更长的时间。这就是我们可以使用heap的时候。

我们可以使用Box<T>类型在堆上分配内存。比如说,

fn main() {
let x = Box::new(100);
let y = 222; println!("x = {}, y = {}", x, y);
}

让我们可视化在上面的例子中调用main()时的内存。

Address地址 Name名称 Value值
0 x ??? addr
1 y 222

和前面一样,我们在堆栈上分配两个变量x和y。

然而,当调用x时,Box::new()的值被分配在堆上。因此,x的实际值是指向堆的指针。

Address地址 Name名称 Value值
578 100
... ... ...
0 x -> 578
1 y 222

这里,变量x保存指向地址→578,这是用于演示的任意地址。堆可以以任何顺序分配和释放。因此,它可能会以不同的地址结束,并在地址之间产生漏洞。

因此,当x消失时,它首先释放堆上分配的内存。

Address地址 Name名称 Value值
... ... ...
1 y 222

一旦main()完成,我们释放堆栈帧,所有东西都消失了,释放了所有内存。

如何排查问题

堆内存的排查

关于堆内存的排查,堆内存的内存量比较大,因此数值相对会大很多,堆内存的大小通常小到几M,大到几个G,所以在堆内存排查的时候可以用宏观的内存管理器,有以下几种方法

  • TOP查看内存,也可以通过调用系统的api,
  • memory-stats实时查看进程当前占用内存数:
use memory_stats::memory_stats;

fn main() {
if let Some(usage) = memory_stats() {
println!("Current physical memory usage: {}", usage.physical_mem);
println!("Current virtual memory usage: {}", usage.virtual_mem);
} else {
println!("Couldn't get the current memory usage :(");
}
}
  • 可以自定义Alloc,因为Rust提供的全局global_alloc,我们可以通过自定义Alloc计算当前申请的内存数,以及可以用这种方式检查内存泄漏,典型的jemalloc就是通过这种方式来的,我们用这种方式实现简单的内存统计,我们定义了一个Trallocator

use std::alloc::{GlobalAlloc, Layout};
use std::sync::atomic::{AtomicU64, Ordering}; pub struct Trallocator<A: GlobalAlloc>(pub A, AtomicU64); unsafe impl<A: GlobalAlloc> GlobalAlloc for Trallocator<A> {
unsafe fn alloc(&self, l: Layout) -> *mut u8 {
self.1.fetch_add(l.size() as u64, Ordering::SeqCst);
self.0.alloc(l)
}
unsafe fn dealloc(&self, ptr: *mut u8, l: Layout) {
self.0.dealloc(ptr, l);
self.1.fetch_sub(l.size() as u64, Ordering::SeqCst);
}
} impl<A: GlobalAlloc> Trallocator<A> {
pub const fn new(a: A) -> Self {
Trallocator(a, AtomicU64::new(0))
} pub fn reset(&self) {
self.1.store(0, Ordering::SeqCst);
}
pub fn get(&self) -> u64 {
self.1.load(Ordering::SeqCst)
}
}

我们通过调用该类,实现

use std::alloc::System;

// 这句使全局的的分配器变成我们自己的分配器
#[global_allocator]
static GLOBAL: Trallocator<System> = Trallocator::new(System); fn main() {
GLOBAL.reset();
println!("memory used: {} bytes", GLOBAL.get());
GLOBAL.reset();
{
let mut vec = vec![1, 2, 3, 4];
for i in 5..20 {
vec.push(i);
println!("memory used: {} bytes", GLOBAL.get());
}
println!("{:?}", v);
} println!("memory used: {} bytes", GLOBAL.get());
}

我们可以得到以下输出:

memory used: 0 bytes
memory used: 32 bytes
memory used: 32 bytes
memory used: 32 bytes
memory used: 32 bytes
memory used: 64 bytes
memory used: 64 bytes
memory used: 64 bytes
memory used: 64 bytes
memory used: 64 bytes
memory used: 64 bytes
memory used: 64 bytes
memory used: 64 bytes
memory used: 128 bytes
memory used: 128 bytes
memory used: 128 bytes
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
memory used: 0 bytes

可以看到分配完之后已经及时释放

栈内存的排查

因为系统提供的栈内存通常只有8m左右,且Rust中的线程的默认栈内存只有2M,如果分配过大的栈内存将会导致栈溢出,比如

fn main() {
let bad = [0;10240000];
}

就会出现如下提示

thread 'main' has overflowed its stack
fatal runtime error: stack overflow

在现在的方法中,我并未找到有合适的检查当前进程占用的栈内存数。

  • 测试用alloc看是否能测出栈内存:
use std::alloc::System;
#[global_allocator]
static GLOBAL: Trallocator<System> = Trallocator::new(System); fn main() {
GLOBAL.reset();
println!("memory used: {} bytes", GLOBAL.get());
GLOBAL.reset();
let x = 0;
let bad = [0;10240];
println!("memory used: {} bytes", GLOBAL.get());
}

运行上述程序,如下输出:

memory used: 0 bytes
memory used: 0 bytes

程序无法感知到栈内存的变化。

  • 测试用memory-stats实时查看内存
use memory_stats::memory_stats;

fn main() {

    if let Some(usage) = memory_stats() {
println!("初始内存 usage: {}", usage.physical_mem);
} else {
println!("Couldn't get the current memory usage :(");
} let value1 = vec![10;102400]; std::thread::sleep(std::time::Duration::from_secs(1)); if let Some(usage) = memory_stats() {
println!("申请堆内存后 usage: {}", usage.physical_mem);
} else {
println!("Couldn't get the current memory usage :(");
} let value = [10;102400]; std::thread::sleep(std::time::Duration::from_secs(1));
if let Some(usage) = memory_stats() {
println!("申请栈内存后 usage: {}", usage.physical_mem);
} else {
println!("Couldn't get the current memory usage :(");
} }

以上程序会输出:

初始内存     usage: 1024000
申请堆内存后 usage: 1478656
申请栈内存后 usage: 1478656

我们可以感知到堆内存的变化,无法感知到栈内存的变化。

  • 目前找到的可以测量类对象的栈内存值。可以用std::mem::size_of_val来测量类对象占用的栈内存大小,我们可以通过该方法进行栈大小的排查,看是否存在超级大的占用栈的对象,如果存在,需将其移动到堆,也就是用Box进行包裹。
fn main() {
let x = 0u32;
assert_eq!(4, std::mem::size_of_val(&x));
let val = vec![0u64;9999];
assert_eq!(24, std::mem::size_of_val(&val)); let mut hash = HashMap::new();
hash.insert(1, 2);
assert_eq!(48, std::mem::size_of_val(&hash));
hash.insert(2, 4);
assert_eq!(48, std::mem::size_of_val(&hash));
}

我们来分析下Vec的内存,为什么其占用大小为24个字节(64位的机器)

pub struct Vec<T, A: Allocator = Global> {
buf: RawVec<T, A>, /// 需要再进行类的分析
len: usize, /// 占用64位,也就是8个字节
} pub(crate) struct RawVec<T, A: Allocator = Global> {
ptr: Unique<T>, /// 指针大小,占用64位,8字节
cap: usize, /// 容量大小,占用64位,8字节
alloc: A, /// 分配器,不占用栈内存
}

综上分析,每个Vec的栈大小占用内存均为24字节。程序测试一致。同样HashMap占用的栈大小均为48个字节,不受其Map大小的影响。

注意:如果用异步的Future的包围,如果返回的对象也就是Furture<Output=xxx>的栈大小过大,很容易在递进处理异步的情况下直接栈溢出,而此时完全还未执行到该函数,造成一种很难排查的景象

注意!!!异步的返回值千万栈大小不要过大!不要过大!不要过大!

  • 另外还有一种是递归的函数调用,也会造成栈溢出,这类问题相对好定位:
fn f(x: i32) {
f(1);
}
fn main() {
f(2);
}

直接会显示

thread 'main' has overflowed its stack
fatal runtime error: stack overflow

小结

所以在排查内存泄漏还是排查栈大小时都需要对当前的数据进行分析,需要处理的东西较多,需要有比较好的耐心去处理,一步步的去排查推进。记得异步返回的Output如果过大,会导致代码还未执行,但已经栈溢出的情况。

点击 [关注][在看][点赞] 是对作者最大的支持

32. 干货系列从零用Rust编写正反向代理,关于堆和栈以及如何解决stack overflow的更多相关文章

  1. (转)Spring Boot干货系列:(七)默认日志logback配置解析

    转:http://tengj.top/2017/04/05/springboot7/ 前言 今天来介绍下Spring Boot如何配置日志logback,我刚学习的时候,是带着下面几个问题来查资料的, ...

  2. 【转】Spring Boot干货系列:(一)优雅的入门篇

    转自Spring Boot干货系列:(一)优雅的入门篇 前言 Spring一直是很火的一个开源框架,在过去的一段时间里,Spring Boot在社区中热度一直很高,所以决定花时间来了解和学习,为自己做 ...

  3. Spring Boot干货系列:(八)数据存储篇-SQL关系型数据库之JdbcTemplate的使用

    Spring Boot干货系列:(八)数据存储篇-SQL关系型数据库之JdbcTemplate的使用 原创 2017-04-13 嘟嘟MD 嘟爷java超神学堂 前言 前面几章介绍了一些基础,但都是静 ...

  4. Spring Boot干货系列:(七)默认日志框架配置

    Spring Boot干货系列:(七)默认日志框架配置 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 今天来介绍下Spring Boot如何配置日志logback,我刚学习的时候, ...

  5. Spring Boot干货系列:(五)开发Web应用JSP篇

    Spring Boot干货系列:(五)开发Web应用JSP篇 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 上一篇介绍了Spring Boot中使用Thymeleaf模板引擎,今天 ...

  6. Spring Boot干货系列:(四)Thymeleaf篇

    Spring Boot干货系列:(四)Thymeleaf篇 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 Web开发是我们平时开发中至关重要的,这里就来介绍一下Spring Boo ...

  7. Spring Boot干货系列:(一)优雅的入门篇

    Spring Boot干货系列:(一)优雅的入门篇 2017-02-26 嘟嘟MD 嘟爷java超神学堂   前言 Spring一直是很火的一个开源框架,在过去的一段时间里,Spring Boot在社 ...

  8. Java多线程干货系列—(四)volatile关键字

    原文地址:http://tengj.top/2016/05/06/threadvolatile4/ <h1 id="前言"><a href="#前言&q ...

  9. Spring Boot干货系列:(十二)Spring Boot使用单元测试(转)

    前言这次来介绍下Spring Boot中对单元测试的整合使用,本篇会通过以下4点来介绍,基本满足日常需求 Service层单元测试 Controller层单元测试 新断言assertThat使用 单元 ...

  10. (转)Spring Boot干货系列:(四)开发Web应用之Thymeleaf篇

    转:http://tengj.top/2017/03/13/springboot4/ 前言 Web开发是我们平时开发中至关重要的,这里就来介绍一下Spring Boot对Web开发的支持. 正文 Sp ...

随机推荐

  1. 节能减排 | AIRIOT智慧工厂节能管理解决方案

    工厂作为高能耗的生产型企业,降低能耗和提升资源利用率方面就显得很重要,对实施国家倡导的节能降耗.绿色发展有着很大程度上的必要性.然而,工厂能源管理从传统手段向智能化升级转型的过程中,企业也不可避免的面 ...

  2. 聊聊分布式 SQL 数据库Doris(三)

    详细内容阅读: Apache Doris 分区分桶新功能 与 数据划分. 在此基础上做总结与延伸. 在 Doris 的存储引擎规则: 表的数据是以分区为单位存储的,不指定分区创建时,默认就一个分区. ...

  3. 【新手入门】JDBC+Servlet+jsp【新闻系统的增删改查】:持续学习,持续优化,争取做一个真正的新闻系统出来!

    新闻的增删改查 学习了一些博客知识[结尾会贴上,大家可以参考]在借鉴的基础上又进行了代码的优化,从而实现了CRUD,下面分享我的代码过程: 包结构,是为了方便管理,所以我们要这样进行设计: 在数据库进 ...

  4. JavaWeb项目中web.xml配置文件<servlet-class>…</servlet-class>中的路径出现问题以及服务器错误的解决办法

    问题如图 原因: 1.改变了 WEB-INF 文件夹下 lib 文件夹下 servlet-api.jar 的路径2.缺失lib文件夹下的 servlet-api.jar,没有添加到库中 解决办法: 不 ...

  5. 构建满足流批数据质量监控用火山引擎DataLeap

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 面对今日头条.抖音等不同产品线的复杂数据质量场景,火山引擎 DataLeap 数据质量平台如何满足多样的需求?本文 ...

  6. 悲观锁、乐观锁、mybatis-plus实现乐观锁

    悲观锁.乐观锁.mybatis-plus实现乐观锁 转载自:www.javaman.cn 1.悲观锁.乐观锁 乐观锁和悲观锁是两种用于处理并发操作的数据锁定策略.它们在处理多个事务尝试同时访问和修改同 ...

  7. Chrome扩展开发实战:快速填充表单

    大家好,我是 dom 哥.我正在写关于 Chrome 扩展开发的系列文章,感兴趣的可以 点个小星星 . 填表单是打工人经常面对的场景,作为一个前端,我经常开发一些PC端的页面,它们主要由表单和表格构成 ...

  8. Roaring bitmaps

    Roaring bitmaps 最近看一篇文章,里面涉及到使用roaring bitmaps来推送用户广告并通过计算交集来降低用户广告推送次数.本文给出roaring bitmaps的原理和基本用法, ...

  9. 数字孪生与GIS结合,为智慧交通带来的改变

    在当代社会,交通问题已经成为城市发展中的一个重要挑战.交通拥堵.安全隐患.环境污染等问题给人们的出行带来了许多不便和困扰.然而,随着数字孪生技术与地理信息系统(GIS)的融合,我们迎来了智慧交通的新时 ...

  10. int和String的相互转换