文章目录
- JVM
- 操作系统中 heap 和 stack 的区别
- Java的内存结构
- java堆,新生代老年代
- 堆GC,内存分代及生命周期
- JVM中最大堆大小有没有限制,堆大小通过什么参数设置
- Java中,栈的大小通过什么参数来设置
- 为什么不把基本类型放堆中呢
- Java中的参数传递时传值呢?还是传引用
- 一个空Object对象的占多大空间
- 内存溢出可能原因和解决
- 栈溢出的原因
- GC算法有哪些
- 垃圾回收器有哪些
- 用过什么垃圾回收器
- 如何解决内存碎片的问题
- 如何选择合适的垃圾收集算法
- 吞吐量优先选择什么垃圾回收器?响应时间优先呢
- 什么时候会进行垃圾回收,以及什么时候会触发full GC
- 线上发送频繁full gc如何处理? CPU 使用率过高怎么办?
- 如何进行JVM调优,做过哪些JVM优化?使用什么方法达到什么效果
JVM
操作系统中 heap 和 stack 的区别
堆栈是两种数据结构,他们都是按数据项有序排列的结构,只能在一段进行插入和删除操作,堆为队列优先,先进先出;栈为先进后出。在空间分配上,堆由程序分配释放,栈由操作系统自动分配释放。堆可以看做树。
Java的内存结构
构成:程序计数器,虚拟机栈,本地方法栈,堆,方法区/直接内存(本地内存,元空间,Metaspace)
其中,方法区和堆为共享内存区域
程序计数器:当前线程的字节码的信号指示器
虚拟机栈:就是常说的栈,执行Java方法,里面存储着方法的栈帧,每个栈帧存放着局部变量表(基本数据类型和对象引用),操作数,方法返回地址,动态连接等。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
本地方法栈:执行native方法
堆:存放对象实例,及字符串常量池,分为新生代和老年代,新生代分为伊甸园和两个幸存区,
方法区/元空间,jdk8 废除方法区,将类型信息及运行时常量池(类静态变量)移至元空间,字符串常量池移至堆中。
java堆,新生代老年代
堆中存放数组和引用对象,堆可分为,年轻代和年老带,年轻代分为伊甸园和幸存区 ;新生代和老年代的比例为1:2;新生代中,伊甸园和幸存区的比例为:Edem:from:to = 8:1:1;
堆GC,内存分代及生命周期
Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法。
新生代几乎是所有 Java 对象出生的地方,即 Java 对象申请的内存以及存放都是在这个地方。Java 中的大部分对象通常不需长久存活,具有朝生夕灭的性质。当一个对象被判定为 “死亡” 的时候,GC 就有责任来回收掉这部分对象的内存空间。新生代是 GC 收集垃圾的频繁区域。当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法。现实的生活中,老年代的人通常会比新生代的人 “早死”。堆内存中的老年代(Old)不同于个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作
JVM中最大堆大小有没有限制,堆大小通过什么参数设置
-Xms10m 初始内存
-Xmx10m 最大内存
-XX:MetaspaceSize=5m 元空间初始值
-XX:MaxMetaspaceSize=5m 元空间最大值
Java中,栈的大小通过什么参数来设置
-Xss 每个线程的栈大小
为什么不把基本类型放堆中呢
因为其占用的空间一般是1~8个字节——需要空间比较少,而且因为是基本类型,所以不会出现动态增长的情况——长度固定,因此栈中存储就够了。
Java中的参数传递时传值呢?还是传引用
引用传递或者基本类型值传递
一个空Object对象的占多大空间
使用jvisualvm.exe 抽样内存,64位JVM 每个空对象大小为8字节,如果有变量引用,则引用占用8字节
内存溢出可能原因和解决
- 加载的数据量超过JVM 内存限制
- 内存泄露,无用的对象GC无法回收
- 死循环
- JVM 内存参数设置过低
解决:
- -Xms 、-Xmx 优化内存,初始内存和最大内存最好一致
- 无用对象及时释放,循环内不用使用大量new 对象
- 尽量优化使用“+” 拼接字符串
栈溢出的原因
- 递归导致无法创建更多的栈帧
- 线程爆满,无法创建新线程,所以要使用线程池技术
GC算法有哪些
标记-清除:标记可回收对象,进行清除,效率低,会产生内存碎片
复制:将内存划分为等量的两块,将存活的对象复制到另外一块内存中,当前使用的内存块全部清理掉,内存使用率降低了一半,进化之后变为: 将一块内存按8:1的比例分为一块Eden区(80%)和两块Survivor区(10%),每次使用Eden和一块Survivor,回收时,将存活的对象一次性复制到另一块Survivor上,如果另一块Survivor空间不足,则使用分配担保机制存入老年代。年轻代则使用该算法
标记—整理 :所有存活的对象向另一端移动,清除边界以外的内存
分代收集:将堆分为新生代和老年代,根据区域特点选用不同的收集算法,如果新生代朝生夕死,则采用复制算法,老年代采用标记清除,或标记整理
垃圾回收器有哪些
Serial收集器:串行收集器,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-整理;垃圾收集的过程中会Stop The World(服务暂停),参数控制:-XX:+UseSerialGC
ParNew收集器,Serial收集器的多线程版本,新生代复制算法并行,老年代标记-整理串行,参数控制:-XX:+UseParNewGC
Parallel收集器:可以理解为ParNew 收集器的智能版本,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量,新生代复制算法、老年代标记-压缩,参数控制:-XX:+UseParallelGC。
Parallel Old收集器:Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法,参数控制: -XX:+UseParallelOldGC
CMS收集器:CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集,CMS收集器是基于“标记-清除”算法实现的,过程分为:初始标记,并发标记,重新标记,并发清除;其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。老年代收集器(新生代使用ParNew),会产生大量空间碎片、并发阶段会降低吞吐量,参数控制:-XX:+UseConcMarkSweepGC
G1收集器:过程分为:标记,Root Region Scanning(程序运行过程中会回收survivor区(存活到老年代)),并发标记,重新标记,复制/清除,与CMS收集器相比G1收集器有以下特点:空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片,可预测停顿
用过什么垃圾回收器
CMS收集器,线上配置的都是使用该收集器,年轻代使用ParNew 算法,老年代使用CMS,参数配置为:-XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=4
如何解决内存碎片的问题
使用复制算法,标记-整理算法,将存活的对象copy到另一片连续的内存区域中,清理当前内存区域
如何选择合适的垃圾收集算法
100MB以下的堆使用串行收集器
应用运行在单核的机器,并且对GC停顿时间有要求,让JVM自己选择垃圾回收器或者使用串行收集器
如果追求应用性能峰值为第一优先级,对GC停顿时间没有太多要求,或者可以接收1秒以上的延迟,让JVM自己选中垃圾收集器或者使用并行收集器
如果响应时间更重要,GC停顿时间必须少于1秒,使用CMS或者G1收集器
吞吐量优先选择什么垃圾回收器?响应时间优先呢
吞吐量优先可以选择并行收集器,如Parallel收集器,相应时间优先,就必须保证GC停顿不能太长,少于1秒,考虑使用CMS或者G!
什么时候会进行垃圾回收,以及什么时候会触发full GC
如果 Eden区域没有足够的空间,那么就会发起一次 Minor GC;对于 Full GC(Major GC)的触发条件:也是如果老年代没有足够空间的话,那么就会进行一次 Full GC
虚拟机在进行minorGC之前会判断老年代最大的可用连续空间是否大于新生代的所有对象总空间
1、如果大于的话,直接执行minorGC
2、如果小于,判断是否开启HandlerPromotionFailure,没有开启直接FullGC
3、如果开启了HanlerPromotionFailure, JVM会判断老年代的最大连续内存空间是否大于历次晋升的平均大小,如果小于直接执行FullGC
4、如果大于的话,执行minorGC
线上发送频繁full gc如何处理? CPU 使用率过高怎么办?
查看GC日志,看是否是内存分配不合理,调整老年代内存分配,CPU内存过高,查看是否有很多IO挂载。
开启空间分配担保
相关命令
top 找到内存占用最大的进程ID
top -Hp [进程ID] 继续跟踪该进程里的所有线程,找到占用CPU过高的线程
jstack [进程ID] > jstack_01 在该文件jstack_01 里搜索 线程id为[16进制的线程id]的线程,查看堆栈信息
jstat -gcutil [进程id] 1000 每1000ms打印一次gc信息
jmap -histo [进程id] > jmap.txt 查看当前jvm内存里对象的实例数和占用内存数
如何进行JVM调优,做过哪些JVM优化?使用什么方法达到什么效果
设置初始化内存和最大内存一致,防止频繁申请内存,-Xms256m -Xmx256m
使用CMS收集器,-XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5
大对象直接进入老年代,-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代中分
晋升老年代的年龄,XX:MaxTenuringThreshold ,在经历了多次的Minor GC后仍然存活:在触发了Minor GC后,存活对象被存入Survivor区在经历了多次MinorGC之后,如果仍然存活的话,则该对象被晋升到Old区
空间分配担保:HandlePromotionFailure,在发生MinorGC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败;如果允许,那只会进行Minor GC;如果不允许,则也要改为进行一次Full GC。大部分情况下都还是会将HandlePromotionFailure开关打开,避免FullGC过于频繁。