安卓已经进入12GB时代,为什么iPhone 13还用4GB?

因为Android原生语言(first class)是Java,而iOS原生语言是object-c。

同样的app,Android版总是iOS版的2~4倍。

其中matmul:m列比较的是两个1000X1000的矩阵乘法,其中GCC编译的代码使用了31.7M内存, 而Java使用了67.1M。内存使用量是C的两倍以上。

而dict:m列比较的是在5百万个字符串中,每个不同字符串的出现次数。其中GCC用了56.2M内 存,Java用了314.8M。内存使用量是C的五倍以上。

在这里给大家推荐一个编程交流群693186131.bug君是我知乎账号

其中,C使用的内存数量为1.17MB,Java使用了6.01MB,内存使用量超过C的5倍。

事实上,这个2~4倍的关系是普遍的。任何平台,无论Linux、Mac OS还是Windows,Java程序 占用的内存都要比C/C++程序大2~4倍。

换句话说,C/C++ 512M内存能跑出的效果,Java经常就需要2G。

而obj-c是c的超集,它实际上是C++出现前、类似于C front的一个C with Class实现。关于 C/C++的大部分讨论也适用于它。

obj-c 2.0版本开始引入了GC(垃圾收集),但在iOS上,这个GC并不能使用

而且,自OS X 10.8之后,苹果引入了ARC(自动引用计数),然后不再推荐使用GC(甚至声明将 在未来版本中弃用GC)。

Android目前内存大小已普遍12G,而iPhone 13“才”4GB——这两个数字看起来悬殊,实际上 呢,Android最大内存仅仅是iPhone 13的仅仅3倍:这都未必够Java挥霍的。

当然,Android也有很多native应用,主要集中在各种游戏上面,这类应用的内存占用和编写它们 的语言相关。

那么,为什么Java如此“能吃”呢?

很简单,为了降低使用门槛,它替程序员做了太多决定——尤其是,为了迁就那些不会管理资源的 程序员,Java标配GC。 而GC,说白了就是空间换便利。

不仅如此。C/C++语言委员会习惯锱铢必较每一个数据结构用到的每一个字节,这种抠门行为,每一个熟悉 C/C++相关标准/库的程序员都如数家珍……

这是C/C++系语言的通病。也是这一系语言入门门槛较高的一个重要原因。

因此,C/C++系程序员差不多也都有“不多占用一个字节”的强迫症——在下就是重度内存小抠强 迫症患者,恨不得能union的全都union了……

而Java呢?一切皆对象。

尤其是,为了支援很多“高级特性”——包括但不限于为实现反射、序列化以及GC索引等等特性而 不得不“预埋”在对象中的字段(其中obj-c普遍采用了引用计数,这会稍微增大每个对象的大小, 因为需要默认埋入一个ref-count字段;但相对于GC还是好得多,因为对象会在出作用域后第一时 间销毁)——Java对象会默认占据相当数量的内存:为了缓解这个问题,它才不得不做了妥协、允 许程序员使用“危险”的原始数字、只在必要时再把integer “装箱”成消耗巨大的对象。

当然,程序员工资比内存贵;而且……用户机器上的内存我又不需要掏钱,对吧? 那,不是不用白不用吗?

最终,就是你们所见的现状:同样的APP,在Android上就是需要iOS版2~4倍的内存。

或者,说的更准确点:在任何平台上,Java之类带GC语言写的程序,就是需要C/C++之类无GC语 言2~4倍的内存(当然,如果小心优化过、或者程序本身较简单,内存使用倍率也可能小于2倍;但 无论如何总是会多一些:再加上其它方面的问题,最终倒逼google取消了Java在Android平台的、 绝对的first class待遇,开放了NDK,这才有效的缓解了大型程序[主要是游戏]的性能问题;但绝大 多数Android平台应用仍然基于Java)。

这是GC语言的锅。它选择了“降低程序员的使用门槛和内存管理的负担”,那么就必然要在其它地 方付出代价。这个代价,就是2~4倍的内存占用。

最后再说一下两个点,一是为什么GC必然导致内存浪费,二是为什么Java的内存浪费问题为何如此 突出。

先说GC为何必然导致内存浪费。 这是GC的原理所决定的。

对于C/C++系其它语言,它们没有GC,至多有一个“引用计数”;于是,它们是“用到才申请、 用后立即释放”——释放不及时,那就成“内存泄漏”了。对这些语言来说,内存泄漏是相当严重 的程序bug。

“引用计数”的问题,一个是无法处理循环引用;另一个是分配/释放过于频繁(第一时间!)。GC彻底解决了引用计数的问题;但解决方案是“(全局的)标记-回收”。

这个方案的消耗显然是非常大的,毕竟每跑一次就要把程序中所有的变量都遍历一遍、标记一遍, 这才能知道是不是应该回收了。如果每个变量出作用域都要触发GC,那这消耗就实在太可怕了—— 引用计数出作用域就改一次计数值都被黑的不要不要的;GC敢每次扫一遍整个程序,岂不是……

那么,什么时候跑标记-回收过程呢?但跑的太不频繁了……内存消耗就更可怕了。

过去,Java的搞法是:永远不跑GC,直到内存达到限定值、或者宿主机内存即将耗尽;于是stop world,回收了回收了……

但这样的一次stop world停顿实在太大、太影响用户体验了;尤其一些对响应时间敏感的应用,这 等于直接破坏了程序功能。

所以后来就搞的稍微“激进”了一些,有空就跑,提前跑,尽早回收,那么内存占用和stop world 问题就没那么可怕了。 但,还是前面那句话:你不能跑太频繁,别说CPU占用常年100%了,常年占用30~40%,那发 热,那耗电……还能看吗?所以,最终还是必须拿内存容量换“程序员不用自己管内存”这点便利。

因此,无论如何优化,有GC的语言写出来的应用必然大量消耗内存;哪怕有“内存即将用尽就触发 GC”甚至“每隔100ms或更短时间就跑一次GC”也无济于事——它无论如何都需要更多的内存。

注意是“需要更多的内存”,可不是什么“看起来占了其实没占,只要有需要我就跑GC了叫什么 叫”——只要平均给每个应用的内存总量小于某个阈值,“频繁触发GC”问题就一定会出现。因为 大家都虚胖,都在挤人……

下一个问题是:C#/go等语言也都有GC啊,它们的内存占用问题怎么就没有Java那么突出、那么可怕?

答案是:Java是老前辈,是第一个吃GC螃蟹的开拓者。为了宣传GC、突出自己的优点,它刻意忽 视了GC的弊端、把一些极其不好的行为习惯推荐给自己的程序员;甚至,它自身也无视了各种弊 端,以纯粹的、绝无掺杂的面向对象语言自矜——而这,其实是一个错误设计方向。

先说不好的行为习惯。正常来说,一个程序员应该事先确定算法需要的内存、提前设计数据结构、统一管理堆内存(包括 按需申请和及时释放);比如C#/go的“最佳编程实践”相关建议里面,就提倡“做好规划,少用 new、尽量不在循环中new”——没有随地狗狗拉屎一样的动辄new几个字节这样的污染,那么自 然就没有海量小对象需要回收:从一开始就别制造垃圾,你还需要费劲巴拉的给程序员擦屁股吗?

但Java呢?为了强调自己的GC优势,它故意怂恿程序员,让他们“需要就new,用完就忘掉”——它甚至都没 有栈对象、在引起了极大的性能问题后、才在相当靠后的版本里添加了逃逸分析。换句话说,它不仅是在怂恿程序员搞破坏,甚至是在逼他们浪费内存!

这种错误的内存使用策略使得Java中充斥着毫无必要的new;拉的屎多,将来收拾起来自然就难。

相比之下,c#/go就反复告诫自己的程序员,让他们不要随地大小便;同时也设计了各种策略、允 许他们不随地大小便。

这也是可以理解的。因为Java那时候,业界对GC这种新生事物还心存疑虑,所以它需要强调GC的 优势、甚至鼓励他们滥用;而到了C#/go时代,GC已经被大多数程序员所接受,于是就可以劝他们 节制了。

但,滥用GC这种心态,反过来也影响了Java团队自己,使得他们从一开始就走向了错误设计方向、 背上了沉重包袱。

此外,Java最大的问题就在于——它诞生于面向对象大流行时代,为了标榜自己的高贵与纯种,它 要求……不,强制,一切皆对象。

为了一切皆对象,它甚至魔怔到连integer都要搞成对象——但这些基本数据类型搞成对象实在太过 劳民伤财,无论时间还是空间都是个彻头彻尾的悲剧;于是,它又搞了复杂的装箱/拆箱机制……

连基本的数据类型都成了对象,函数之类的东西就更不用说了。

这就使得它极其的笨拙、带来了极大的心智负担——以至于有人吐槽,说(一些)设计模式不是什 么高明的抽象,而是为了给Java的设计缺陷擦屁股:很多在其它语言里面直截了当就可以做的,在 Java里面就必须用设计模式七绕八转的做。

这种笨拙反过来,又把程序逻辑切的七零八碎、使得整体上控制内存使用更加困难。稍不小心,你 就得迷失在层层委托的迷宫中,哪里还能知道“我这个算法实现需要多少内存、在哪里可以提前准 备好免得不停的临时分配”呢?

而C#/go就没有那么大的负担。至少它们不怕学习C++或其它优秀语言的长处——当然,它们也有 自己的取舍、自己的短处。

最后,为了规避某些魔怔人,不得不声明一下:我只是客观指出Java这种语言在内存管理和设计思 想上的某些缺陷而已,请不要解读为“捧一踩一”。

它在这方面就是有缺陷。就好像C++有一万种编程范式以至于搞的它的程序员无所适从、从此“精 通C++”成了全世界都在玩的梗一样;也好像C#/go掌握在某些大公司手中的、很多地方不可控一 样——没有十全十美的东西,甚至有些缺陷是为了某种特别好处而不得不付出的代价;但缺陷就是 缺陷。

知道了缺陷的存在,哪怕Java,一样可以写出内存占用没那么可怕的程序——很简单,学学 C#/C++的“最佳实践”,更合理的使用内存,不要学狗狗拉屎一样到处new就行了。

充分认识、并想办法超越一门语言的固有缺陷,这其实也是程序员能力的一种体现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值