面试必问JVM高频知识点总结

480 阅读25分钟

原文地址:github.com/qinxuewu/do…

[TOC]

JAVA内存模型与JVM内存模型的区别

  • JAVA内存模型:Java内存模型规定所有的变量都是存在主存中,每个线程都有自己的工作内存。线程堆变量的操作都必须在工作内存进行,不能直接在主存进行操作,并且每个线程不能访问其他线程的工作内存

jvm线上问题排查思路

  • 思路:首先找到 CPU 飚高的那个 Java 进程,因为你的服务器会有多个 JVM 进程。然后找到那个进程中的 “问题线程”,最后根据线程堆栈信息找到问题代码。最后对代码进行排查
  • 通过 top命令找到CPU消耗最高的进程,并记住进程 ID。
  • 再次通过 top -Hp [进程 ID]找到CPU 消耗最高的线程 ID,并记住线程 ID.
  • 通过 JDK 提供的 jstack 工具dump 线程堆栈信息到指定文件中。具体命令jstack -l [进程 ID] >jstack.log。
  • 由于刚刚的线程ID是十进制的,而堆栈信息中的线程ID是16进制的,因此我们需要将10进制的转换成16进制的,并用这个线程 ID 在堆栈中查找。使用 printf "%x\n"[十进制数字],可以将10进制转换成16进制。
  • 通过刚刚转换的16进制数字从堆栈信息里找到对应的线程堆栈。就可以从该堆栈中看出端倪

说下一Java的内存模型

  • Java内存模型是JVM的抽象模型,就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
  • 目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性。
  • Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存.不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

说一下jvm 的主要组成部分?及其作用?

  • JVM基本上由三部分组成:类加载器,执行引擎,运行时数据区
  • 类加载器:在JVM启动时以及程序运行时将需要加载的class文件加载到JVM中
  • 执行引擎:负责执行class文件中包含的字节码指令,相当于物理机器上的CPU
  • 运行时数据区:将划分给Java程序的内存划分成几个区来模拟物理机器上的存储、记录和调度功能

说一下 jvm 运行时数据区?

运行时数据区

  • 线程私有的:虚拟机栈,本地方法栈,程序计数器
  • 线程共享的 方法区,堆
  • 程序计数器可以看作是当前线程所执行的字节码行号指示器。通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要这个计数器来完成
  • 虚拟机栈:每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程
  • 本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
  • 方法区主要用于存放已经被虚拟机加载的类信息,如常量,静态变量,即时编译器编译后的代码等。和Java堆一样不需要连续的内存,并且可以动态扩展
  • Java 堆是整个虚拟机所管理的最大内存区域,所有的对象创建都是在这个区域进行内存分配。堆内存也分为 新生代、老年代。

说一下堆栈的区别?

  • 栈内存:栈内存首先是一片内存区域,存储的都是方法的局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。
  • 堆内存:存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。

队列和栈是什么?有什么区别?

  • 队列(Queue):是限定只能在表的一端进行插入和在另一端进行删除操作的线性表;
  • 栈(Stack):是限定只能在表的一端进行插入和删除操作的线性表。
  • 队列是先进先出,栈是先进后出
  • 队列:基于地址指针进行遍历,而且可以从头部或者尾部进行遍历,但不能同时遍历,无需开辟空间,因为在遍历的过程中不影响数据结构,所以遍历速度要快;
  • 栈:只能从顶部取数据,也就是说最先进入栈底的,需要遍历整个栈才能取出来,而且在遍历数据的同时需要为数据开辟临时空间,保持数据在遍历前的一致性。

什么是双亲委派模型?

  • 如果一个类收到加载请求时,它不会先自己去尝试加载这个类,而是委派给父类加载器去加载,只有当父类加载器在自己的搜索范围找不到这个类时,才会委派给子类加载器去执行加载。
  • 优点:加载的类是同一个,保证内库更安全,缺点效率低

说一下类加载的执行过程?

类的加载过程.png

  • 类的加载过程分为加载,验证,准备,解析,初始化,使用,卸载七个阶段。其中准备,解析和初始化统称为链接阶段。其中类加载工作由ClassLoader及其子类负责。
  • 加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定
  • 类装载包括了加载,连接(验证、准备、解析(可选)),初始化
  • 加载指的是把class字节码文件从各个来源通过类加载器装载入内存中
  • 验证是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误
  • 准备是为类变量(注意,不是实例变量)分配内存,并且赋予初值
  • 解析是将常量池内的符号引用替换为直接引用的过程
  • 初始化,这个阶段主要是对类变量初始化,是执行类构造器的过程。
  • 使用阶段包括主动引用和被动引用
  • 卸载,类所有的实例都已经被回收,加载该类的ClassLoader已经被回收,该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法,jvm就会在方法区垃圾回收的时候对类进行卸载

说一下类加载器有哪些?

双亲委派模型.png

  • 启动类加载器:负责加载JRE的核心类库,如jre目标下的rt.jar,charsets.jar等.
  • 扩展类加载器:负责加载JRE扩展目录ext中JAR类包
  • 系统类加载器:负责加载ClassPath路径下的类包
  • 用户自定义加载器:负责加载用户自定义路径下的类包

怎么判断对象是否可以被存活?

GCroot

  • java是使用根搜索算法判断对象是否存活的
  • 通过一系列的名为“GC-roots"的对象作为起点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象的GC-roots没有任何引用链相连时,则证明此对象是不可用的
  • 可以作为GC-Roots对象有虚拟机栈中的引用对象,方法区中的类静态属性引用对象,方法区中的常量引用的对象,本地方法中JNI(即一般说的native方法)的引用的对象。

java 中都有哪些引用类型?

  • 四种:强引用,软引用,弱引用,虚引用
  • 只要强引用还存在,垃圾回收期永远不会回收掉被引用的对象
  • 软引用:用来描述一些还有用,但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出前,将会把这些对象列进回收范围之内并进行第二次回收,如果这此次回收还是没有足够的内存,才会抛出内存溢出
  • 弱引用:用来描述非必须的对象,但是它的强度比软引用更弱一下,被弱引用关联的对象,只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,只会回收被弱引用关联的对象
  • 虚引用:被称为幽灵引用或幻引用,是最弱的一种引用关系。为一个对象设置虚引用的目的就是在对象被回收时收到一个系统通知。

说一下 jvm 有哪些垃圾回收算法?

  • 标记-清除算法:算法分为标记和清除两个阶段。首先先标记所有要被回收的对象,标记完成后再统一清除被标记的对象。效率低,会产生大量不连续的内存碎片
  • 复制算法:将可用内存按容量划分为大小相等的两块,每次只用其中的一块,当这一块内存用完,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉
  • 标记-整理算法:标记过程仍然与标记-清楚算法一样。但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存
  • 分代收集算法:根据对象的存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高,没有额外空间对它进行分配担保,就必须使用标记-清理或标记-整理算法来进行回收

说一下 jvm 有哪些垃圾回收器?

垃圾收集器.png

  • Serial收集器:一个单线程的收集器,只会使用一个CPU或一条收集线程去完成垃圾收集工作。在进行垃圾收集时必须暂停其它所有的工作线程,直接到结束。是虚拟机运行在Client模式下的默认新手代收集器,简单而高效
  • ParNew收集器:Serial收集器的多线程版本,使用多条线程收集。是许多运行在Server模式下的虚拟机首选新生代收集器。且目前除了Serial收集器,只有它可以与CMS收集器配合工作 -Parallel Scavenge收集器:它是一款新生代收集器。使用复制算法收集,又是并行的多线程收集器
  • Serial Old收集器:它是Serial收集器的老年代版本,是一个单线程收集器,使用标记-整理算法收集
  • Parallel Old收集器:它是Parallel-Scavenge收集器的老年代版本,使用多线程和标记-整理算法。JDK1.6才开始提供。

CMS垃圾收集器.png

  • CMS收集器:是一种以获取最短回收停顿时间的为目标的收集器。基于标记-清楚算法实现。运作过程分为四个阶段。初始标记,并发标记,重新标记,并发清除。
  • 初始标只会标记出所有GC Roots直接应用的对象,虽说会造成Stop the World但影响不大,因为他的速度很快。
  • 并发标记阶段会让系统线程随意创建各种新对象,在这个过程中,垃圾回收线程会尽可能的对已有的对象进行GC-Roots追踪,这个过程系统会不停的工作和创建对象,这个阶段也是最耗时的,但是因为是和系统程序并发允许的,所以其实不会对系统运行造成影响。
  • 重新标记阶段会再次进入Stop-the-World,然后重新标记下在第二阶段里新创建的对象和已有对象可能失去引用变成垃圾的情况,这个速度是很快的,他其实就是对第二阶段中被系统程序运行变动过的少数对象进行标记,所以运行速度很快。
  • 并发清理阶段让系统程序随意运行,然后他来清理掉之前标记为垃圾的对象即可,这个阶段其实也是最耗时的,但他也是和系统程序并发运行,不影响系统程序执行。

G1垃圾收集器

  • G1收集器:将整个Java堆分为多个大小相等的独立区域。虽然保留新生代和老年代,但它们不再是物理隔离,都是一部分不需要连续的集合。特点是并行与并发充分利用CPU缩短停顿时间。分代收集,空间整合不会产生内存空间碎片,可预测的停顿。有计划的避免回收整个Java堆。
  • XX:G1NewSizePercent参数设置新生代初始占比,维持默认值即可。系统运行中JVM其实会不停的给新生代代增加更多的Region,但是新生代占比不会超过60%,可以通过XX:G1MaxNewSizePercent
  • G1中新生代也有Eden区Survivor概念,之前的参数-XX:SurvivorRatio=8就可以区分,比如100个Region,按照默认8:1:1那么可能80个Region就是Eden,两个Survivor各占10个
  • G1可以做到自己设定垃圾回收对我们系统造成停顿的影响,他通过追踪每个Region中可回收对象大小和预估时间,最后在垃圾回收时尽量吧停顿时间控制在我们指定的范围内,同时在有限的时间回收尽量可能多的垃圾对象

G1的新生代垃圾回收

  • 随着系统不停的分配内存,一旦新生代到达设定的最大大小60%,比如1200个Region,里面Eden占据1000,每个Survivor是100,而且Eden区还占满对象,这个时候就会触发新生代GC,使用之前的复制算法进行垃圾回收,进入stop the world状态
  • 然后把Eden对象的Region中存活的独享放入S1对应的Region中。接着回收掉Region中的垃圾对象

G1大对象的判定规则

  • 在G1中大对象的判定规则就是一个大对象超过一个Region大小的50%,就会放入专门的大对象Region中,如果一个对象太大,可以横跨多个Region存放

G1什么时候触发新老年代的混合垃圾回收

  • G1有一个参数XX:InitatingHeapOccupancyPercent 默认值是45%。意思是当老年代占据堆内存的45%的Region时会触发一次混合回收阶段

G1垃圾回收过程

G1回收算法

  • 混合回收阶段会停止程序运行,所有G1允许执行多次混合回收。默认值是8次,可通过参数设置
  • 假设一个混合回收预期要总共回收160个Region,因为要控制可停顿时间,所以第一次回收部分Regon,比如20个,接着系统恢复运行一会,然后在执行一次混合回收,如此反复执行8次,就把所有的Region回收掉了,也把系统停顿时间控制在指定范围内了
  • XX:G1HeapWastePercent 默认值5%。表示在混合回收阶段对Region都基于复制算法进行回收,把要回收的Region中存活对象放入其它Region,然后这个Region中垃圾对象全部清除,这样在回收过程中不断空出新的Region,一旦空闲Region数量达到堆内存的5%,立即停止混合回收,本次混合回收结束。
  • XX:G1MixedGCLiveThreadoldPercent默认值85%。表示确定要回收 的Region是必须存活对象低于85%的才可以进行回收
  • 以上条件都是成功回收的情况下。但是一段回收过程中没有空闲的Region可以承载自己的存活对象,就会触发一次失败,然后立马切换为停止系统程序,采取单线程标记,清理和压缩整理,这个过程是很慢很慢的

新生代与复制算法

  • 目前大部分 JVM的GC对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照1:1来划分新生代。一般将新生代划分为一块较大的 Eden空间和两个较小的Survivor空间(FromSpace, To Space),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中。
  • 这样做最大的好处是之浪费少数的内存空间,内存空间利用率可达到90%

老年代与标记整理算法(Mark-Compact )

  • 对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目前存放对象的那一块),少数情况会直接分配到老生代。
  • 当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC,进行 GC 后, Eden Space 和 From Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和 From Space 进行清理
  • 如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代。
  • 在进行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反复循环
  • 当对象在 Survivor 区躲过一次 GC 后,其年龄就会+1。 默认情况下年龄到达 15 的对象会被移到老生代中

常用的 jvm 调优的参数都有哪些?

  • -Xms20M:表示设置JVM启动内存的最小值为20M,必须以M为单位
  • -Xmx20M:表示设置JVM启动内存的最大值为20M
  • -verbose:gc:表示输出虚拟机中GC的详细情况
  • -Xss128k:表示可以设置虚拟机栈的大小为128k
  • -Xoss128k:表示设置本地方法栈的大小为128k
  • -XX:PermSize=10M:表示JVM初始分配的永久代(方法区)的容量,必须以M为单位

java内存泄漏常见5种情况总结

  • 静态集合类,如HashMap、LinkedList等等引起。如果这些容器为静态的,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。
  • 简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
  • 各种连接,如数据库连接、网络连接和IO连接等,使用完后没释放,将会造成大量的对象无法被回收,从而引起内存泄漏。
  • 对变量设置了不合理的作用域,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生。
  • static关键字的使用问题:它的生命周期是很长的,如果他用来引用一下资源耗费过多的实例 (全局生存期)
  • 对象内存过大:保存了多个耗用内存过大的对象,造成内存超出限制
  • 如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。

MESI缓存一致性协议和总线锁

  • 总线锁:CPU从主内存读取数据到高速缓存,会在总线对这个数据加锁,这样其它CPU没法去读或写这个数据,直到这个CPU使用完数据释放锁之后其它CPU才能读取数据
  • 缓存一致性协议:多个CPU从内存读取同一个数据到各自的高速缓存,当其中某个CPU修改 了缓存里的数据,该数据会马上同步回主内存,其它CPU通过总线嗅探机制可以感知到数据的变化从而将自己的缓存里的数据失效

tomcat需要破坏双亲委派模型的原因

  • tomcat中的需要支持不同web应用依赖同一个第三方类库的不同版本,jar类库需要保证相互隔离;
  • 同一个第三方类库的相同版本在不同web应用可以共享
  • tomcat自身依赖的类库需要与应用依赖的类库隔离
  • jsp需要支持修改后不用重启tomcat即可生效 为了上面类加载隔离和类更新不用重启,定制开发各种的类加载器

JVM参数设置

JVM参数设置.png

  • -Xms:Java堆内存大小
  • -Xmx:Java堆内存最大大小
  • -Xmn:Java堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了
  • -XX:PermSize:永久代大小
  • -XX:MaxPermSize:永久代最大大小
  • -Xss:每个线程的栈内存大小
  • java -Xms512M -Xmx512M -Xmn256M -Xss1M -XX:PermSize=128M -XX:MaxPermSize=128M -jar App.jar

JVM常见配置汇总

//堆设置 
-Xms:初始堆大小 
-Xmx:最大堆大小 
-XX:NewSize=n:设置年轻代大小 
-XX:NewRatio=n:设置年轻代和年老代的比值.如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4 
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值.注意Survivor区有两个.如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5 
-XX:MaxPermSize=n:设置持久代大小
//收集器设置 
-XX:+UseSerialGC:设置串行收集器 
-XX:+UseParallelGC:设置并行收集器 
-XX:+UseParalledlOldGC:设置并行年老代收集器 
-XX:+UseConcMarkSweepGC:设置并发收集器

//垃圾回收统计信息 
-XX:+PrintGC 
-XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps 
-Xloggc:filename
-XX:+DisableExplicitGC来禁用JVM对显示GC的响应
//并行收集器设置 
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数.并行收集//线程数. 
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间 
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比.公式为1/(1+n)
//并发收集器设置 
-XX:+CMSIncrementalMode:设置为增量模式.适用于单CPU情况. 
-XX:CMSInitiatingOccupancyFaction  设置CMS收集器老年代占用多少比例的时候触发CMS垃圾回收,JDK 1.6里面默认的值是92%。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数.并行收集线程数.
-XX:+UseCMSCompactAtFullCollection  在Full GC之后要再次进行“Stop the World”,停止工作线程,然后进行碎片整理,就是把存活对象挪到一起,空出来大片连续内存空间,避免内存碎片。
-XX:CMSFullGCsBeforeCompaction  意思是执行多少次Full GC之后再执行一次内存碎片整理的工作,默认是0


// JVM参数参考模板,根据系统情况设置不同参数即可
-Xms4096M -Xmx4096M -Xmn3072M -Xss1M  
-XX:MetaspaceSize=256M 
-XX:MaxMetaspaceSize=256M 
-XX:+UseParNewGC 
-XX:+UseConcMarkSweepGC 
-XX:CMSInitiatingOccupancyFraction=92 
-XX:+UseCMSCompactAtFullCollection 
-XX:CMSFullGCsBeforeCompaction=0 
-XX:+CMSParallelInitialMarkEnabled  // (初始标记阶段)开启该阶段的并行标记
-XX:+CMSScavengeBeforeRemark  // 开启在CMS重新标记阶段之前的清除尝试
-XX:+DisableExplicitGC 
-XX:+PrintGCDetails 
-Xloggc:gc.log 
-XX:+HeapDumpOnOutOfMemoryError  
-XX:HeapDumpPath=/usr/local/app/oom

GC的详细执行流程

GC执行流程

  • 开始执行GC,首先检查老年代可用空间是否大于新生代全部对象
  • 如果大于则执行Minor GC。否则检查-XX:HandlePromotionFaulure参数是否开启,
  • 如果开启上述参数,则判断老年代可用空间是否大于历次Minor GC过后进入老年代的对象平均大小
  • 如果大于 则放心执行Minor GC
  • 如果小于则执行Full GC,然后执行Minor GC
  • 如果Full GC,然后执行Minor GC过后 内存还是不够则抛出OOM

Young GC和Full GC分别在什么情况下会发生

  • Young GC其实一般就是在新生代的Eden区域满了之后就会触发,采用复制算法来回收新生代的垃圾
  • 发生Young GC之前进行检查,如果“老年代可用的连续内存空间” < “新生代历次Young GC后升入老年代的对象总和的平均大小”,说明本次Young GC后可能升入老年代的对象大小,可能超过了老年代当前可用内存空间,此时必须先触发一次Old GC给老年代腾出更多的空间,然后再执行Young GC
  • 执行Young GC之后有一批对象需要放入老年代,此时老年代就是没有足够的内存空间存放这些对象了,此时必须立即触发一次Old GC
  • 老年代内存使用率超过了92%,也要直接触发OldGC,当然这个比例是可以通过参数调整的

什么情况下会栈溢出

  • 栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口灯信息。局部变量表又包含基本数据类型,对象引用类型(局部变量表编译器完成,运行期间不会变化)
  • 所以我们可以理解为栈溢出就是方法执行是创建的栈帧超过了栈的深度。那么最有可能的就是方法递归调用产生这种结果。
  • 局部数组变量空间太大,局部变量是存储在栈中的;解决这类问题的办法有两个,一是增大栈空间,二是改用动态分配,使用堆(heap)而不是栈(stack)

如何预估估算系统压力和JVM内存的分配

byte 1字节 ,short 2字节 
int 4字节,long 8字节,float 4字节,
double 8字节,boolean 1字节,char 2字节 
一个Unicode码占两字节

  • 平时做秒杀活动涉及到基本接口秒杀下单。一个订单可能有600字节大小,,公司需求不大,一秒处理500请求即可。一秒内存消耗300KB,再加上一些杂七杂八的对象,在次基础上扩大20倍,每秒产生6M左右内存
  • 服务器4核8G,堆内存4G,新生代3g,老年代1g,eden区2.5g,两个s区250m。那吗,大约416秒就被占eden,产生一次ygc,也就是6分钟才一次,那其实很低了。
  • 假设遇到羊毛党薅羊毛,一下次请求暴涨到每秒创建5000订单,那吗一秒60M内存占用,41秒触发一次ygc,假设每次有10M正在处理无非回收,那吗s区最多25次就占满,然后进入老年代