!.从进阶到脑死亡的问题
前言
聊聊常见的面试题
1、请你谈谈你对JVM的理解? java8 虚拟机有什么更新?
2、什么是OOM,请你说说OOM产生的原因?如何分析?
3、JVM 的常用调优参数有哪些?
4、内存快照抓取,如何分析,命令是什么?
5、堆里面分区:Eden、Survial(from to)、老年区
6、GC垃圾收集算法有那个几个?谈谈利弊?
BAT 难度的面试题
1、JVM 垃圾回收的时候如何确定垃圾,GC Roots?
2、-X、 -XX 参数你用过哪些?
3、你常用的项目,发布后配置过JVM 调优参数吗?
4、引用、强引用、弱引用、虚引用都是什么,请你谈谈?
5、GC垃圾回收器和GC算法的关系?分别有哪些?
6、谈谈默认的垃圾回收器?
7、G1垃圾回收器的特点?
8、OOM 你看过几种?
1.1什么是jvm
JVM是一种用于计算设备的规范,它是一个虚构出来的计算机
使用JVM就是为了支持与操作系统无关,实现跨平台
1.2JVM内存区域划分
JVM的内部体系结构分为三部分,分别是:类装载器(ClassLoader)子系统,运行时数据区,和执行引擎。
1.2.1类加载器ClassLoader
类的加载、链接和初始化(了解)
加载:查找并加载类的二进制数据
连接:
- 验证:保证被加载的类的正确性;
- 准备:给类静态变量分配内存空间,赋值一个默认的初始值;
- 解析:把类中的符号引用转换为直接引用
在把java编译为class文件的时候,虚拟机并不知道所引用的地址;助记符:符号引用!
转为真正的直接引用,找到对应的直接地址!
初始化:给类的静态变量赋值正确的值;
public class Test{
public static int a = 1;
}
// 1、加载 编译文件为 .class 文件,通过类加载,加载到JVM
// 2、连接
验证(1) 保证Class类文件没有问题
准备(2) 给int类型分配内存空间,a = 0;
解析(3) 符号引用转换为直接引用
// 3、初始化
经过这个阶段的解析,把1 赋值给 变量 a;
// JVM 参数:
// -XX:+TraceClassLoading // 用于追踪类的加载信息并打印出来
// 分析项目启动为什么这么慢,快速定位自己的类有没有被加载!
// rt.jar jdk 出厂自带的,最高级别的类加载器要加载的!
public class Demo02 {
public static void main(String[] args) {
System.out.println(MyChild1.str2);
// 运行的结果
/**
* MyParent1 static
* MyChild1 static
* hello,str2
*/
}
}
class MyParent1{
public static String str = "hello,world";
static {
System.out.println("MyParent1 static");
}
}
class MyChild1 extends MyParent1{
public static String str2 = "hello,str2"; //如果final修饰 只会打印 "hello,str2" 常量在编译阶段的时候 常量池
static {
System.out.println("MyChild1 static");
}
}
ClassLoader 分类
1、java虚拟机自带的加载器
- BootStrap 根加载器 (加载系统的包,JDK 核心库中的类 rt.jar)
- Ext 扩展类加载器 (加载一些扩展jar包中的类)
- Sys/App 系统(应用类)加载器 (我们自己编写的类)
2、用户自己定义的加载器
- ClassLoader,只需要继承这个抽象类即可,自定义自己的类加载器
双亲委派机制
双亲委派机制 可以保护java的核心类不会被自己定义的类所替代
一层一层的让父类去加载,如果顶层的加载器不能加载,然后再向下类推
// Demo01
// AppClassLoader 03
// ExtClassLoader 02
// BootStrap (最顶层) 01 java.lang.String rt.jar
1.2.2.运行时数据区
Stack 栈是什么
栈就是管理程序运行的
存储一些基本类型的值,对象的引用,方法等…
栈的优势:存取速度比堆快!仅次于寄存器,栈的数据是不可以共享的;
栈原理
java栈的组成元素–栈帧
栈(存什么)+ 堆 + 方法区的交互图:
我们的这个栈主要是 HotSpot (指针)
假设你的公司用的 JVM 不是HotSpot
问题:请你谈谈你认识几种 JVM? (3种)
- SUN 公司 HotSpot (掌握即可)
- BEA 公司 JRockit
- IBM 公司 J9VM
堆(heap)
Java7之前:
Heap 堆,一个JVM实例中只存在一个堆,堆的内存大小是可以调节的。
可以存的内容:类、方法、常量、保存了类型引用的真实信息;
分为三个部分:
- 新生区:Young (Eden-s0-s1)
- 养老区:Old Tenure
- 永久区:Perm
堆内存在逻辑上分为三个部分:新生、养老、永久(JDK1.8以后,叫元空间)
物理上只有 新生、养老;元空间在本地内存中,不在JVM中!
GC 垃圾回收主要是在 新生区和养老区,又分为 普通的GC 和 Full GC,如果堆满了,就会爆出 OutOfMemory;
新生区
新生区 就是一个类诞生、成长、消亡的地方!
新生区细分: Eden、s(from to),所有的类Eden被 new 出来的,慢慢的当 Eden 满了,程序还需要创建对象的时候,就会触发一次轻量级GC;清理完一次垃圾之后,会将活下来的对象,会放入幸存者区(),… 清理了 20次之后,出现了一些极其顽强的对象,有些对象突破了15次的垃圾回收!这时候就会将这个对象送入养老区!运行了几个月之后,养老区满了,就会触发一次 Full GC;假设项目1年后,整个空间彻彻底底的满了,突然有一天系统 OOM,排除OOM问题,或者重启;
Sun HotSpot 虚拟机中,内存管理(分代管理机制:不同的区域使用不同的算法!)
Eden from to
99% 的对象在 Eden 都是临时对象;
养老区
15次都幸存下来的对象进入养老区,养老区满了之后,触发 Full GC
默认是15次,可以修改!
永久区(Perm)
放一些 JDK 自身携带的 Class、Interface的元数据;
几乎不会被垃圾回收的;
OutOfMemoryError:PermGen 在项目启动的时候永久代不够用了?加载大量的第三方包!
JDK1.6之前: 有永久代、常量池在方法区;
JDK1.7:有永久代、但是开始尝试去永久代,常量池在堆中;
JDK1.8 之后:永久代没有了,取而代之的是元空间;常量池在元空间中;
闲聊:方法区和堆一样,是共享的区域,是JVM 规范中的一个逻辑的部分,但是记住它的别名 非堆
元空间:它是本地内存!
/**
* 默认情况:
* maxMemory : 1808.0MB (虚拟机试图使用的最大的内存量 一般是物理内存的 1/4)
* totalMemory : 123.0MB (虚拟机试图默认的内存总量 一般是物理内存的 1/64)
*/
// 我们可以自定堆内存的总量
// -XX:+PrintGCDetails; // 输出详细的垃圾回收信息
// -Xmx: 最大分配内存; 1/4
// -Xms: 初始分配的内存大小; 1/64
// -Xmx1024m -Xms1024m -XX:+PrintGCDetails
public class Demo01 {
public static void main(String[] args) {
// 获取堆内存的初始大小和最大大小
long maxMemory = Runtime.getRuntime().maxMemory();
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("maxMemory="+maxMemory+"(字节)、"+(maxMemory/1024/(double)1024)+"MB");
System.out.println("totalMemory="+totalMemory+"(字节)、"+(totalMemory/1024/(double)1024)+"MB");
}
}
oom
/*
* -Xmx8m -Xms8m -XX:+PrintGCDetails
*
* 分析GC日志:
*
* [Times: user=0.00 sys=0.00, real=0.00 secs]
* 1、GC 类型 GC:普通的GC,Full GC :重GC
* 2、1536K 执行 GC之前的大小
* 3、504K 执行 GC之后的大小
* 4、(2048K) young 的total大小
* 5、0.0012643 secs 清理的时间
* 6、user 总计GC所占用CPU的时间 sys OS调用等待的时间 real 应用暂停的时间
*
* GC :串行执行 STW(Stop The World) 并行执行 G1
*/
public class Demo02 {
public static void main(String[] args) {
System.gc(); // 手动唤醒GC(),等待cpu的调用
String str = "ilovecoding";
while (true){
str += str
+ new Random().nextInt(999999999)
+ new Random().nextInt(999999999);
}
// 出现问题:java.lang.OutOfMemoryError: Java heap space
}
}
java.lang.StackOverflowError
public static void main(String[] args) {
a();
}
public static void a(){
a();
}
java.lang.OutOfMemoryError: Java heap space
// -Xms10m -Xmx10m
public class OomDemo {
public static void main(String[] args) {
String str = "Coding";
while (true){
str += str + new Random(1111111111) + new Random(1111111111);
}
}
}
java.lang.OutOfMemoryError: GC overhead limit exceeded
GC 回收时间过长也会导致 OOM;
可能CPU占用率一直是100%,GC 但是没有什么效果!
// -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails
public class OomDemo {
public static void main(String[] args) throws Throwable {
int i = 0;
List<String> list = new ArrayList<String>();
try {
while (true){
list.add(String.valueOf(++i).intern());
}
} catch (Throwable e) {
System.out.println("i=>"+i);
e.printStackTrace();
throw e;
}
}
}
java.lang.OutOfMemoryError: Direct buffer memory 基础缓冲区的错误!
// -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails
public class OomDemo {
public static void main(String[] args) throws Throwable {
System.out.println("配置的MaxDirectMemorySize"
+VM.maxDirectMemory()/(double)1024/1024+"MB");
TimeUnit.SECONDS.sleep(2);
// 故意破坏!
// ByteBuffer.allocate(); 分配JVM的堆内存,属于GC管辖
// ByteBuffer.allocateDirect() ; // 分配本地OS内存,不属于GC管辖
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);
// java.lang.OutOfMemoryError: Direct buffer memory
}
}
java.lang.OutOfMemoryError: unable to create native Thread
高并发 , unable to create native Thread这个错误更多的时候和平台有关!
1、应用创建的线程太多!
2、服务器不允许你创建这么多线程!
public class TDemo {
public static void main(String[] args) {
for (int i = 1; ; i++) {
System.out.println("i=>"+i);
new Thread(()->{
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
},""+i).start();
}
}
}
Exception in thread “main” java.lang.OutOfMemoryError: unable to create new native thread
1、服务器线程不够了,超过了限制,也会爆出OOM异常!
java.lang.OutOfMemoryError: Metaspace 元空间报错
java8 之后使用元空间代替永久代;本地内存!
1、虚拟机加载类信息
2、常量池
3、静态变量
4、编译后的代码
…
模拟元空间溢出、不断的生成类即可!
// -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
public class OomDemo {
static class OOMTest{}
public static void main(String[] args) throws Throwable {
int i = 0; // 模拟计数器
try {
while (true){
i++;
// 不断的加载对象! Spring的 cglib;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMTest.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return method.invoke(o,args);
}
});
enhancer.create();
}
} catch (Throwable e) {
System.out.println("i=>"+i);
e.printStackTrace();
}
}
}
Dump内存快照
在java程序运行的时候,想测试运行的情况!
使用一些工具来查看;
1、Jconsole
2、idea debug
3、Eclipse(MAT插件)
4、IDEA(Jprofiler插件)
你常用的项目,发布后配置过JVM 调优参数吗?
java三种类型: 标配参数,x参数,xx参数
运行时
jps -l 看运行进程版本号
jinfo -flag 看某个运行程序某个参数是否开启
-Xms 初始堆大小
-Xmx 最大堆大小
-Xss : 线程栈大小设置,默认 512k~1024k
-Xmn 设置年轻代的大小,一般不用动!
-XX:MetaspsaceSize 设置元空间的大小,这个在本地内存中!
-XX:+PrintGCDetails
-XX:SurvivorRatio
设置新生代中的 s0/s1 空间的比例;
uintx SurvivorRatio = 8 Eden:s0:s1 = 8:1:1
uintx SurvivorRatio = 4 Eden:s0:s1 = 4:1:1
-XX:NewRatio
设置年轻代与老年代的占比:
NewRatio = 2 新生代1,老年代是2,默认新生代整个堆的 1/3;
NewRatio = 4 新生代1,老年代+是4,默认新生代整个堆的 1/5;
-XX:MaxTenuringThreshold
进入老年区的存活阈值;
MaxTenuringThreshold = 15
java -XX:+PrintFlagsInitial 可看默认参数
GC详解
主要调堆
口诀:关于垃圾回收:分代收集算法 不同的区域使用不同的算法
Young代: GC频繁区域
Old代:GC次数较少
Perm代:不会产生GC!
JVM在进行GC时,并非每次都是对三个区域进行扫描的!大部分的时候都是指的新生代!
两个类型:
普通GC:只针对新生代 【GC】
全局GC:主要是针对老年代,偶尔伴随新生代! 【Full GC】
GC四大算法
引用计数法 (了解即可!)
特点:每个对象都有一个引用计数器,每当对象被引用一次,计数器就+1,如果引用失效,则计数器-1,如果为0,则GC可以清理;
缺点:
- 计数器维护麻烦!
- 循环引用无法处理!
JVM 一般不采用这种方式
闲聊:现在一般使用可达性算法,GC Root…
复制算法
年轻代中,就是使用的复制算法!
1、一般普通GC 之后,差不多Eden几乎都是空的了!
2、每次存活的对象,都会被从 from 区和 Eden区等复制到 to区,from 和 to 会发生一次交换;记住一个点就好,谁空谁是to,每当幸存一次,就会导致这个对象的年龄+1;如果这个年龄值大于15(默认值,后面我们会讲解调整),就会进入养老区!
优点:没有标记和清除的过程!效率高!没有内存碎片!
缺点:需要浪费双倍的空间
Eden 区,对象存活率极低! 统计:99% 对象都会在使用一次之后,引用失效!推荐使用 复制算法
标记清除算法
老年代一般使用这个,但是会和我们后面的整理压缩一起使用!
优点:不需要额外的空间!
缺点:两次扫描,耗时较为严重,会产生内存碎片,不连续!
标记清除压缩!
减少了上面标记清除的缺点:没有内存碎片!但是耗时可能也较为严重!
那我们什么时候可以考虑使用这个算法呢?
在我们这个要使用算法的空间中,假设这个空间中很少,不经常发生GC,那么可以考虑使用这个算法!
小总结
内存效率:复制算法 > 标记清除算法 > 标记整理(时间复杂度!)
内存整齐度:复制算法=标记整理>标记清除算法
内存利用率:标记整理 = 标记清除算法 > 复制算法
从效率来说,复制算法最好,但是空间浪费较多!为了兼顾所有指标,标记整理会平滑一点,但是效率不尽人意!
年轻代:
相对于老年区,对象存活率低!
Eden 区,对象存活率极低! 统计:99% 对象都会在使用一次之后,引用失效!推荐使用 复制算法
老年代:
区域比较大,对象存活率较高!
推荐使用:标记清除压缩!
JVM 垃圾回收的时候如何确定垃圾,GC Roots?
什么是垃圾:简单地说,就是不在被引用的对象!
Person person = null;
如果我们要进行垃圾回收,第一步:判断这个对象是否可以回收!
引用计数法!(上面已经谈过)
Java中,引用和对象都是有关联的,如果要操作对象,就要通过引用进行;
可达性分析算法!
一切都是从 GC Root 这个对象开始遍历的,只要在这里面的就不是垃圾!
什么是Gc Root,(4种)
1、虚拟机栈中引用的对象!
2、类中静态属性引用的对象
3、方法区中的常量
4、本地方法栈中 Native 方法引用的对象!
public class GCRoots{
// private byte[] array = new byte[100*1024*1024]; // 开辟内空间!
// private static GCRoots2 t2; // GC root;
// private static final GCRoots3 t3 = new GCRoots3(); // GC root;
// 引用远远不止于此,强引用,软引用,弱引用,虚引用! 四个类的使用!
public static void m1(){
GCRoots g1 = new GCRoots(); //GCroot
System.gc();
}
public static void main(String[] args){
m1();
}
}
谈谈G1垃圾回收器
以前的垃圾回收器的特点
1、年轻代和老年代是各自独立的内存区域
2、年轻代使用 eden+s0+s1 复制算法
3、老年代手机必须扫描整个老年代的区域;
4、垃圾回收器原则:尽可能少而快的执行GC为设计原则!
G1垃圾回收器
G1(Garbage-First)收集器 ,面向服务器端的应用的收集器;
如何使用
-XX:+UseG1GC
这个还不是它最大的亮点,它增加了一些参数,可以自定义垃圾回收的时间!
-XX:MaxGCPauseMillis=100
最大的GC停顿时间单位:毫秒,JVM尽可能的保证停顿小于这个时间!
G1 优点
1、没有内存碎片!
2、可以精准空垃圾回收时间!
这个jvm蛮详细的https://blog.youkuaiyun.com/TZ845195485/article/details/93238857?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158401791019724846438483%2522%252C%2522scm%2522%253A%252220140713.130056874…%2522%257D&request_id=158401791019724846438483&biz_id=0&utm_source=distribute.pc_search_result.none-task