在这篇文章中,我们将为您详细介绍「译」开发者如何提升和推销自己的内容,并且讨论关于如何激发开发者选项的相关问题。此外,我们还会涉及一些关于Android开发者,如何提升自己的职场竞争力?、jvm系列(
在这篇文章中,我们将为您详细介绍「译」开发者如何提升和推销自己的内容,并且讨论关于如何激发开发者选项的相关问题。此外,我们还会涉及一些关于Android 开发者,如何提升自己的职场竞争力?、jvm系列(七):如何优化Java GC「译」、「译」Android原生开发的现状与未来、「译」Go 安全性备忘单:Go 开发者的 8 个安全性最佳实践的知识,以帮助您更全面地了解这个主题。
本文目录一览:- 「译」开发者如何提升和推销自己(如何激发开发者选项)
- Android 开发者,如何提升自己的职场竞争力?
- jvm系列(七):如何优化Java GC「译」
- 「译」Android原生开发的现状与未来
- 「译」Go 安全性备忘单:Go 开发者的 8 个安全性最佳实践
「译」开发者如何提升和推销自己(如何激发开发者选项)
原文:10 steps to becoming the developer everyone wants
作者:Andrew C. Oliver
译文:开发者如何提升和推销自己
译者:wangguo
编写出伟大的代码,对于开发者来说已经到达一个层级了。 但是不断提升、适当的推销自己,可以令你得到更多。 以下是一些有效的途径,希望能够帮到你。
1. 写博客
建立自己的博客,并至少每月发布一篇文章。 文章中要尽量有一些你真正的研究项目,这样会确保你看起来有些水平。
2. 参与开源
投身开源领域,发布自己的开源项目。 别人可以通过你开放的源代码,来了解你的水平和方向。 并试着在你的项目中,用最简单的方式来解决复杂的问题。
3. 不要频繁跳槽,也不要举步不前
不要每 6 个月就换一次工作,那样你什么都得不到。 但也不要停留在一个地方,做同样的工作 10 年,那样你会被模式化。 比如在IBM,你不能只会用 IBM 的方式编写 IBM 的栈。 我不会雇用在 IBM 或类似组织工作超过 1、2 年的,他们往往在面试中会给我留下印象,但他们往往无法通过编程测试。
4. 放眼未来,着手实际
年轻的开发者往往倾向于新的技术,Ruby 是我最喜欢的语言,但是它的市场要远远小于 Java。 同时,也不要停留在一个技术上太久,因为未来的你会相当于现在的 COBOL 或 PowerBuilder 开发者。
5. 写你自己的文档
我之前参加的项目中,很多次都被拉进项目经理级别的会议上,就是因为我写了一个他们能够看到和理解的文档或演示。
6. 简洁是灵魂
项目中的事情或问题,试图给出最短、最简洁的答案或解决方案。 一旦你的回复过于冗长或复杂,往往会令人不知所云。
7. 在公共场合演讲
试着在公共场合演讲或介绍一个项目。 研究一个主题,让自己至少有一个专家头衔。 如果你的演讲使别人感兴趣,那么效果会更好。
8. 成为现实主义者
你应该知道一种以上的语言和知识,包括新的、热门的话题。 永远不要说“我不会编程,除非使用 Erlang”类似的话。 比如,你认为 NoSQL 可能更适合你的小项目,但是你的公司不会在一个小型的、一次性的系统上使用它,这种情况下,关系型数据库会更适合。
9. 擅于使用工具解决疑难杂症
投入时间去学习一些其他人一般不知道的工具,这样可以让你比身边的人更有效率。
10. 保持谦虚
这应该成为你的基本技能。 你可能做了某一个工作,让你获得了 Geek 称号,但下周该称号可能就会消失。 所以,不要傲娇,时刻保持一个谦虚的态度。 借用《搏击俱乐部》中 Tyler Durden 的一句话“You are not special”。
怎么样才算成功?
看看你的左右,如果他们在和你做同样的工作,说明你还没有到达这一地步。 如果你坐在他们中间,他们都在看着你,告诉你你的演讲很棒,并很重视你的意见,恭喜你,你已经做到了。
但是,这些名誉和成功可能很短暂。 此外,比较讽刺的是,当你成为一个比较抢手的开发者时,你的编码时间也会越来越少。
总体来说,不是每个软件开发者都能成为优秀的那一个,但是有效的自我推销者会比安静的人更容易得到这个机会。 前提是,你也必须锻炼好自己的基本技能。
Android 开发者,如何提升自己的职场竞争力?
前言
该文章是笔者参加 Android 巴士线下交流会成都站 的手写讲稿虚拟场景,所以大家将就看一下。
开始
大家好,我是刘世麟,首先感谢安卓巴士为我们创造了这次奇妙的相遇。现场的氛围也让我十分激动。
职场竞争力,从来都是我们关注的话题,所以我今天给大家分享一下:《Android 初学者,如何快速提升职场竞争力》。
在今年 4 月,全球互联网引来了一个历史性的转折点:随着流量分析工具 StatCounter 的报告,Android 首次超越 Windows,成为用户访问互联网最常用的操作系统!Android 不仅在移动领域,在全操作系统内,都成为了当之无愧的霸主!无论你有志于成为领先企业的 Android 工程师,还是想成为改变世界的独立开发者,我希望你们为此而骄傲!!!
即便如此,大家觉得 Android 开发就业形势可观吗?
答案是不可观。我这绝对不是危言耸听,随着近年来 O2O 公司的大幅度倒闭,互联网公司烧钱严重,很多公司根本烧不起,所以很多开发者随着企业的倒闭再次卷入了求职的浪潮之中。
我想请问一下,在场的各位,现在用的共享单车都用的是哪家的?
想必基本都是小黄车和小橙车。现在 ofo 和膜拜两家独大,前段时间还能看到的悟空单车现在已经销声匿迹,因为它烧不起钱,ofo 和膜拜一个月免费骑行足以让它直接倒下,而就职于悟空单车的 Android 程序员可能因此被辞职。
Android 行业发展年限并不久,所以最开始接触 Android 的那一批人,现在极有可能还在 Android 领域,再加上近年来各大培训机构和高校向社会中注入了很大一部分的 Android 开发人才,所以造成了现在供大于求的窘境。可能有人经历或者听说过 14 年 Android 会写一个 ListView 的适配器就能找到不错的工作吧,你现在会用这些能找到工作吗?
所以这个时候,出色的开发能力能让你的开发职业生涯更加顺风顺水。
下面我想结合我的经历给大家讲一下几点:
如何系统化学习 Android 技术?(Android 知识体系)
企业到底需要什么样的 Android 开发人才?(Android 市场行情)
Android 学习的方式都有哪些?(Android 学习方式)
对 Android 学习和提升的一些建议。(Android 提升建议)
Android 知识体系
重点看我们 PPT 中的图。在 Android 开发的过程中,需要很多很多的知识,而有些知识也很难为其分类,在这里,我把它们分为上述五个部分:UI、架构、性能、NDK、其他,其他则可以细化为代码风格,学习能力,第三方库等。
UI方面
首先是 UI 方面,我相信大多数人都是开发 Android 应用的,所以 UI 的知识,必不可少。UI 一共分为三部分:绘制、布局和触摸反馈。要想写出漂亮的 UI 搭配动画,这需要花费巨量的时间,可能已经有特别多的朋友和我一样,在一直跟进扔物线(朱凯)的 HenCoder 系列了。如果你可以写出非常漂亮的控件或者动画框架并开源出来,我相信,你找份好工作已经没有问题了。架构方面
这个方面是卡年限的一个东西。随着工作的需要以及阅历(看代码)的增长,我们总是会越来越不满意自己写的代码模块,比如我,就深受这个的苦恼,我经常会出现三个月前写的代码,自己都看不下去,强迫症的我,一定会把它翻写的。但如果项目周期短的你,千万不要像我一样做。
当你不断改写封装自己的代码的时候,你可能就已经涉及到了架构方面的知识啦。架构方面的知识主要包括设计思想,及其选择合适架构的能力,这两者通常来讲是相辅相成的。每一种新的架构或设计思想问世往往是用来解决实际问题的,他们解决问题的侧重点各不相同,因此在什么时候采用什么架构或设计就非常考验程序员的阅历了。通常这部分的知识学起来最为困难,因为他不仅仅是一种思想,如果你的代码量不多,或者接触的项目比较小的话,你往往会很少触及到这部分的知识,最后自然而言又忘了。
所以在这方面我建议大家在工作之余,多参考 GitHub 上开源的完整项目,也要多写点自己的项目,提升自己的代码量和架构能力。
性能方面
性能方面相信开发稍大型 APP 的工程师体会尤其深刻了。随着 APP 工程的体量越来越大,开发功能越来越多,你会发现原来非常流畅的 APP 可能会出现卡顿、OOM、ANR 等现象,除了前面韩神所提到的内存泄漏,可能你还会因为其他问题导致性能表现不够优秀。而这部分的只是相对比较零散,也很难总结,更多的就是你的经验积累,所以多年经验的程序猿更受企业的青睐。NDK方面
这一方面的知识比较特殊,很多 Android 开发工程师在初中级阶段可能都不会涉及到。在开发中我们可能只会在特定的领域才会用到 NDK 进行开发,由于我也没遇到过,所以暂不多讲。
Android 市场分析
总体上,目前市面上可以将互联网公司分为两类:
没有实体产品的,以软件产品为主的互联网公司(其中相当一部分为外包);
有实体产品的,将硬件和软件结合起来的互联网公司。
我们暂且把它们称呼为 A 类和 B 类。
A 类公司往往偏向于 UI 及其架构,性能稍次,并且大多数中小型企业会要求应聘者能熟练掌握市场上流行的开源库以节约开发成本。这类公司开发的 APP 往往是纯虚拟的产品 —— 社交通讯、各类工具、资源聚合等。这些产品因为竞争激烈,功能需求变化也快,所以版本迭代的速度会非常快。这个时候,一个好的架构就可以节省许多时间,它能快速的响应需求,并迅速添加新的功能模块。UI 也是重点,在两个 APP 功能差不多的情况下,拼的就是用户体验了,其中用户最能感受到的就是两点,首先是界面是否美观,动画是否自然;其次才是 APP 是否流畅。
A 类公司对人才的需求大概是这样。
而对于 B 类公司,他们因为有实体产品,因此往往会要求应聘者具有某种特殊的技能,例如蓝牙,WIFI等。
这类公司往往偏向于性能及 NDK,UI 稍次。在目前的大环境下,这类公司多数都在物联网产业链上,以智能硬件、智能家居、机器人居多,而这类公司的话,大部分其实都是传统企业转型而来,因此其开发的产品比较注重功能的完善及响应速度,UI 的话反而会稍微丑陋一些。毕竟消费者买的是实体产品,配套的 APP 就算再丑也得用不是么。
他们对人才的需求往往是这样的:
明确自己想去什么样的公司,或者对哪一类的业务更感兴趣,会有助于你更有的放矢地学习技能。
Android 学习方式?
我认为最主要的一点就是主观能动性,或者说兴趣,如果你对 Android 开发没有太大的兴趣,那么还是尽早换方向吧。有了兴趣,自然是确立一个比较正确的学习路线。据说我们参会的还有一些应届毕业生或者还没开始工作的朋友。可能还在学习中很迷茫,我这里先给大家说一下我的学习经历。
学习 Android 无非几种方式:
看书类
此类可能比较全面,但也意味着过时的问题,我不是非常提倡,但书籍是人类进步的阶梯,你们可以考虑。其实我也买了挺多相关书籍,但自从习惯在网络上学习后就不看了。线上课程直播或者录播
这个可能是初学者当下比较受欢迎的学习方式了,但所讲知识通常比较基础,而且学习速度慢,但效率确实挺高的。这也是我在入门 Android 的时候用的最多的方式。采用这种方式学习的时候通常会有一个毛病,就是喜欢边看边写,这是我不提倡的,我提倡看一段写一段。在前期你可以不用一整段都看完,以小节的方式学习后再写代码。前期可能你会觉得自己像在背代码一样,但我不可否认,这确实是效率挺高的方法。但真的太费时了,至少我在工作后没用过这种方案。看博文、看别人的开源项目、看官方文档、官方例子代码
这是我现在用的最多的一种学习提升方式。一般可以通过上面的看书或者看录播的方式学习到完整的基础组件和代码编写,但这时候你用基础的组件实现出来的东西美观度通常比较局限。但当你加入公司后,产品会设计出各种新颖的效果,而这些效果并不能通过原生控件实现,这时候,网上超大量的资源就涌现出来了。掌握 GitHub 正确的搜索姿势,通常能让你事半功倍。而这个时候,一个熟悉的英文阅读能力和理解能力将展现的淋漓尽致。随着开发经验的积累,你会发现你的开发速度直线飙升。
Android 提升建议
成为一名Android开发者很容易,但是要成为一名成功的、突出的Android开发者却不那么容易。这需要付出很多的努力,耐心,奉献和毅力才能做到。
我并没有什么捷径或者简单的方法来告诉你让你成为一名成功的开发者。但如果你愿意全身心投入并付出努力的话,你肯定会得到你应得的成功的。
应大家的请求,我这里给大家提供一些提升建议,仅供参考。
开始阅读更多的代码
大部分的开发者都不会抽时间去阅读其他开发者所写的代码,他们大多数时间只是花在写他们已经知道的事情上。但那并不利于你成长为一名优秀的开发者,也不会增长你的见识。我建议大家阅读同事的代码,让你对你们的项目更加了解,当你对公司的产品代码足够了解后,你给老板提加薪的时候才更有底气。
有的人会说,咱们公司就我一个 Android 开发者,我看谁的代码呢?GitHub 上那么多优秀的开源应用和类库,我建议你们去阅读并开始学习它们,每天 30 分钟的代码阅读量是个非常好的开始。你将会惊讶的发现,还有如此多美妙的东西是你没见过的。
克服对未知的害怕
Android 系统很大,非常大。你不可能在一两个月内完全掌握它。随着学习越深入,你会发现越多的内容需要学习。作为一名初学者,很容易也很正常地出现这种对未知恐惧的害怕。我也不例外,曾经我学习 RxJava 的时候,我看了很多很多的博文,包括扔物线朱凯的那篇出名的《给Android开发者的RxJava详解》,但持续了整整一个周,依然不懂,这时候觉得 RxJava 真是太难了。在这种时候,不妨先放下它一段时间,过些日子再去学习你会觉得如鱼得水的。比如我,前面就在GitHub 上开源了 RxJava2Examples(附带整个系列 RxJava 2 文章) 尝试去克服对未知的恐惧吧,学习那些你确实需要用到的东西,能够让你开始手头正在构建的应用,然后慢慢地开拓你的视野。尝试开始写博客
写博客或许是一件相当费时的事,或许你写的博客只是阅览了多篇文章后的总结而以,或许你写的博客一点都不深入,但但是!!!别人的永远是别人的,作为一个已经写了很多博文的我来说,我所收获到的东西,不是这点时间能比拟的。因为只有你足够懂了,你才能讲给别人听。开始贡献开源库
GitHub 上有很多很棒的开源库,但由于一些特殊的原因可能作者并没有时间维护它。尝试去为开源库贡献你的源码,你会得到非常非常非常大的反馈的。可能这里有的人也知道,我在去年有维护 ImagePicker 图片选择库,我在里面贡献了不少代码,虽然后面由于一些时间原因停止维护了,但我收获非常大。具体收获,这个只能意会。
除了为别人开源库贡献,你也可以开源自己的代码。图片压缩在项目中挺常用的,在编写我们公司的项目的时候,我自己写了图片压缩,后面觉得挺好用的,所以直接把它开源到了 GitHub 上。这个过程非常的 nice,不仅学习到了特别多的知识,而且通过大家的测试,我的开源库的伸展性更佳。开源的过程有很多的东西是需要你学习的,而且这有助于你成为一名优秀的开发者。
花一些时间来学习Android最佳实践
要使自己比其他开发者更加出众,并能构建出整洁美观而又功能完善的应用,你需要开始学习一些Android开发最佳实践。除此之外,你还应该多去关注市场发展,看看最近流行的库都是怎么用,并尝试阅读它们。
写在最后
本次我的分享也接近尾声了,感谢你们在百忙中花上一下午来这里聆听我们的宣讲,希望在接下来的日子,我们共同成长,一起进步!!!
jvm系列(七):如何优化Java GC「译」
本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作。
Sangmin Lee发表在Cubrid上的”Become a Java GC Expert”系列文章的第三篇《How to Tune Java Garbage Collection》,本文的作者是韩国人,写在JDK 1.8发布之前,虽然有些地方有些许过时,但整体内容还是非常有价值的。译者此前也看到有人翻译了本文,发现其中有许多错漏生硬和语焉不详之处,因此决定自己翻译一份,供大家分享。
本文是“成为Java GC专家”系列文章的第三篇,在系列的第一篇文章《理解Java GC》中,我们了解到了不同GC算法的执行过程、GC的工作原理、新生代和老年代的概念、JDK 7中你需要了解的5种GC类型以及每一种GC对性能的影响。
在系列的第二篇文章《如何监控Java GC》中笔者已经解释了JVM进行实时GC的原理、监控GC的方法以及可以使这一过程更加迅速高效的工具。
在第三篇文章中,笔者将基于实际生产环境中的案例,介绍几个GC优化的最佳参数设置。在此我们假设你已经理解了本系列前两篇文章的内容,因此为了更深入的理解本文所讲内容,我建议你在阅读本篇文章之前先仔细阅读这两篇文章。
GC优化是必要的吗?
或者更准确地说,GC优化对Java基础服务来说是必要的吗?答案是否定的,事实上GC优化对Java基础服务来说在有些场合是可以省去的,但前提是这些正在运行的Java系统,必须包含以下参数或行为:
- 内存大小已经通过-Xms和-Xmx参数指定过
- 运行在server模式下(使用-server参数)
- 系统中没有残留超时日志之类的错误日志
换句话说,如果你在运行时没有手动设置内存大小并且打印出了过多的超时日志,那你就需要对系统进行GC优化。
不过你需要时刻谨记一句话:GC tuning is the last task to be done.
现在来想一想GC优化的最根本原因,垃圾收集器的工作就是清除Java创建的对象,垃圾收集器需要清理的对象数量以及要执行的GC数量均取决于已创建的对象数量。因此,为了使你的系统在GC上表现良好,首先需要减少创建对象的数量。
俗话说“冰冻三尺非一日之寒”,我们在编码时要首先要把下面这些小细节做好,否则一些琐碎的不良代码累积起来将让GC的工作变得繁重而难于管理:
- 使用
StringBuilder
或StringBuffer
来代替String
- 尽量少输出日志
尽管如此,仍然会有我们束手无策的情况。XML和JSON解析过程往往占用了最多的内存,即使我们已经尽可能地少用String、少输出日志,仍然会有大量的临时内存(大约10-100MB)被用来解析XML或JSON文件,但我们又很难弃用XML和JSON。在此,你只需要知道这一过程会占据大量内存即可。
如果在经过几次重复的优化后应用程序的内存用量情况有所改善,那么久可以启动GC优化了。
笔者总结了GC优化的两个目的:
- 将进入老年代的对象数量降到最低
- 减少Full GC的执行时间
将进入老年代的对象数量降到最低
除了可以在JDK 7及更高版本中使用的G1收集器以外,其他分代GC都是由Oracle JVM提供的。关于分代GC,就是对象在Eden区被创建,随后被转移到Survivor区,在此之后剩余的对象会被转入老年代。也有一些对象由于占用内存过大,在Eden区被创建后会直接被传入老年代。老年代GC相对来说会比新生代GC更耗时,因此,减少进入老年代的对象数量可以显著降低Full GC的频率。你可能会以为减少进入老年代的对象数量意味着把它们留在新生代,事实正好相反,新生代内存的大小是可以调节的。
降低Full GC的时间
Full GC的执行时间比Minor GC要长很多,因此,如果在Full GC上花费过多的时间(超过1s),将可能出现超时错误。
- 如果通过减小老年代内存来减少Full GC时间,可能会引起
OutOfMemoryError
或者导致Full GC的频率升高。 - 另外,如果通过增加老年代内存来降低Full GC的频率,Full GC的时间可能因此增加。
因此,你需要把老年代的大小设置成一个“合适”的值。
影响GC性能的参数
正如我在系列的第一篇文章《理解Java GC》末尾提到的,不要幻想着“如果有人用他设置的GC参数获取了不错的性能,我们为什么不复制他的参数设置呢?”,因为对于不用的Web服务,它们创建的对象大小和生命周期都不相同。
举一个简单的例子,如果一个任务的执行条件是A,B,C,D和E,另一个完全相同的任务执行条件只有A和B,那么哪一个任务执行速度更快呢?作为常识来讲,答案很明显是后者。
Java GC参数的设置也是这个道理,设置好几个参数并不会提升GC执行的速度,反而会使它变得更慢。GC优化的基本原则是将不同的GC参数应用到两个及以上的服务器上然后比较它们的性能,然后将那些被证明可以提高性能或减少GC执行时间的参数应用于最终的工作服务器上。
下面这张表展示了与内存大小相关且会影响GC性能的GC参数
表1:GC优化需要考虑的JVM参数类型 | 参数 | 描述 |
---|---|---|
堆内存大小 | -Xms |
启动JVM时堆内存的大小 |
-Xmx |
堆内存最大限制 | |
新生代空间大小 | -XX:NewRatio |
新生代和老年代的内存比 |
-XX:NewSize |
新生代内存大小 | |
-XX:SurvivorRatio |
Eden区和Survivor区的内存比 |
笔者在进行GC优化时最常用的参数是-Xms
,-Xmx
和-XX:NewRatio
。-Xms
和-Xmx
参数通常是必须的,所以NewRatio
的值将对GC性能产生重要的影响。
有些人可能会问如何设置永久代内存大小,你可以用-XX:PermSize
和-XX:MaxPermSize
参数来进行设置,但是要记住,只有当出现OutOfMemoryError
错误时你才需要去设置永久代内存。
还有一个会影响GC性能的因素是垃圾收集器的类型,下表展示了关于GC类型的可选参数(基于JDK 6.0):
表2:GC类型可选参数GC类型 | 参数 | 备注 |
---|---|---|
Serial GC | -XX:+UseSerialGC | |
Parallel GC | -XX:+UseParallelGC</br>-XX:ParallelGCThreads=value | |
Parallel Compacting GC | -XX:+UseParallelOldGC | |
CMS GC | -XX:+UseConcMarkSweepGC</br>-XX:+UseParNewGC</br>-XX:+CMSParallelRemarkEnabled</br>-XX:CMSInitiatingOccupancyFraction=value</br>-XX:+UseCMSInitiatingOccupancyOnly | |
G1 | -XX:+UnlockExperimentalVMOptions</br>-XX:+UseG1GC | 在JDK 6中这两个参数必须配合使用 |
除了G1收集器外,可以通过设置上表中每种类型第一行的参数来切换GC类型,最常见的非侵入式GC就是Serial GC,它针对客户端系统进行了特别的优化。
会影响GC性能的参数还有很多,但是上述的参数会带来最显著的效果,请切记,设置太多的参数并不一定会提升GC的性能。
GC优化的过程
GC优化的过程和大多数常见的提升性能的过程相似,下面是笔者使用的流程:
1.监控GC状态
你需要监控GC从而检查系统中运行的GC的各种状态,具体方法请查看系列的第二篇文章《如何监控Java GC》
2.分析监控结果后决定是否需要优化GC
在检查GC状态后,你需要分析监控结构并决定是否需要进行GC优化。如果分析结果显示运行GC的时间只有0.1-0.3秒,那么就不需要把时间浪费在GC优化上,但如果运行GC的时间达到1-3秒,甚至大于10秒,那么GC优化将是很有必要的。
但是,如果你已经分配了大约10GB内存给Java,并且这些内存无法省下,那么就无法进行GC优化了。在进行GC优化之前,你需要考虑为什么你需要分配这么大的内存空间,如果你分配了1GB或2GB大小的内存并且出现了OutOfMemoryError
,那你就应该执行堆转储(heap dump)来消除导致异常的原因。
注意:
堆转储(heap dump)是一个用来检查Java内存中的对象和数据的内存文件。该文件可以通过执行JDK中的
jmap
命令来创建。在创建文件的过程中,所有Java程序都将暂停,因此,不要再系统执行过程中创建该文件。
你可以在互联网上搜索heap dump的详细说明。对于韩国读者,可以直接参考我去年发布的书:《The story of troubleshooting for Java developers and system operators》 (Sangmin Lee, Hanbit Media, 2011, 416 pages)
3.设置GC类型/内存大小
如果你决定要进行GC优化,那么你需要选择一个GC类型并且为它设置内存大小。此时如果你有多个服务器,请如上文提到的那样,在每台机器上设置不同的GC参数并分析它们的区别。
4.分析结果
在设置完GC参数后就可以开始收集数据,请在收集至少24小时后再进行结果分析。如果你足够幸运,你可能会找到系统的最佳GC参数。如若不然,你还需要分析输出日志并检查分配的内存,然后需要通过不断调整GC类型/内存大小来找到系统的最佳参数。
5.如果结果令人满意,将参数应用到所有服务器上并结束GC优化
如果GC优化的结果令人满意,就可以将参数应用到所有服务器上,并停止GC优化。
在下面的章节中,你将会看到上述每一步所做的具体工作。
监控GC状态并分析结果
在运行中的Web应用服务器(Web Application Server,WAS)上查看GC状态的最佳方式就是使用jstat
命令。笔者在《如何监控Java GC》中已经介绍过了jstat
命令,所以在本篇文章中我将着重关注数据部分。
下面的例子展示了某个还没有执行GC优化的JVM的状态(虽然它并不是运行服务器)。
$ jstat -gcutil 21719 1s
S0 S1 E O P YGC YGCT FGC FGCT GCT
48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673
48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673
我们先看一下YGC(从应用程序启动到采样时发生 Young GC 的次数)和YGCT(从应用程序启动到采样时 Young GC 所用的时间(秒)),计算YGCT/YGC会得出,平均每次新生代的GC耗时50ms,这是一个很小的数字,通过这个结果可以看出,我们大可不必关注新生代GC对GC性能的影响。
现在来看一下FGC( 从应用程序启动到采样时发生 Full GC 的次数)和FGCT(从应用程序启动到采样时 Full GC 所用的时间(秒)),计算FGCT/FGC会得出,平均每次老年代的GC耗时19.68s。有可能是执行了三次Full GC,每次耗时19.68s,也有可能是有两次只花了1s,另一次花了58s。不管是哪一种情况,GC优化都是很有必要的。
使用jstat
命令可以很容易地查看GC状态,但是分析GC的最佳方式是加上-verbosegc
参数来生成日志。在之前的文章中笔者已经解释了如何分析这些日志。HPJMeter是笔者最喜欢的用于分析-verbosegc
生成的日志的工具,它简单易用,使用HPJmeter可以很容易地查看GC执行时间以及GC发生频率。
此外,如果GC执行时间满足下列所有条件,就没有必要进行GC优化了:
- Minor GC执行非常迅速(50ms以内)
- Minor GC没有频繁执行(大约10s执行一次)
- Full GC执行非常迅速(1s以内)
- Full GC没有频繁执行(大约10min执行一次)
括号中的数字并不是绝对的,它们也随着服务的状态而变化。有些服务可能要求一次Full GC在0.9s以内,而有些则会放得更宽一些。因此,对于不同的服务,需要按照不同的标准考虑是否需要执行GC优化。
当检查GC状态时,不能只查看Minor GC和Full GC的时间,还必须要关注GC执行的次数。如果新生代空间太小,Minor GC将会非常频繁地执行(有时每秒会执行一次,甚至更多)。此外,传入老年代的对象数目会上升,从而导致Full GC的频率升高。因此,在执行jstat
命令时,请使用-gccapacity
参数来查看具体占用了多少空间。
设置GC类型/内存大小
设置GC类型
Oracle JVM有5种垃圾收集器,但是在JDK 7以前的版本中,你只能在Parallel GC, Parallel Compacting GC 和CMS GC之中选择,至于具体选择哪个,则没有具体的原则和规则。
既然这样的话,我们如何来选择GC呢?最好的方法是把三种都用上,但是有一点必须明确——CMS GC通常比其他并行(Parallel)GC都要快(这是因为CMS GC是并发的GC),如果确实如此,那只选择CMS GC就可以了,不过CMS GC也不总是更快,当出现concurrent mode failure时,CMS GC就会比并行GC更慢了。
Concurrent mode failure
现在让我们来深入地了解一下concurrent mode failure。
并行GC和CMS GC的最大区别是并行GC采用“标记-整理”(Mark-Compact)算法而CMS GC采用“标记-清除”(Mark-Sweep)算法(具体内容可参照译者的文章《GC算法与内存分配策略》),compact步骤就是通过移动内存来消除内存碎片,从而消除分配的内存之间的空白区域。
对于并行GC来说,无论何时执行Full GC,都会进行compact工作,这消耗了太多的时间。不过在执行完Full GC后,下次内存分配将会变得更快(因为直接顺序分配相邻的内存)。
相反,CMS GC没有compact的过程,因此CMS GC运行的速度更快。但是也是由于没有整理内存,在进行磁盘清理之前,内存中会有很多零碎的空白区域,这也导致没有足够的空间分配给大对象。例如,在老年代还有300MB可用空间,但是连一个10MB的对象都没有办法被顺序存储在老年代中,在这种情况下,会报出“concurrent mode failure”的warning,然后系统执行compact操作。但是CMS GC在这种情况下执行的compact操作耗时要比并行GC高很多,并且这还会导致另一个问题,关于“concurrent mode failure”的详细说明,可用参考Oracle工程师撰写的《Understanding CMS GC Logs》。
综上所述,你需要根据你的系统情况为其选择一个最适合的GC类型。
每个系统都有最适合它的GC类型等着你去寻找,如果你有6台服务器,我建议你每两个服务器设置相同的参数,然后加上-verbosegc
参数再分析结果。
设置内存大小
下面展示了内存大小、GC运行次数和GC运行时间之间的关系:
大内存空间
- 减少了GC的次数
- 提高了GC的运行时间
小内存空间
- 增多了GC的次数
- 降低了GC的运行时间
关于如何设置内存的大小,没有一个标准答案,如果服务器资源充足并且Full GC能在1s内完成,把内存设为10GB也是可以的,但是大部分服务器并不处在这种状态中,当内存设为10GB时,Full GC会耗时10-30s,具体的时间自然与对象的大小有关。
既然如此,我们该如何设置内存大小呢?通常我推荐设为500MB,这不是说你要通过-Xms500m
和-Xmx500m
参数来设置WAS内存。根据GC优化之前的状态,如果Full GC后还剩余300MB的空间,那么把内存设为1GB是一个不错的选择(300MB(默认程序占用)+ 500MB(老年代最小空间)+200MB(空闲内存))。这意味着你需要为老年代设置至少500MB空间,因此如果你有三个运行服务器,可以把它们的内存分别设置为1GB,1.5GB,2GB,然后检查结果。
理论上来说,GC执行速度应该遵循1GB> 1.5GB> 2GB,1GB内存时GC执行速度最快。然而,理论上的1GB内存Full GC消耗1s、2GB内存Full GC消耗2 s在现实里是无法保证的,实际的运行时间还依赖于服务器的性能和对象大小。因此,最好的方法是创建尽可能多的测量数据并监控它们。
在设置内存空间大小时,你还需要设置一个参数:NewRatio
。NewRatio
的值是新生代和老年代空间大小的比例。如果XX:NewRatio=1
,则新生代空间:老年代空间=1:1,如果堆内存为1GB,则新生代:老年代=500MB:500MB。如果NewRatio
等于2,则新生代:老年代=1:2,因此,NewRatio
的值设置得越大,则老年代空间越大,新生代空间越小。
你可能会认为把NewRatio
设为1会是最好的选择,然而事实并非如此,根据笔者的经验,当NewRatio
设为2或3时,整个GC的状态表现得更好。
完成GC优化最快地方法是什么?答案是比较性能测试的结果。为了给每台服务器设置不同的参数并监控它们,最好查看的是一或两天后的数据。当通过性能测试来进行GC优化时,你需要在不同的测试时保证它们有相同的负载和运行环境。然而,即使是专业的性能测试人员,想精确地控制负载也很困难,并且需要大量的时间准备。因此,更加方便容易的方式是直接设置参数来运行,然后等待运行的结果(即使这需要消耗更多的时间)。
分析GC优化的结果
在设置了GC参数和-verbosegc
参数后,可以使用tail命令确保日志被正确地生成。如果参数设置得不正确或日志未生成,那你的时间就被白白浪费了。如果日志收集没有问题的话,在收集一或两天数据后再检查结果。最简单的方法是把日志从服务器移到你的本地PC上,然后用HPJMeter分析数据。
在分析结果时,请关注下列几点(这个优先级是笔者根据自己的经验拟定的,我认为选取GC参数时应考虑的最重要的因素是Full GC的运行时间。):
- 单次Full GC运行时间
- 单次Minor GC运行时间
- Full GC运行间隔
- Minor GC运行间隔
- 整个Full GC的时间
- 整个Minor GC的运行时间
- 整个GC的运行时间
- Full GC的执行次数
- Minor GC的执行次数
找到最佳的GC参数是件非常幸运的,然而在大多数时候,我们并不会如此幸运,在进行GC优化时一定要小心谨慎,因为当你试图一次完成所有的优化工作时,可能会出现OutOfMemoryError
错误。
优化案例
到目前为止,我们一直在从理论上介绍GC优化,现在是时候将这些理论付诸实践了,我们将通过几个例子来更深入地理解GC优化。
示例1
下面这个例子是针对Service S的优化,对于最近刚开发出来的Service S,执行Full GC需要消耗过多的时间。
现在看一下执行jstat -gcutil
的结果
S0 S1 E O P YGC YGCT FGC FGCT GCT
12.16 0.00 5.18 63.78 20.32 54 2.047 5 6.946 8.993
左边的Perm区的值对于最初的GC优化并不重要,而YGC参数的值更加对于这次优化更为重要。
平均执行一次Minor GC和Full GC消耗的时间如下表所示:
表3:Service S的Minor GC 和Full GC的平均执行时间GC类型 | GC执行次数 | GC执行时间 | 平均值 |
---|---|---|---|
Minor GC | 54 | 2.047s | 37ms |
Full GC | 5 | 6.946s | 1.389s |
37ms对于Minor GC来说还不赖,但1.389s对于Full GC来说意味着当GC发生在数据库Timeout设置为1s的系统中时,可能会频繁出现超时现象。
首先,你需要检查开始GC优化前内存的使用情况。使用jstat -gccapacity
命令可以检查内存用量情况。在笔者的服务器上查看到的结果如下:
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC
212992.0 212992.0 212992.0 21248.0 21248.0 170496.0 1884160.0 1884160.0 1884160.0 1884160.0 262144.0 262144.0 262144.0 262144.0 54 5
其中的关键值如下:
- 新生代内存用量:212,992 KB
- 老年代内存用量:1,884,160 KB
因此,除了永久代以外,被分配的内存空间加起来有2GB,并且新生代:老年代=1:9,为了得到比使用jstat
更细致的结果,还需加上-verbosegc
参数获取日志,并把三台服务器按照如下方式设置(除此以外没有使用任何其他参数):
- NewRatio=2
- NewRatio=3
- NewRatio=4
一天后我得到了系统的GC log,幸运的是,在设置完NewRatio后系统没有发生任何Full GC。
这是为什么呢?这是因为大部分对象在创建后很快就被回收了,所有这些对象没有被传入老年代,而是在新生代就被销毁回收了。
在这样的情况下,就没有必要去改变其他的参数值了,只要选择一个最合适的NewRatio
值即可。那么,如何确定最佳的NewRatio值呢?为此,我们分析一下每种NewRatio
值下Minor GC的平均响应时间。
在每种参数下Minor GC的平均响应时间如下:
- NewRatio=2:45ms
- NewRatio=3:34ms
- NewRatio=4:30ms
我们可以根据GC时间的长短得出NewRatio=4是最佳的参数值(尽管NewRatio=4时新生代空间是最小的)。在设置完GC参数后,服务器没有发生Full GC。
为了说明这个问题,下面是服务执行一段时间后执行jstat –gcutil
的结果:
S0 S1 E O P YGC YGCT FGC FGCT GCT
8.61 0.00 30.67 24.62 22.38 2424 30.219 0 0.000 30.219
你可能会认为是服务器接收的请求少才使得GC发生的频率较低,实际上,虽然Full GC没有执行过,但Minor GC被执行了2424次。
示例2
这是一个Service A的例子。我们通过公司内部的应用性能管理系统(APM)发现JVM暂停了相当长的时间(超过8秒),因此我们进行了GC优化。我们努力寻找JVM暂停的原因,后来发现是因为Full GC执行时间过长,因此我们决定进行GC优化。
在GC优化的开始阶段,我们加上了-verbosegc
参数,结果如下图所示:

上图是由HPJMeter生成的图片之一。横坐标表示JVM执行的时间,纵坐标表示每次GC的时间。CMS为绿点,表示Full GC的结果,而Parallel Scavenge为蓝点,表示Minor GC的结果。
之前我说过CMS GC是最快的GC,但是上面的结果显示在一些时候CMS耗时达到了15s。是什么导致了这一结果?请记住我之前说的:CMS在执行compact(整理)操作时会显著变慢。此外,服务的内存通过-Xms1g
和=Xmx4g
设置了,而分配的内存只有4GB。
因此笔者将GC类型从CMS GC改为了Parallel GC,把内存大小设为2GB,并把NewRatio
设为3。在执行jstat -gcutil
几小时后的结果如下:
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 30.48 3.31 26.54 37.01 226 11.131 4 11.758 22.890
Full GC的时间缩短了,变成了每次3s,跟15s比有了显著提升。但是3s依然不够快,为此笔者创建了以下6种情况:
- Case 1:
-XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=2
- Case 2:
-XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=3
- Case 3:
-XX:+UseParallelGC -Xms1g -Xmx1g -XX:NewRatio=3
- Case 4:
-XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=2
- Case 5:
-XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=3
- Case 6:
-XX:+UseParallelOldGC -Xms1g -Xmx1g -XX:NewRatio=3
上面哪一种情况最快?结果显示,内存空间越小,运行结果最少。下图展示了性能最好的Case 6的结果图,它的最慢响应时间只有1.7s,并且响应时间的平均值已经被控制到了1s以内。

基于上图的结果,按照Case 6调整了GC参数,但这却导致每晚都会发生OutOfMemoryError
。很难解释发生异常的具体原因,简单地说,应该是批处理程序导致了内存泄漏,我们正在解决相关的问题。
如果只对GC日志做一些短时间的分析就将相关参数部署到所有服务器上来执行GC优化,这将是非常危险的。切记,只有当你同时仔细分析服务的执行情况和GC日志后,才能保证GC优化没有错误地执行。
在上文中,我们通过两个GC优化的例子来说明了GC优化是怎样执行的。正如上文中提到的,例子中设置的GC参数可以设置在相同的服务器之上,但前提是他们具有相同的CPU、操作系统、JDK版本并且运行着相同的服务。此外,不要把我使用的参数照搬到你的应用上,它们可能在你的机器上并不能起到同样良好的效果。
总结
笔者没有执行heap dump并分析内存的详细内容,而是通过自己的经验进行GC优化。精确地分析内存可以得到更好的优化效果,不过这种分析一般只适用于内存使用量相对固定的场景。如果服务严重过载并占有了大量的内存,则建议你根据之前的经验进行GC优化。
笔者已经在一些服务上设置了G1 GC参数并进行了性能测试,但还没有应用于正式的生产环境。G1 GC的速度快于任何其他的GC类型,但是你必须要升级到JDK 7。此外,暂时还无法保证它的稳定性,没有人知道运行时是否会出现致命的错误,因此G1 GC暂时还不适合投入应用。
等未来JDK 7真正稳定了(这并不是说它现在不稳定),并且WAS针对JDK 7进行优化后,G1 GC最终能按照预期的那样来工作,等到那一天我们可能就不再需要GC优化了。
想了解关于GC优化的更多细节,请前往Slideshare.com 查看相关资料。强烈推荐Everything I Ever Learned About JVM Performance Tuning @Twitter,作者是Attila Szegedi, 一名Twitter工程师,请花些时间好好阅读它。
「译」Android原生开发的现状与未来
原文:The State of Native Android Development, November 2019原文:Vasiliy Zukanov
翻译作者: ronaldong
Android原生开发的生态一直在不断地发展变化,过去5年从事android开发的经历让我深刻的体会到了这一点。每隔2到3年,谷歌就会发布一些的新的开发指导建议、libraries、frameworks,我花了很多时间来认真审查这些变化并从中找出可能存在的问题。我相信许多Android开发者都有我这样类似的经历。
然而,2019年绝对是Android原生开发生态发生剧变的一年。在这一年里,Android sdk添加了许多新的内容、重写和移除了一些旧的内容,官方的开发者指南也进行了大幅度的更新。想要对Android开发有一种完整而又详细的认识实在是太难了。
于是我写下了这篇文章,我试图去总结Android生态系统中所发生的事情,并对原生开发的未来做出一些预测。接下来我会把我的观点分成不同的章节来进行具体的阐述,文章的最后我会分享一些极具争议的观点。
我希望这篇文章会对你们有所帮助,但是请记住,文章中肯定遗漏了许多重要的内容,而且其中的许多观点都是我个人的偏见。
AndroidX
AndroidX的预览版是在一年半前发布的。大约一年前它变得稳定了,与此同时Google也停止了对旧版Support Library的进一步开发。当我写下这句话的那一刻,我想起了我之前在StackOverflow上提出的一个问题:Why does AOSP add new APIs to support libraries without adding them to SDK?,当时我是一个android开发新手,我想知道Support Library背后的动机,Googl为什么不直接把Support Library放到android sdk里呢?
不过使用“稳定”一词来描述AndroidX似乎有点讽刺,因为关于AndroidX的任何信息似乎都不是稳定的。Google不断的在往AndroidX里添加新的库和框架。命名空间和许多旧的API(目前还不到一年)正在以非常快的速度发展。
到目前为止,我已经将我的两个应用迁移到了AndroidX。一切都很顺利,但我也不觉得有多惊喜。Jetifier是一种将Support Library上的依赖项重定向到其AndroidX对等项的工具,转换效果令人惊讶。但是即使应用程序不大,也并不是“一键式迁移”。
我还参与了一个尚未迁移到AndroidX的项目,没有任何问题。在某些情况下,似乎我完全不需要AndroidX。
总而言之,我想说的是:如果是开启新的Android项目,那么肯定是应该使用AndroidX;对于现有的项目,我也建议您做好迁移到AndroidX的计划,即使您现在看不到明显的好处。无论如何,您很可能都需要迁移,因此最好按自己的计划进行迁移,而不是以后当你需要一些新的AndroidX库时进行紧急的迁移
Jetpack
在讨论完AndroidX之后,就不得不提到Jetpack了。据我所知,Jetpack最初只是“architecture components”的工具集合,但是后来扩展为包含了AndroidX的大多数(甚至所有)API的工具集合。因此,到目前为止,我还没有看到AndroidX和Jetpack之间有任何有意义的区别,市场营销和公关宣传除外。
当您访问Jetpack的官方网站时,它看起来不像是技术文档,更像是早期SaaS初创公司的主页。
看看这些“感言”:
再看看下面这些app:
如果Jetpack申请2020年独立IPO,我不会感到惊讶,因为他们是如此的专注于营销和公关。
不过说真的,这种向自己的生态系统中的开发人员“销售”api的做法存在一些深层次的问题。比如,为什么有人真的想在搜索中宣传ViewModel?
总而言之,由于Jetpack的大部分内容都是来源于AndroidX,所以我之前写的有关AndroidX的内容在很大程度上也适用于Jetpack。
下面,我将分别讨论其中一些具体的API。
Background Work
当应用程序不在前台时让应用也能执行操作是Android开发中最常见的场景之一。在引入doze模式、SyncAdapter、GCMNetworkManager、FirebaseJobDispatcher、JobScheduler以及最近的WorkManager之前,您可以通过启动服务(而不是绑定服务)来实现。这些都是Google自己的API,不过也有很多第三方解决方案,比如Android-Job。
但是,Google最近宣布,他们将通过WorkManager API来统一后台任务的调度。听起来很不错,但是由于某种原因,当我听到这样的声音时,我总有种似曾相识的感觉……
无论最终能否统一,WorkManager都无法解决在执行后台任务过程中存在的一个最严重的问题:可靠性。我在这里不做解释,但是请记住,如果您需要在应用程序中执行后台任务,请先阅读dontkillmyapp.com上的所有信息。此外,请在Google的issue tracker中阅读并加注星标。
Android系统的后台任务执行与调度是一团糟,碎片化使得它非常细微且不可靠。
过去,我一直主张尽可能的将数据同步等类似的工作放在后台来执行,我可能是SyncAdapter的最后一批粉丝。但是今天,鉴于可靠性问题,我主张相反的做法:尽可能避免在后台执行操作。如果您的PM坚持使用此功能,请向他们展示以上链接,并向他们解释,后台任务需要花费数百小时的时间来实现,而且带来的麻烦多于收益。
有些时候执行后台任务是不可避免的,但是在大多数情况下,您可以不这样做。即使以给用户带来一些不便为代价,它也可能是最佳选择。
Databases
毫无疑问,Room在众多SQLite的ORM框架中占据着主导地位。从2.2.0开始,Room支持增量注解处理。不过请记住,您的应用架构不应过于关心使用了哪种ORM框架。因此,作为architecture components的一员,Room只是市场术语,而不是技术角色。
Android开发中ORM框架的主要竞争者是SQLDelight。这个库比Room还要老,但是据我了解,在过去的一年左右的时间里,它已经被重写了很多。不幸的是,它现在只针对Kotlin。另一方面,SQLDelight支持Kotlin跨平台。因此,随着Kotlin使用率的增加,我预计SQLDelight的使用率也会随之增加。
顺便说一下,AndroidX中有对原生SQLite的使用说明,我还不知道该怎么使用。但是如果您想在应用中使用原生SQLite,那么你或许需要去认真的研究下这个主题。
此外还有许多针对Android的非关系型的数据库,例如Realm,Parse,Firebase,ObjectBox等(其中有些仍在使用SQLite)。如果我没记错的话,它们中的大多数(甚至全部)都具有自动数据同步功能。一段时间以来,这些解决方案比较流行,但据我所知,它们已经不复存在了。但是我并不会马上认为非关系型的数据库不再重要了。
去年,我编写了一个非常复杂的集成了Parse Server的Android应用。我使用了Android版本的的Parse SDK,体验都非常好。如果您的公司已经雇用了许多后端开发人员,或者您需要实现许多服务器端逻辑,这可能不是最佳解决方案,但是对于仅在后端执行CRUD操作的初创企业和个人来说,这可能会是一种好的选择。
但是我必须提醒的一点是:如果您要采用数据库即服务的解决方案(例如Firebase),那么请务必了解其长期的成本和影响。
External Storage
关于外部存储的开发,这里有许多有“意思”的事情。
如果您应用的target sdk版本等于或者大于29,那么你的应用将无法再正常访问手机外部存储上的文件,除了少数几种明显的情况。相反,您需要使用SAF框架(据说),该框架允许用户进行更精细的访问管理。不幸的是,SAF的工作方式与之前完全不同,因此某些应用程序可能需要进行重大重构。
Google希望从Android 10开始对所有的应用程序都实行这一要求,但它引起了开发者社区的强烈抗议,于是他们决定推迟此功能。因此,即使您的应用设置target sdk版本为 29,它仍可以在“旧版”模式下工作。但是,无论目标API级别是多少,下一版的Android系统都将对所有应用的存储访问范围做更加严格的限制。
到目前为止,我还没有使用SAF框架,但是从我在互联网上阅读的许多讨论中看来,这可能是一项艰巨的任务。因此,如果您的应用程序还在以“旧版”模式使用外部存储,那么最好立即开始进行重构和测试。
Shared Preferences
几周前,AndroidX系列中添加了一个新框架。它的commit message是这么说的:
New library meant to replace SharedPreferences. The name is not final, this is just for implementation review and to support the design doc (feel free to request the design doc privately)[…]
目前我们无需担心,但从长远来看,似乎SharedPreferences会被重写,我们需要使用这种新的方法。
SharedPreferences和这个新框架之间的主要区别在于,默认情况下后者是异步的。换句话说,您需要实现一个回调以获取特定键的值,该回调将在以后的某个时间收到通知。
如果您对这种异步通知的机制感到好奇,则可以阅读StackOverflow上的这个答案。Reddit用户Tolriq在这里分享了他们遇到此bug的概率。在他们的应用中,这个bug会影响1 / 10,000 / SESSIONS_PER_USER_PER_MONTH的用户。对于一般的应用程序,这可能微不足道。但是在需要高可靠性的情况下,这可能会引起严重的后果。例如,在装有Android Auto的汽车中,应用程序挂起和随后的崩溃会分散驾驶员的注意力,这可能会导致非常不幸的后果。
Dependency Injection
在依赖注入方面,最大的变化就是Dagger-Android的弃用。这里我想解释两点:首先,我说的弃用并不是指“正式”弃用,因为它尚未正式弃用。其次,Dagger-Android并不是整个Dagger2框架,而只是相对较新的功能。我在这个主题上写了一篇非常详尽的文章,所以我在这里不再重复。
至于其他依赖注入框架,我不认为它们是Dagger的真正竞争者。例如,Koin也许不错,但我认为它不会吸引很多人。实际上,我相信它仅由于两个主要原因而得到了初步采用。第一个是Dagger的糟糕文档,Koin在这方面要比Dagger领先N光年。第二个原因是Koin是用Kotlin编写的,它借着kotlin发展的浪潮开始兴起。到目前为止,这波浪潮已经几乎消逝。
我认为可能会发生的情况是,纯依赖注入的框架(又称为手动依赖注入)的会逐渐出现。
现在,谷歌声称“随着应用程序的不断增大,手动依赖项注入成本呈指数增长”。我认为,这仅表明他们既不了解“指数”的含义,也没做过任何实际的“测量”。此声明是完全错误的,我希望Google不要以这种方式来误导社区里的开发者了。
事实上,纯依赖注入在后端开发中非常普遍(尤其是在开发微服务的时候,您不想在其中添加对每个服务的框架的依赖),反射也是后端开发中的一个有效的选项。因此,如果要使用依赖注入框架,他们通常不需要解析编译时代码。
但是,Android开发的情况有所不同。由于我们不能使用反射式DI框架,所以我们使用了Dagger。事实上,我们可以使用反射式DI框架,并且对于大多数项目来说都可以,但是却存在性能问题。我并不是说使用反射式DI框架是安全的,但它绝对不是一种非黑即白的方案。无论如何,Dagger已经是在Android开发中使用依赖注入的事实上的标准,我们都使用它。但使用Dagger的代价也很明显:
- 1)应用的代码越多,在构建过程中运行注解处理所花费的时间就越多。
- 2)应用参与的开发人员越多,他们需要执行的构建次数就越多。
- 3)所有开发人员都需要学习Dagger ,这需要很多时间。
换句话说,虽然Dagger确实允许您编写更少的代码,但由于它会影响构建时间和所需的培训时间,因此在大型项目上它会花费更多的时间。
在大型项目中,构建时间慢才是真正的问题,并成为主要的生产力瓶颈。因此,尽管Dagger确实提供了非常出色的功能来简化DI(当然,一旦您知道如何使用它),但我相信我们对纯依赖注入会产生越来越多的兴趣。
DataBinding
开发人员采用DataBidning的主要原因之一是不再需要调用findViewById()了。老实说,findViewById确实很冗余,我也不介意摆脱它们。但是,在我看来,调用findViewById()带来的小麻烦并不能证明使用DataBinding是合理的。好消息是,很快我们将能够使用另一个新功能ViewBinding来删除这些findViewById()的调用。
实际上,我从来都不相信DataBinding。对于它(应该)解决的问题,我感觉太复杂了。此外,DataBinding允许开发人员将逻辑放入XML布局中。经验丰富的开发人员是不会使用这种方法的,因为这增加了项目维护的难度。这是DataBinding框架的另一个缺点。
早在2016年11月,当DataBinding正处于大肆宣传的顶峰时,我在StackOverflow上的一个答案中做出了以下预测:
However, there is one prediction I can make with a high degree of confidence: Usage of the Data Binding library will not become an industry standard. I’m confident to say that because the Data Binding library (in its current implementation) provides short-term productivity gains and some kind of architectural guideline, but it will make the code non-maintainable in the long run. Once long-term effects of this library will surface – it will be abandoned.
现在,关于DataBinding的使用率,我没有任何统计数据,但是很明显,它并没有成为行业标准。我自己还从未见过使用DataBinding的专业项目,也很少见到在其应用中使用DataBinding的开发人员。据我估计,一旦ViewBinding成熟并被广泛采用,DataBinding将会更加流行,并成为“传统”框架。
Preserving State on Configuration Changes
自从引入ViewModel之后,在Android应用中对于配置更改的处理就变得一团糟。我知道我的这种说法太苛刻了点,但实际上,这是我可以描述的最温和的表达方式。
对我来说幸运的是,Gabor Varadi(又名Zhuinden)已经在Reddit上的这篇文章中对这一问题进行了总结,所以我不需要自己做。他的结论就是:不推荐使用onRetainCustomNonConfigurationInstance(),而推荐使用ViewModel。有趣的是,在该帖子的结尾,Gabor做了一些颇具嘲讽味道的预测:
你发现什么了吗?Retained Fragments现在已经被弃用了! 。
我认为,弃用Retained Fragments实际上是一个好主意。Fragment的生命周期里具有onAttach()和onDetach()这两个方法的唯一原因就是为了支持Retained Fragments的使用。通过弃用Retained Fragments,这些方法也可以弃用,并且可以简化Fragment的生命周期。如果您使用我的方法来处理Fragment的生命周期,那么这种弃用就不会让您感到困扰,因为我长期以来一直建议您避免Retained Fragments,忽略onAttach()和onDetach()方法。
尽管有充分的理由要弃用Retained Fragments,但弃用onRetainCustomNonConfigurationInstance()却是胡说八道。这不是我说的,而是Jake Wharton说的(您可以在前面提到的Gabor在Reddit上的帖子下阅读他的原话)。
为什么要做这些变动呢?我只能看到一种解释:Google决定不管其它技术优势如何,都强制将所有Android项目迁移到ViewModel。他们愿意弃用所有现有的替代方案以实现其目标,即使这些替代方案实际上优于ViewModel本身。
听起来有点阴谋吧?我同意。但是,幸运的是,我们可以对此理论有一个简单的检验。
虽然我不喜欢Preserving State on Configuration Changes,但它不会以任何方式影响我,因为我没有使用它。实际上,绝大部分应用程序都不需要它。它们也不需要ViewModel。正确处理Configuration Changes的方式就是在onSaveInstanceState(Bundle)回调方法里增加处理逻辑。这是一种更简单,更好的方法,因为它还可以处理保存和恢复流程(也称为进程终止)。因此,只要我能以这种方式保存状态,就可以了。尽管Google进行了大量的营销和公关工作,但许多经验丰富的开发人员都意识到ViewModel太复杂了,并且有更好的方法来保留配置更改的状态。
因此,如果Google确实有别有用心,并且想迫使所有项目都使用ViewModel,那么他们还需要弃用onSaveInstanceState(Bundle)。我知道这听起来很疯狂,但这实际上是件好事,因为如果这种疯狂的预测成真,您就会知道基础理论是正确的。
但是,鉴于Android的内存管理机制,Google不能仅在不提供可靠替代方案的情况下就弃用onSaveInstanceState(Bundle)。“幸运”的是,这些变化已经应用在ViewModel的保存状态模块上了。
我想在一两年内我们就会知道这种做法是否有任何优点。‘
总而言之,正如我在本节开头所说的那样,自ViewModel发布以来,Android中的Configuration Changes就成了屎。两年多以前,当我撰写题为“Android ViewModel Architecture Component Considered Harmful”的文章时,我预测ViewModels将是一种浪费。我的所有预测都是真实的,但不幸的是,事实证明真相比这还糟。
Concurrency
在并发这方面,最大的变化就是AsyncTask的弃用。我已经写了一篇有关此主题的非常详细的文章,并提出了具体建议,因此在此不再赘述。
接下来我说的话可能会使部分读者感到不满。拜托,别太把这事当真。
Android开发中另一个流行的多线程框架RxJava很快就会成为“过去式”。从下面这幅StackOverflow趋势图可以明显看出:
许多开发者会质疑我的观点,称该数据不具有代表性,并且还有其他方法的可以解释该图。他们可能是正确的,因为我自己也不是数据科学家。但是,在此图中,我看不到任何其他关于峰值的解释,而且RxJava的曲线与AsyncTask的曲线具有相同的斜率。
因此,如果您尚未花时间在学习RxJava上并且您的项目没有使用它,那么我建议您避免使用它。实际上,这一直是我的建议,今天它也得到了数据的支持。
如果您的项目已经使用了Rx,也请不要惊慌,您无需立即重构任何东西。但是,请记住,今后找到具有Rx经验的开发人员将越来越困难。因此,在项目中广泛使用Rx可能需要新的开发人员投入更多的时间。最终,广泛使用Rx的项目将被视为“not cool”(例如今天使用AsyncTask和Loaders的项目)。
我知道我的这些观点对于许多开发人员来说很不友好。他们花了数周时间来学习RxJava,甚至说服了同事在项目中使用RxJava,现在我却说它会成为“过去式”。我只想说我只是分析实际情况并根据我所看到的做出预测,我可能是错的,也可能是对的。
在Kotlin语言中,我们可以使用协程。我最近使用协程实现了一些复杂的用例,发现此框架非常的细微和复杂,并且相对不成熟,我甚至发现了一个bug。
有一种流行的说法是,协程使得并发处理更简单。我从来不这样认为,因为我知道并发是非常复杂的,但是在我有了一些实践经验之后,我可以自信地说,协程并没有想像中的那么美好。在我看来,协程实际上增加了程序复杂性,所以我建议你们小心地使用它们。
另一方面,协程似乎将成为Kotlin语言里处理并发操作的默认方式。因此,我认为如果您编写Kotlin代码,您需要投入时间并学会使用它们。
据我所知,目前还有一个流式框架,它在协程之上添加了流处理操作符。几个月前才稳定下来,所以我现在还不能说什么。
Kotlin
现在让我们来讨论一下Kotlin。根据以往的经验,我知道这是一个非常敏感的话题,而且不管我描述的多么客观,最终都会遭受一些开发者的攻击。然而,我认为在总结原生Android开发现状的时候跳过Kotlin是极不诚实的。因此,我再次请你不要把我说的话当真。
你所需要知道的一个重要的事实是:在Android开发中使用Kotlin会严重增加你的构建时间。
在这篇文章中,您会了解到我在使用Kotlin进行开发时对构建时间所进行的统计测试的结果。clean build 增加了18%的构建时间,incremental build 增加了8%的构建时间。
Uber与JetBrains也联合发表了他们自己的研究结果,他们的结果更为负面。如果您不在应用程序中使用注解处理器,那么引入Kotlin可能会使您的构建时间增加四倍!如果您使用了注解处理器,那么引入Kotlin会使您的构建时间增加50%-100%。
Uber的研究结果与将OkHttp迁移到Kotlin版本后构建时间增加了4倍的结果是一致的。
如果您对这些数字感到惊讶,您不用担心-这不是您的错,而且您并不孤单。尽管这个事实极为重要,但它并未得到广泛讨论,并且我觉得Google也试图回避这个事实。我曾与Google内部一个熟悉此事的开发人员有过一次非常有趣的讨论,我问他是否可以讨论下这个话题,他说:“我不喜欢;我不喜欢;我不喜欢。这是一件很微妙的事情。”
除了增加构建时间之外,Kotlin还不支持增量注解处理,而在大约10个月前Java就已经支持增量注解处理了。
两年前,我写了一篇文章来警告开发者们在早期使用Kotlin时可能会遇到的潜在风险。在很长一段时间内我被称为“kotlin的讨厌者”。
但是,如果您今天阅读这篇文章,您会发现我实际上低估了这些问题的严重性。在大型的Android项目上,构建时间是最糟糕的生产力杀手之一,而且即使在今天,即Kotlin被官方“正式采用”两年多之后的今天,Kotlin仍然不如Java。不管Kotlin带来什么其他好处,所有这些都可能由于更长的构建时间而被否定。
也就是说,我们不应改忽视这样一个事实:是谷歌将android开发的生态强行推向了kotlin,使得其使用率在稳步上升。
就我个人而言,我并没有在我目前已经开始的新项目中选择kotlin语言,我不想在kotlin上浪费我自己的时间。不过,从现在开始,我会认真考虑使用kotlin来开发新项目,我已经在几个demo上尝试过了。但是我不同意开发人员说你必须在新项目中使用Kotlin,这仍然是一种权衡。
至于你们是否应该将现有项目迁移到Kotlin,我无法提供任何一般性建议,您需要根据具体的情况进行仔细的分析。但是,如果您确实决定开始(或已经开始)迁移,那么这个帖子可能会对您有用。
Summary
在过去的两年中,我开发了三个新的应用程序。我认真研究了现有的项目并分析了早期技术决策所带来的长期影响。我写了一些博客,提供有关Android开发的高级课程。我花了很多时间在互联网上讨论Android开发相关主题。
尽管如此,我还是感觉自己无法跟上Android生态系统的变化。
如果是这样的话,对于那些缺乏经验、需要指导的Android开发人员,我深表歉意,而且我至今无法想象从头开始学习Android开发的感觉。当您对框架和工具感到满意的时候,其中许多将已过时或即将过时。加入这个原本很棒的社区可能是最糟糕的时刻。Google为他们的“包容性”感到非常自豪,但看起来它不适用于经验不足的开发新手。
我个人认为Google对Android框架所做的更改会导致巨大的人类潜力浪费。阅读所有这些更改需要花费数小时,更不用说实际实施它们了。我宁愿花更多的时间来创造价值,而不是追逐自己的尾巴。
在这篇文章中,我试图总结有关Android原生开发现状的一些重要的内容,我还对未来做了一些预测。这篇文章并不完美:它可能包含一些错误,而且还错过了一些其他重要内容。请随时在下面的评论中纠正我。但是请记住,本文没有任何私人内容。我知道我提出了一些非常有争议的观点,但是我相信这是对的。
我还在本文的多个地方引用了我之前写的一些文章。我这样做并不是为了炫耀并说“看,我是正确的!”,而是让您能够了解我过去的预测并将其与实际发生的情况进行比较。当我写这些文章时,他们读起来就像您今天读本篇文章一样疯狂。但是我所做的预测却非常准确。
当然,我也想说:“看,我是对的!”。我冒着巨大的专业风险发表了这些有争议的预测,在得知自己没有误导读者后我感到非常的欣慰。即使有时候我宁愿自己是错的,也希望Google成为真正的合作伙伴。但是到目前为止,情况并非如此。
最后附上文章作者对于跨平台开发的一些看法,仅供参考。
在这里小编也分享一份私货,自己收录整理的Android学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习
如果你有需要的话,可以点赞+评论,关注我, 加Vx:15388039515(备注思否,需要进阶资料)
「译」Go 安全性备忘单:Go 开发者的 8 个安全性最佳实践
原文地址:https://snyk.io/blog/go-security-cheatsheet-for-go-developers/
原文作者:Eric Smalling, Gerred Dillon
本文永久链接:https://github.com/gocn/translator/blob/master/2021/w10_Go_Security_cheatsheet.md
译者:guzzsek
校对:lsj1342,fivezh
在我们的备忘单系列中,我们将为 Go 开发者介绍八项有关于 Go 安全的最佳实践。Go 语言集成了许多内置特性,这些功能(与 C 等较早和较低级别的语言相比)比如 -- 内存垃圾回收和强类型指针,可以促进更安全的开发实践。
这些功能使得开发人员不需要自行管理内存,从而避免了一些可能被利用的漏洞。然而,开发人员仍需了解一些安全最佳实践。在 Dan Enman(Snyk的高级软件工程师)的帮助下,由 Eric Smalling 和 Gerred Dillon 撰写的这份备忘单,涉及到其中的一些更常见的主题。
-
使用 Go Modules -
扫描 CVE 之间的依赖关系 -
使用 Go 标准加密软件包 -
使用 html/template 来避免 XSS 攻击 -
shell 子进程 -
避免使用 unsafe 和 cgo 特性 -
谨慎使用反射 -
最小化容器攻击面
1.使用 Go Modules
从 go 1.11 版本开始, Go Modules 正式成为 go 版本依赖的管理工具,而旧的 Vendor 和 Dep 工具已被弃用。Go Modules 允许指定依赖项版本,包括可传递模块,还可以通过 go.sum
文件的校验和数据库防止产生非预期的模块变化。
首先,您应该在当前目录的中运行 go mod init [namespace/project-name]
命令来初始化项目。
$ go mod init mycorp.com/myapp
这将在当前目录中创建一个 go.mod
文件,其中包含您的项目名称和当前使用的 Go 版本。假如您的源代码需要引入第三方库,则只需运行 go build
(或 test,install
等)命令,即可通过所用模块及其版本来更新 go.mod 文件。还可以使用 go get 将依赖更新至特定版本,并将其更新至 go.mod。
示例 go.mod
文件:
module mycorp.com/myapp
go 1.15
require (
github.com/containerd/console v1.0.1
rsc.io/quote/v3 v3.1.0
)
请注意,go mod init mycorp.com/myapp 还创建了一个名为 go.sum
的文件。这个文件包含每个第三方库的哈希列表,Go 可以利用该列表来验证每次构建是否都使用相同的依赖。您应该将 go.mod
和 go.sum
文件都加入到版本管理中。
可以在 Go 的官方博客上找到关于 Using Go Modules 的教程,这是学习更多有关于 Go Modules 的极好资源,包括学习如何指定依赖版本,清理未使用的依赖等等。
2.扫描 CVE 之间的依赖关系
与大多数项目一样,您的应用程序所依赖的模块中的代码量通常会超过应用程序本身的代码量,而依赖的这些第三方库通常是引入安全漏洞的一个途径。借助 Snyk 这类的工具, 一款由我们提供的通用漏洞数据库,可以测试这些依赖关系图中的已知漏洞,建议进行升级以修复所发现的问题,甚至以持续监视您的项目中是否存在将来发现的任何新漏洞。
例如,仅在 Go 应用程序上运行 synk test
将解析您的模块并报告任何已知的 CVEs,以及有关您可以升级到的任何修复版本的信息。此外,基于 Web 的 Snyk 工具可以直接并连续监视 GitHub 仓库,即使在您未更改代码或在其上运行 CI 构建的情况下,也可以提醒您将来发现的漏洞。
3.使用 Go 标准加密软件包而不是第三方所提供的
Go 标准库加密程序包已经过安全研究人员的严格审核,但由于它们提供的功能并不全面,因此您可能会想使用第三方程序包。
就像不使用自己的加密算法一样,您应该非常警惕第三方加密库,因为它们可能会或可能不会受到相同级别的审核。您应该需要清楚地知道您的应用程序所依赖包的来源。
4.使用 html/template 来避免 XSS 攻击
使用 io.WriteString()
或 text/template
包传递回 Web 客户端的未经过滤的字符串, 这可能会使您的用户遭受跨站点脚本 (XSS) 攻击。这是因为返回的字符串中的所有 HTML 标签都将不进行编码而呈现到输出流中,并且如果未明确设置,则可能会发送带有错误定义的 Content-Type: plain/text
响应标头。
使用html/template
包是一种简单的自动对返回的内容进行网络编码的方法,而不是尝试在应用逻辑中自己实现。OWASP/GO-SCP 文档有出色的章节和示例,详细介绍了有关这方面的内容。
5.shell 子进程
在 Go 中,shell 子进程基本上可以直接对您的系统进行访问,并且这种方式通常仅限于命令行工具类型的应用程序。在可能的情况下,始终希望使用适当的模块在 Go 代码中本地实现(译者注:尽量使用 go 代码实现, 而不是依赖于调用系统外部命令)。
如果您确实需要使用 shell 子进程,请务必清理可能传递给 shell 的任何外部来源数据以及返回的数据,以确保您的应用程序不会暴露有关基础系统的不必要的详细信息(译者注:就是不要对外暴露操作系统的基本信息)。这种考虑类似于要注意模板渲染攻击(请参阅上面的#4)或者 SQL 命令注入。还应考虑将调用运行外部流程作为应用程序请求线程的一部分进行操作可能会产生其他副作用,这些副作用是您无法从 Go 代码中控制的,例如对文件系统的更改,对外部依赖项的调用或对安全格局的更改 可能会阻止此类调用-例如,由在容器中运行或由 AppArmor,SELinux 等工具施加的限制
6.谨慎使用 unsafe 和 cgo
与 C 语言非常相似,Go 支持使用指针类型变量, 但是,go 语言中的指针具有严格的类型安全性,以保护开发者免受意外或者恶意的副作用。在C语言中,您始终可以定义未分配任何类型的 void *
指针;要在 Go 中执行相同的操作,请使用恰当命名的 unsafe
标准包来打破类型安全性限制。Go文档中通常不建议使用 unsafe
,因为它可以直接访问内存,再加上用户数据,攻击者有可能破坏 Go 的内存安全性。
同样令人关注的是使用 cgo
,这是一个功能强大的命令,可让您将任意 C 库集成到 Go 应用程序中。像任何强大的工具一样,必须非常谨慎地使用 cgo
,因为您正相信以不安全的语言编写的完全外部的依赖关系可以正确地完成所有操作。如果该外部代码中潜伏着错误或恶意例程,那么Go内存安全网将无法为您提供保护。可以通过在构建中简单地设置 CGO_ENABLED = 0
来禁用 cgo
,如果您不需要显式使用 cgo
,这通常是一个安全的选择,因为大多数现代的 Go 库都是用纯 Go 代码编写的。
7.反射
Go 是一种强类型语言,这意味着变量类型很重要。有时,您需要在运行时代码中反映的变量类型或值的相关信息。Go 提供了一个 reflect
包,它允许您查找和操纵任意类型的变量的类型和值,例如,确定变量是否属于某种类型,或者包含某些属性或函数。
尽管反射很有用,但也增加了在 Go 代码中运行时引入错误的风险。如果您尝试以错误的方式修改被反射的变量(例如,设置无法在结构上设置的值),则代码会引发 panic。很难很好地掌握代码流以及所反映的各种类型和值类型。最后,在使用反射类型或值时,您可能需要断言这可能会使代码混淆的类型,并导致运行时错误。
尽管反射功能很强大,但是在 Go 的类型和接口系统中,应很少使用它,因为它很容易引发意想不到的问题。
8. 最大限度地减小容器攻击面
许多 Go 应用程序没有外部依赖关系,并且设计为可以在容器中运行,因此我们应该使用几种镜像构建技术来简化对它们可用的文件系统。最简单的方法之一是使用多阶段 Dockerfile,在该阶段我们在构建阶段构建应用程序,然后将临时基础镜像用于部署工件镜像。
看一下以下 Dockerfile 示例:
1| FROM golang:1.15 as build
2|
3| COPY . .
4|
5| ENV GOPATH=""
6| ENV CGO_ENABLED=0
7| ENV GOOS=linux
8| ENV GOARCH=amd64
9| RUN go build -trimpath -v -a -o myapp -ldflags="-w -s"
10| RUN chmod +x go-goof
11|
12| RUN useradd -u 12345 moby
13|
14| FROM scratch
15| COPY --from=build /go/myapp /myapp
16| COPY --from=build /etc/passwd /etc/passwd
17| USER moby
18|
19| ENTRYPOINT ["/myapp"]
如果您是 Dockerfile 的新手,那么它们是逐步的说明,几乎所有 OCI 镜像构建都可以使用它们来构建镜像。它们记录在这里。此示例是一个多阶段 Dockerfile,具有两个不同的阶段:构建阶段和最终的运行时镜像阶段
阶段1,第1-12行:构建阶段
从官方的 golang:1.15
基础镜像开始,在此阶段,我们将设置一些环境变量并构建我们的 Go 应用程序。当此阶段完成时,将使用带有 build 标签的临时镜像进行缓存,稍后我们可以参考该标签。
您可能想知道我们传递到构建中的所有环境 var 和参数是什么:
-
GOPATH=””
:清除此变量(在golang:1.15
基础镜像中设置),因为在使用Go模块时不需要此变量。 -
CGO_ENABLED=0
: Disablescgo
(see section 6 above). -
CGO_ENABLED=0
:禁用cgo
(请参阅上面的第6节)。 -
GOOS=linux
: Explicitly tells go to build for the linux operating system. -
GOOS=linux
:明确告诉go用于linux操作系统的构建。 -
GOARCH=amd66
:明确告知 go 构建amd64
(Intel)体系结构的镜像。 -
-trimpath
:从二进制文件中删除文件系统路径信息。 -
ldflag -s
:省略符号表和调试信息。 -
ldflag -w
:省略DWARF符号表。
该设置集合将构建几乎所有可能的最小二进制文件,但是其中一些可能不适用于您的应用程序,因此请根据需要进行选择。
第2阶段,第13-10行:运行时镜像阶段
在此阶段,我们只需将静态二进制文件和 /etc/passwd
文件从 build
阶段复制到一个空的暂存文件系统中,指定适当的所有权和在容器启动时运行的命令。
要构建此镜像,我们只需在与Dockerfile相同的目录中运行以下命令:
docker build -t [app image name]:[version tag] .
注意:.
在该构建行的末尾,这一点很重要,因为它告诉构建系统在哪里可以找到 Dockerfile 及其可能引用的任何其他文件。
生成的镜像将具有一个包含两个文件的文件系统:我们的应用程序和具有 moby
用户的 passwd 文件(用户名无关紧要,我们只是不想在容器中以 root 身份运行任何文件)。不会有 sh
,ps
或攻击者可以利用的任何其他文件。当然,如果您需要其他文件来运行应用程序,则需要包含这些文件或在运行时将其挂载。
本文分享自微信公众号 - GoCN(golangchina)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
我们今天的关于「译」开发者如何提升和推销自己和如何激发开发者选项的分享就到这里,谢谢您的阅读,如果想了解更多关于Android 开发者,如何提升自己的职场竞争力?、jvm系列(七):如何优化Java GC「译」、「译」Android原生开发的现状与未来、「译」Go 安全性备忘单:Go 开发者的 8 个安全性最佳实践的相关信息,可以在本站进行搜索。
本文标签: