目录
1、详细介绍一下JVM内存模型
2、说一下JVM内存结构(Java内存结构/Java内存区域)、Java内存模型区别与关系
3、讲讲什么情况下会出现内存溢出,内存泄漏?
4、说说线程栈
5、JVM 年轻代到年老代的晋升过程的判断条件是什么呢?
6、JVM 出现 fullGC 很频繁,怎么去线上排查问题
7、类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?
8、类的实例化顺序
9、JVM垃圾回收机制,何时触发MinorGC等操作
10、JVM 中一次完整的 GC 流程(从 ygc 到 fgc)是怎样的
11、各种回收算法
12、各种回收器,各自优缺点,重点CMS、G1
13、stackoverflow错误,permgen space错误
1、 详细介绍一下JVM内存模型
根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。
具体可能会 聊聊jdk1.7以前的PermGen(永久代),替换成Metaspace(元空间)
原本永久代存储的数据:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap
Metaspace(元空间)存储的是类的元数据信息(metadata)
元空间的本质和永久代类似,都是对JVM规范中方法区的实现 。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存 。
替换的好处 :一、字符串存在永久代中,容易出现性能问题和内存溢出。二、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
图片来源:https://blog.csdn.net/tophawk/article/details/78704074
参考资料:
https://www.cnblogs.com/paddix/p/5309550.html
2、说一下JVM内存结构(Java内存结构/Java内存区域)、Java内存模型区别与关系
参考:https://blog.csdn.net/Soinice/article/details/98038991
3、讲讲什么情况下会出现内存溢出,内存泄漏?
内存泄漏的原因很简单:
常见的内存泄漏例子:
public static void main(String[] args) {
Set set = new HashSet<>();
for (int i = 0; i < 10; i++) {
Object object = new Object();
set.add(object);
// 设置为空,该对象不再使用
object = null;
}
// 但是set集合中还维护object的引用,gc不会回收object对象
System.out.println(set);
System.out.println(set.size());
}
}
输出结果
[java.lang.Object@74a14482,
java.lang.Object@677327b6,
java.lang.Object@6d6f6e28,
java.lang.Object@4554617c,
java.lang.Object@45ee12a7,
java.lang.Object@1b6d3586,
java.lang.Object@7f31245a,
java.lang.Object@135fbaa4,
java.lang.Object@1540e19d,
java.lang.Object@14ae5a5]
10
Process finished with exit code 0
解决这个内存泄漏问题也很简单,将set设置为null,那就可以避免上述 内存泄漏问题了。其他内存泄漏得一步一步分析了。
内存泄漏参考资料:
https://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/
内存溢出的原因:
内存泄露导致堆栈内存不断增大,从而引发内存溢出。
大量的jar,class文件加载,装载类的空间不够,溢出
操作大量的对象导致堆内存空间已经用满了,溢出
nio直接操作内存,内存过大导致溢出
解决:
查看程序是否存在内存泄漏的问题
设置参数加大空间
代码中是否存在死循环或循环产生过多重复的对象实体、
查看是否使用了nio直接操作内存。
参考资料:
https://www.cnblogs.com/bingosblog/p/6661527.html
http://www.importnew.com/14604.html
4、说说线程栈
这里的线程栈应该指的是虚拟机栈吧...
JVM规范让每个Java线程 拥有自己的独立的JVM栈 ,也就是Java方法的调用栈。
当方法调用的时候,会生成一个栈帧 。栈帧是保存在虚拟机栈中的,栈帧存储了方法的局部变量表、操作数栈 、动态连接和方法返回地址等信息
线程运行过程中,只有一个栈帧是处于活跃状态 ,称为“当前活跃栈帧”,当前活动栈帧始终是虚拟机栈的栈顶元素 。
通过jstack 工具查看线程状态
参考资料:
http://wangwengcn.iteye.com/blog/1622195
https://www.cnblogs.com/Codenewbie/p/6184898.html
https://blog.csdn.net/u011734144/article/details/60965155
5、JVM 年轻代到年老代的晋升过程的判断条件是什么呢?
部分对象会在From和To区域中复制来复制去,如此交换15次 (由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。
如果对象的大小大于Eden的二分之一会直接分配在old ,如果old也分配不下,会做一次majorGC,如果小于eden的一半但是没有足够的空间,就进行minorgc也就是新生代GC。
minor gc后,survivor仍然放不下,则放到老年代
动态年龄判断 ,大于等于某个年龄的对象超过了survivor空间一半 ,大于等于某个年龄的对象直接进入老年代
6、JVM 出现 fullGC 很频繁,怎么去线上排查问题
这题就依据full GC的触发条件来做:
如果有perm gen的话(jdk1.8就没了),要给perm gen分配空间,但没有足够的空间时 ,会触发full gc。
所以看看是不是perm gen区的值设置得太小了。
System.gc()
方法的调用
当统计 得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间 ,则会触发full gc(这就可以从多个角度上看了)
是不是频繁创建了大对象(也有可能eden区设置过小) (大对象直接分配在老年代中,导致老年代空间不足--->从而频繁gc)
是不是老年代的空间设置过小了 (Minor GC几个对象就大于老年代的剩余空间了)
7、类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?
双亲委托模型的重要用途是为了解决类载入过程中的安全性问题 。
假设有一个开发者自己编写了一个名为java.lang.Object
的类,想借此欺骗JVM。现在他要使用自定义ClassLoader
来加载自己编写的java.lang.Object
类。
然而幸运的是,双亲委托模型不会让他成功。因为JVM会优先在Bootstrap ClassLoader
的路径下找到java.lang.Object
类,并载入它
Java的类加载是否一定遵循双亲委托模型?
在实际开发中,我们可以通过自定义ClassLoader,并重写父类的loadClass方法 ,来打破这一机制。
SPI就是打破了双亲委托机制的(SPI:服务提供发现)。SPI资料:
https://zhuanlan.zhihu.com/p/28909673
https://www.cnblogs.com/huzi007/p/6679215.html
https://blog.csdn.net/sigangjun/article/details/79071850
参考资料:
https://blog.csdn.net/markzy/article/details/53192993
8、类的实例化顺序
1. 父类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
2. 子类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
3. 父类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
4. 父类构造方法
5. 子类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
6. 子类构造方法
检验一下是不是真懂了:
public class Base {
private String name = "博客:Soinice";
public Base() {
tellName();
printName();
}
public void tellName() {
System.out.println("Base tell name: " + name);
}
public void printName() {
System.out.println("Base print name: " + name);
}
}
public class Dervied extends Base {
private String name = "Java3y";
public Dervied() {
tellName();
printName();
}
@Override
public void tellName() {
System.out.println("Dervied tell name: " + name);
}
@Override
public void printName() {
System.out.println("Dervied print name: " + name);
}
public static void main(String[] args) {
new Dervied();
}
}
输出数据:
Dervied tell name: null
Dervied print name: null
Dervied tell name: Java3y
Dervied print name: Java3y
Process finished with exit code 0
第一次做错的同学点个赞,加个关注不过分吧(hahaha。
9、JVM垃圾回收机制,何时触发MinorGC等操作
当young gen中的eden区分配满的时候触发MinorGC(新生代的空间不够放的时候).
10、JVM 中一次完整的 GC 流程(从 ygc 到 fgc)是怎样的
这题不是很明白意思(水平有限...如果知道这题的意思可在评论区留言呀~~)
因为按我的理解:执行fgc是不会执行ygc的呀~~
YGC和FGC是什么
YGC :对新生代堆进行gc 。频率比较高,因为大部分对象的存活寿命较短,在新生代里被回收。性能耗费较小。
FGC :全堆范围的gc 。默认堆空间使用到达80%(可调整)的时候会触发fgc。以我们生产环境为例,一般比较少会触发fgc,有时10天或一周左右会有一次。
什么时候执行YGC和FGC
a.eden空间不足,执行 young gc
b.old空间不足,perm空间不足,调用方法System.gc()
,ygc时的悲观策略, dump live的内存信息时(jmap –dump:live),都会执行full gc
11、各种回收算法
GC最基础的算法有三种:
标记 -清除算法
复制算法
标记-压缩算法
我们常用的垃圾回收器一般都采用分代收集算法 (其实就是组合上面的算法,不同的区域使用不同的算法)。
具体:
标记-清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
12、各种回收器,各自优缺点,重点CMS、G1
图来源于《深入理解Java虚拟机:JVM高级特效与最佳实现》,图中两个收集器之间有连线,说明它们可以配合使用 .
Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器 ,但可能会产生较长的停顿 ,只使用一个线程去回收。
ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本 。
Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量 。
Parallel Old收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程“标记-整理”算法
CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间 为目标的收集器。它需要消耗额外的CPU和内存资源 ,在CPU和内存资源紧张,CPU较少时,会加重系统负担。CMS无法处理浮动垃圾 。CMS的“标记-清除”算法,会导致大量空间碎片的产生 。
G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征 。
13、stackoverflow错误,permgen space错误
stackoverflow错误主要出现:
在虚拟机栈中(线程请求的栈深度大于虚拟机栈锁允许的最大深度)
permgen space错误(针对jdk之前1.7版本):
未完待续,长期更新~
如果喜欢,或者感觉对你有用的话,希望点个赞,点个关注。