JVM从GC角度看,堆的分区情况?
答:java堆从GC角度可分为老年代和新生代.其中新生代.其中 新生代又分为Eden区和两个Survivor区(S0和S1)
为什么要将堆内存分为两块而不是直接一个老年代就行?
答:因为JAVA对象%90以上的对象都是"朝生夕死"其中GC回收的成本很高,为了提高性能所以将新生成的对象放在Eden区,将扛过多次GC的"老家伙"放在老年代
那为什么新生代还需要继续细分?
因为Eden区绝大部分对象寿命很短,那么Eden每次满了清理垃圾,存活的对象被迁移到老年区,老年区满了,就触发Full GC Full GC 是非常耗时的,设立s区的一目的就是在Eden区和老年代中增加了一个缓冲池,发放一些年纪不够老的对象,增加垃圾回收性能
Survivor区会进行垃圾回收吗?
会,但是并非主动进行垃圾回收,是Eden区在进行垃圾回收的时候顺带回收, 默认Eden区和S0和S1区的比利是8:1:1
直接分成一块Eden区和1块S区不行吗?
这涉及到年轻代的垃圾回收算法(复制算法)设置两个Survivor区最大的好处就是解决了碎片化
介绍一下JVM的7中垃圾回收算法?
1.可达性分析算法(标记阶段)
2.标记-清除算法(年轻代清除阶段)
3.复制算法(年轻代清除阶段)
4.标记整理算法(老年代清除阶段)
5.分代收集算法
6.增量收集算法
7.分区算法(G1收集器)
JVM各区域间是如何协同工作的?
jvm可以分为运行时数据区以及类加载器,执行引擎,本地方法库
运行时数据区包含一下部分:
1.方法区:非堆
2.虚拟机栈:本地方法
3.本地方法栈:native方法
4.堆:新生代,老年代
5:程序计数器:标记当前线程所执行行的位置,方便上下文切换完成以后继续执行非运行时数据区:
6.类加载器:启动类加器,扩展类加载器,系统类加载器,自定义类加载器
7.执行引擎:将字节码指令解释/编译为对应平台上的本地机器指令;
8:本地方法库:java调用跨语言(C,C++)的接口
堆,栈,方法区之间数据存储怎么协调的?
答:栈指向方法区;栈指向堆,堆指向方法区,方法区指向堆:
栈---->方法区
动态链接指向klass对象在方法区的地址
栈---->堆
局部变量表存放的引用变量,指向真实对象存放在堆中的地址
方法区---->堆(JDK8以后方法区不在指向堆)
JDK7以前静态性在方法区中;
JDK8以及后,存在堆中元数据类Class中;
堆---->方法区
klass pointer 作用去找到对象依赖的类
双亲委派机制了解吗?
双亲委派:沙箱安全机制,防止核心API库被随意篡改
还有一些场景破坏了双亲委派机制,因为受类加载器范限制,存在某些情况下父类加载器无法加载到需要的文件,在JDBC Tomcat,场景就需要托子类加载器去加载class文件破坏了双亲委派机制.
内存分配策略了解吗?
空闲列表:idea(空闲),used(已用),available(可用)
指针碰撞:自选+CAS
为什么要引入元空间?
1.永久代缺点
存在内存瓶颈
GC:字符串常量池,动态字节码
存储类信息
2.元空间能解决永久的问题,它本身还存在问题吗?
元空间存储应用程序的类加载信息,当前实现回收以后会产生内存碎片
说一下G1收集器
system.gc 和 runtime. getRuntime(). gc() 会做些什么事?
System.gc() 在内部调用 Runtime.gc()。
硬要说区别的话 Runtime.gc() 是 native method。
而 System.gc() 是非 native method,它依次调用 Runtime.gc();调用gc方法在默认情况下,会显示触发full gc,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。
system.gc 调用附带一个免责声明,无法保证垃圾收集器的调用。即gc()函数的作用只是提醒虚拟机,程序员希望进行一次垃圾回收。但是这次回收不能保证一定进行,具体什么时候回收取决于jvm。如果每次调用gc方法后想让gc必须执行,可以追加调用system. runFinalization方法。
JVM调由思路
其实工作中,很少有机会能接触到 jvm 调优,大部分时间都是在写 CRUD 代码,但如果万一线上真的出问题了,那么再去想 jvm 调优就有点晚了,所以我们需要先把这部分知识储备起来。
面试官思路:主要是想看下你对造成 JVM 性能问题有没有思考总结过。
可以从三个方面说:
-
工作中引起 JVM 性能问题的原因到底是代码问题还是 JVM 参数问题?
-
JVM 性能问题如何监控和排查?
-
如何根据性能问题进行参数调优?
代码排查
首先第一个方面,其实大部分 JVM 性能问题,并不是我们设置的参数问题,一般情况下,都是用默认参数就搞定了,而真正出问题的情况多是自己写的代码有问题,如频繁创建大对象,然后又引用它们不释放,然后这些大对象进入了老年代后,垃圾收集器有回收不了它们,老年代内存不足,造成频繁 Full GC,每次 Full GC 都会触发 STW,也就是造成卡顿现象,这样性能不就很差了吗?
如何监控
这个就是为了记录日志用的,我们可以利用日志来快速定位性能问题。
-
记录日志就是这个命令了:
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:gc.log复制复制失败复制成功
-
然后还可以设置内存溢出后自动导出Dump文件:
-XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\jvm.dump复制复制失败复制成功
-
另外如果想立即导出 dump 文件,用这个命令就可以了:
jmap -dump:format=b,file=D:/demo.hprof pid复制复制失败复制成功
当然,我们最好是能把 dump 文件获取到,然后放到本地的工具中分析就好办了。
如何分析 dump 文件
第二个方面中,拿到 dunp 文件后,就是分析:
说到常用的分析工具,当然是少不了 jvisualvm 可视化工具了,可以通过输入命令 jvisualvm 打开,然后载入之前的 dump 文件就可以了。这个工具会显示现在有哪些大对象占用着内存在。另外也可以通过 JProfiler 可视化工具来排查。
整体思路
就是拿到 dump 文件,放到可视化工具中分析一把,大部分情况都是大对象造成的,然后再结合自己的代码,看看哪个地方造成了对象创建后没有被回收,然后优化代码就好了。
如何排查 Full GC
有时候,我们只能在线上的服务器上通过命令排查,那么就只能使用命令行工具来排查了,其实思路也很常规:
-
jps -l 找到当前进程的pid
-
ps -mp -o THREAD,tid,time 定位到具体线程。
-
printf “%x\n” ,把线程 pid 转为16进制,比如 0xf58
-
jstack pid | g rep (代表) -A 10 0xf58 查看线程的堆栈日志,还找不到问题继续。
-
实在没办法了,只能 dump 出内存文件用可视化工具进行分析了,然后 定位到代码后修复。
分析 YGC
大多数情况下,新创建的对象都会在新生代的 Eden 区中分配,当 Eden 区没有足够的空间分配时,虚拟机将会发生一次 Minor GC,也就是 YGC。频繁发生 YGC 也是会对性能造成影响的。
分析年轻代对象增长速率。
每5秒执行一次,执行10次,然后观察这50秒内 eden 区增加的趋势,即可知道年轻代对象增长的速率。
jstat -gc pid 5000 10复制复制失败复制成功
思路:如果 eden 区增长很快,那么发生 YGC 的频率也会很高,说明 Eden 区太小了,可以调大 Eden 区(调整 -Xmn 参数),然后再次进行测试,看小是否减少了 YGC 回收频率。
另外如果 YGC 后,存活的对象超过了 Survivor 的 50%,则会进入老年代。
我们的调优思路是尽量减少对象进入老年代,以减少发生 FGC 的频率。所以通过调整 Eden 区的大小,减少了对象进入老年代的频率。
参数调优
第三个方面,如何进行参数调优。一般情况下,参数用默认的就好了,但是某些场景还是要进行参数调优的。
调优思路如下:
-
第一步肯定是看下到底配置了哪些参数:jinfo -flags pid。
-
然后看下 Java 的版本,Java 7 和 Java 8 差别有点大的,Java 8 取消了永久区,新增了 metaspace 区,具体对 垃圾回收的影响,放到后面分享。查看 Java 版本的命令:jinfo -sysprops pid。
-
一般设置-Xms=-Xmx,这样可以获得固定大小的堆内存,减少GC的次数和耗时,可以使得堆相对稳定。
-
-Xmn 设置新生代的大小,太小会增加 YGC,太大会减小老年代大小,一般设置为整个堆的1/4到1/3。
-
设置-XX:+DisableExplicitGC禁止系统System.gc(),防止手动误触发FGC造成问题。
什么时候触发垃圾回收?
一般就分为 Minor GC 和 Full GC 两种情况。
年轻代发生垃圾回收的时机(Minor GC)
-
当 Eden 区没有足够空间分配时
整堆触发垃圾回收的时机 (FULL GC)
-
当年轻代晋升到老年代的对象大小比目前老年代剩余的空间大小还要大时。
-
当老年代的空间使用率超过某阈值时
-
当元空间不足时(JDK1.7永久代不足)
-
调用 System.gc() 时,系统建议执行 Full GC,但是不必然执行。