- 921. Minimum Add to Make Parentheses Valid 使括号有效的最少添加
- 915. Partition Array into Disjoint Intervals 分割数组
- 932. Beautiful Array 漂亮数组
- 940. Distinct Subsequences II 不同的子序列 II
案例的背景,是一个百万级注册用户的在线教育平台,主要目标用户群体是几岁到十几岁的孩子,注册用户大概是几百万的规模,日活用户规模大概在几十万。
其业务包括选课、排课、浏览课程以及付费购买之类的低频的行为。此外最核心也最为主要的高频行为是 “上课” 。
从该课程面向的群体来说,主要是幼儿园的孩子到中小学的孩子。所以这些群体最活跃使用该平台的时候主要集中在晚上放学之后到八九点之间。以及周末也是最活跃使用该平台的时候。
由此,可以知道,每天晚上那两三小时的高峰期,有将近几十万日活用户集中在该时间段在平台上上在线课程。而白天几乎没有什么流量。夜晚两三小时的流量占比将近99%。
在线教育APP主要偏向于互动方式的授课模式。通过游戏互动让孩子们感兴趣,愿意学,而且通过游戏强互动让他们保持注意了,促使他们对学习到的东西进行输出,提升学习的效果。
所以,这个游戏互动功能,会承载用户高频率、大量的互动点击。
比如在完成什么任务的时候要点击很多的按钮,频繁的进行互动,然后系统后台需要接收大量的互动请求,并且记录下来用户的互动过程和互动结果。
同时,系统得记录下来用户完成了多少个任务,做对了几个,做错了几个,诸如此类的。
假设晚上高峰期内几十万用户同时在线使用平台,平均每个用户大概会使用1小时左右来上课,那么每小时大概会有20万活跃用户同时在线学习。
这20万活跃用户进行的互动操作,大致为每分钟进行1次互动操作,一小时内就会进行60次互动操作。
那么20万用户在1小时内会进行1200万次互动操作,平均到每秒钟大概是 3000 次左右的互动操作。
以每秒钟要承载3000并发请求来看,核心服务需要部署5台4核8G的机器来抗。每台机器每秒钟抗个600请求,这个压力可以接受,也不会导致宕机的风险。
每次互动完成会累加一些对应的“XX币”之类的东西。
大致估算一下,一次互动请求大致会连带创建几个对象,占据几KB的内存,比如我们就认为是5KB吧!那么一秒600请求会占用3MB左右的内存。
基于4核8G的机器来部署系统,然后每台机器每秒会有600个请求会占用3MB左右的内存空间。
假设对机器上的JVM,分配4G给堆内存,其中新生代默认初始占比为5%,最大占比为60%,每个Java线程的栈内存为1MB,元数据区域(永久代)的内存为256M,此时JVM参数如下:
“-Xms4096M -Xmx4096M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:+UseG1GC”
“-XX:G1NewSizePercent” 参数是用来设置新生代初始占比的,不用设置,维持默认值为5%即可。
"-XX:G1MaxNewSizePercent" 参数是用来设置新生代最大占比的,也不用设置,维持默认值为60%即可。
此时堆内存共4G,那么此时会除以2048,计算出每个 Region 的大小,也就是每个 Region 为2MB。刚开始新生代就占5%的Region,也就是新生代只有100个Region,有200MB的内存空间。
G1垃圾回收器中的参数 “-XX:MaxGCPauseMills” ,默认值为200毫秒。意思是每次触发一次GC的时候导致的系统停顿时间(STW)不要超过200毫秒,避免系统因为GC长时间卡死。
该参数一般保持默认值。
当系统运行起来之后,会不停的在新生代的 Eden 区域内分配对象,按照推算是每秒分配3MB的对象。
虽然G1回收器有 “-XX:G1MaxNewSizePercent” 参数限定了新生代最多占用堆内存 60%的空间。
但不代表就是随着系统运行一直给新生代分配更多的 Region,知道新生代占据了 60%的Region之后,无法再分配更多的 Region了,才出发新生代 GC。这种思路是错误的。
案例分析
假设G1回收掉300个Region(600MB内存),大致需要200ms。
那么随着系统运行,每秒创建3MB的对象,大概1分钟左右就会塞满100个Region(200MB内存)。
此时的G1如果有计划出发一次新生代GC,就会去看回收那200MB只需要大概几十ms,最多让系统停顿几十ms,这个时间和设定的 “-XX:MaxGCPauseMills” 参数限制的200ms停顿时间相差甚远。
要是这时触发新生代GC,那么会导致回收完后接着1分钟再次让新生代100个Region塞满,继续触发Minor GC。如此反复,会导致频繁的Minor GC。
如此频繁的Minor GC是很不合理的,所以,G1会考虑给新生代新增一些Region,然后让系统继续运行着,在新生代Region分配新对象,避免频繁的触发新生代GC。
然后系统继续运行,知道可能300个Region都占满了,此时计算后发现回收这300个Region大概需要200ms,那么就可能会触发一次Minor GC。
总结一下,就是G1里是很动态灵活的,它会根据你设定的GC停顿时间给你的新生代不停分配更多 Region。
然后到一定程度,感觉差不多了,就会触发新生代GC,保证新生代GC的时候导致的系统停顿时间在你预设范围内。
以上只是一个示例,实际生产中,G1本身就是这样的一个运行原理,再结合你预设的gc停顿时间,给新生代分配一些Region,然后到一定程度就触发GC,并且把GC时间控制在预设范围内,尽量避免一次性回收过多的 Region 导致GC停顿时间超出预期。
对于G1而言,首先要给整个JVM的堆区域足够的内存,比如这里就给了JVM超过5G的内存,其中堆内存有4G。
接着合理设置 “-XX:MaxGCPauseMills” 参数。
如果这个参数设置的小了,说明每次GC停顿时间可能特别短,此时G1一旦发现你对象几十个Region 占满了就立即出发新生代GC,然后GC频率特别频繁,虽然每次GC时间很短。
比如说30秒触发一次新生代GC,每次就停顿30毫秒。
如果这个参数设置大了,那么可能G1会允许你不停的在新生代里分配新的对象,然后积累很多对象了,再一次性回收几百个 Region。
此时可能一次GC停顿时间就就会达到几百毫秒,但是GC的频率很低。比如30分钟触发一次新生代GC,但是每次停顿500毫秒。
老年代在堆内存里占比超过45%就会触发 Mixed GC。
年轻代的对象进入老年代的几个条件,一个是新生代GC过后存活对象太多没法放入Survivor区域,一个是对象年龄太多,另一个就是动态年龄判定规则。
这里最关键的,就是新生代GC过后存活对象过多无法放入Survivor区域,以及动态年龄判定规则。
这两个条件可能让很多对象快速进入老年代,一旦老年代频繁达到占用堆内存45%的阈值,那么就会频繁触发Mixed GC。
所以Mixed GC本身很复杂,很多参数可以优化,但是优化Mixed GC的核心不是优化它的参数,而是尽量避免对象过快进入老年代,尽量避免频繁触发Mixed GC,就可以做到根本上优化Mixed GC了。
G1优化的核心,是 “-XX:MaxGCPauseMills” 这个参数。如果该参数的值很大,导致系统运行很久,新生代可能都占用了堆内存的60%了,此时才触发新生代GC。
那么存活下来的对象可能就会很多,此时就会导致Survivor区域放不下那么多的对象,就会进入老年代中。
或者是新生代GCC过后,存活下来的对象过多,导致进入Survivor区域后触发了动态年龄判定规则,达到了Survivor区域的50%,也会快速导致一些对象进入老年代中。
总之,调节好 “-XX:MaxGCPauseMills” 这个参数的值,在保证它的新生代GC别太频繁的同时,还得考虑每次GC过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触发Mixed GC。
我目前正在尝试让 g++ 工作,并查看 http://gcc.gnu.org/install/build.html ,我似乎找不到它在哪里说如何“执行编译器的 3 阶段 bootstrap ”。我在哪
James Powell 在他对即将举行的演示文稿的简短描述中说,他自豪地发明了最粗糙的 Python 单行代码之一: (None for g in g if (yield from g) and F
请告诉我我的证明是否正确 We have a connected graph, and specific vertex u in V(G). Suppose we compute the dfs tr
下面的test2和test3结果是不同的。 我对此感到困惑,因为它看起来像相同的逻辑,并且与linux bash ||逻辑不同。 $data = @( [PSCustomObject]@{St
我试图找到一个明确的 G 代码语法规范,而不是单个 G 代码的含义,我无处不在的规范,我的意思是详细的语法规范,目的是编写解析器。 我编写解析器没有问题,我只是在寻找语法规范,例如。我知道您不必总是为
我写了这个 mixin,但它循环了很多时间。你能帮我优化我的代码吗?或者你能建议一些其他的东西来获得想要的结果吗? dfgdfgsdfgsdf 最佳答案 希望这就是您要找的。 $spaces: (4,
默认情况下,g++ 似乎会省略未使用的类内定义方法的代码。示例 from my previous question : struct Foo { void bar() {} void baz(
是否可以将文件内容通过管道传送到 g++编译程序? 我想这样做是因为我想使用数据库中的文件而不是磁盘上的物理文件。可以通过我制作的 API 轻松检索文件内容。 例如,我想做这样的事情: g++ con
如何profile c++代码获取每行代码的调用次数和消耗时间,就像profile工具一样在 Matlab 中呢? 我尝试使用-fprofile-arcs之类的东西,但它只生成代码覆盖率报告,其中可以
如何在几行代码上禁用所有警告。可以使用 GCC 诊断功能禁用特定警告,但是否有针对所有警告的标志。我尝试了这个方法,但不起作用 #pragma GCC diagnostic push #pragma
我有一个链接到 opencv 2.2 的可执行文件。但是,我删除了 opencv 2.2 并安装了 opencv 2.3。 问题是,有没有办法在不重新编译整个源代码的情况下将这个可执行文件链接到新的共
在编译带有一些标志的以下文件时,是否可以让 g++ 显示错误? #include using namespace std; int main() { int arr[ 2 ]; cout
在学习 Haskell 时,我遇到了一个挑战,要找到两个函数 f 和 g,例如 f g 和 f 。 g 是等价的(并且是总计,因此像 f = undefined 或 f = (.) f 这样的东西不算
根据我的理解,Theta 位于 Big O 和 Omega 之间,但我看到了这个声明,但我无法理解为什么交集会出现在这里。我能否对 Θ(g(n)) = O(g(n)) ∩ Ω(g(n)) 获得数学和分
我需要为这个递归函数编写一个迭代函数。 int funcRec(int n){ if(n>1) { return 2*funcRec(n - 1) + 3*funcRec(n
我在 github repository 上有代码示例并在 travis-ci 上创建了一个构建便于复制。 最小的、完整的和可验证的例子 可能不是最小的,但我相信它足够小 它使用 boost.inte
编辑:我们将调用箭头 p纯如果存在这样的函数f即:p = arr f . 我试图更好地掌握 Haskell 中的 Arrows,我想弄清楚什么时候 f >>> (g &&& h) = (f >>> g
我有两个(或更多)函数定义为: val functionM: String => Option[Int] = s => Some(s.length) val functionM2: Int => Op
好像是的。任何直观或严肃的证据都值得赞赏。 最佳答案 没有。 我认为您的问题等同于:给定函数 f 和 g,f 是 O(g) 或 g 是 O(f) 是否总是正确的?这在 SE Computer Scie
如果我设法证明 f(n) = o(g(n))(小 o),那么这两个函数的总和 f( n) + g(n) 应该被“更大”的函数 g(n) 紧紧束缚。 然而,我在证明这一点时遇到了一些麻烦。 最佳答案 以
我是一名优秀的程序员,十分优秀!