JVM经典面试题总结
文章目录
一、内存结构
1.简述一下JVM的内存结构?
JVM在执行Java程序时,会把它管理的内存划分为若干个的区域,每个区域都有自己的用途和创建销毁时间。如下图所示,可以分为两大部分,线程私有区和共享区。
线程私有区
程序计数器:
- 作用:是一块较小的内存空间,可以理解为是当前线程所执行程序的字节码文件的行号指示器,存储的是当前线程所执行的行号
- 特点:线程私有 ,唯一 一个不会出现内存溢出的内存空间
虚拟机栈
-
作用:管理JAVA方法执行的内存模型。每个方法执行时都会创建一个栈桢来存储方法中变量的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法
-
特点:
1.线程私有
2.局部变量表存放了编译期可知的各种基本数据类型以及对象引用
3.栈太小或者方法调用过深,都将抛出StackOverflowError异
配置虚拟机参数-Xss可以指定栈内存大小;例如:-Xss180k
栈内存的默认值问题:
The default value depends on the platform:
* Linux/x64 (64-bit): 1024 KB
* macOS (64-bit): 1024 KB
* Oracle Solaris/x64 (64-bit): 1024 KB
* Windows: The default value depends on virtual memory
本地方法栈:
- 与虚拟机栈作用相似。但它不是为Java方法服务的,而是本地方法(C语言)。由于规范对这块没有强制要求,不同虚拟机实现方法不同。
线程共享区
堆内存
- 作用:是Java内存区域中一块用来存放对象实例的区域,新创建的对象,数组都使用堆内存;【从Java7开始,常量池也会使用堆内存】
Java 堆从GC的角度还可以细分为: 新生代( Eden区 、From Survivor区和 To Survivor区 )和老年代。
- 特点:
- 被线程共享,因此需要考虑线程安全问题
- 会产生内存溢出问题
- 虚拟机参数:
- -Xms 设置最小堆内存大小(不能小于1024K); -Xms 堆内存初始大小,可以通过jmap工具进行查看
- -Xmx 设置最大堆内存大小(不能小于1024K); -Xmx 堆内存最大值,可以通过jmap工具进行查看
方法区
- 作用:它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 特点:
- 方法区是一块线程共享的内存区域
- 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出的错误
- jdk1.6和jdk1.7方法区也常常被称之为永久区(永久代),大小一般都是几百兆;
- jdk1.8已经将方法区取消,替代的是元数据区(元空间),如果不指定大小,默认情况下,虚拟机会耗尽可用系统内存
- jdk7以后就将方法区中的常量池移动至堆内存
- 变化的原因:
- 提高内存的回收效率(方法区内存的回收效率远远低于堆内存,因为方法去中存储的都是类信息,静态变量…这些信息不能被轻易回收
- 字符串常量池在方法区,那么很容易产生内存溢出(因为方法区的垃圾回收效率比较低);
常用参数
(1)-Xms20M
表示设置JVM启动内存的最小值为20M,必须以M为单位
(2)-Xmx20M
表示设置JVM启动内存的最大值为20M,必须以M为单位。将-Xmx和-Xms设置为一样可以避免JVM内存自动扩展。大
的项目-Xmx和-Xms一般都要设置到10G、20G甚至还要高
(3)-verbose:gc
表示输出虚拟机中GC的详细情况
(4)-Xss128k
表示可以设置虚拟机栈的大小为128k
(5)-Xoss128k
表示设置本地方法栈的大小为128k。不过HotSpot并不区分虚拟机栈和本地方法栈,因此对于HotSpot来说这个参数
是无效的
(6)-XX:PermSize=10M
表示JVM初始分配的永久代(方法区)的容量,必须以M为单位
(7)-XX:MaxPermSize=10M
表示JVM允许分配的永久代(方法区)的最大容量,必须以M为单位,大部分情况下这个参数默认为64M
(8)-Xnoclassgc
表示关闭JVM对类的垃圾回收
(9)-XX:+TraceClassLoading
表示查看类的加载信息
(10)-XX:+TraceClassUnLoading
表示查看类的卸载信息
(11)-XX:NewRatio=4
表示设置 年轻代(包括Eden和两个Survivor区)/老年代 的大小比值为1:4,这意味着年轻代占整个堆的1/5
(12)-XX:SurvivorRatio=8表示设置2个Survivor区:1个Eden区的大小比值为2:8,这意味着Survivor区占整个年轻代的1/5,这个参数默认为8
(13)-Xmn20M
表示设置年轻代的大小为20M
(14)-XX:+HeapDumpOnOutOfMemoryError
表示可以让虚拟机在出现内存溢出异常时Dump出当前的堆内存转储快照
(15)-XX:+UseG1GC
表示让JVM使用G1垃圾收集器
(16)-XX:+PrintGCDetails
表示在控制台上打印出GC具体细节
(17)-XX:+PrintGC
表示在控制台上打印出GC信息
(18)-XX:PretenureSizeThreshold=3145728
表示对象大于3145728(3M)时直接进入老年代分配,这里只能以字节作为单位
(19)-XX:MaxTenuringThreshold=1
表示对象年龄大于1,自动进入老年代,如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于
年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,
这样可以增加对象在年轻代的存活时间,增加在年轻代被回收的概率。
(20)-XX:CompileThreshold=1000
表示一个方法被调用1000次之后,会被认为是热点代码,并触发即时编译
(21)-XX:+PrintHeapAtGC
表示可以看到每次GC前后堆内存布局
(22)-XX:+PrintTLAB
表示可以看到TLAB的使用情况
(23)-XX:+UseSpining
开启自旋锁
(24)-XX:PreBlockSpin
更改自旋锁的自旋次数,使用这个参数必须先开启自旋锁
(25)-XX:+UseSerialGC
表示使用jvm的串行垃圾回收机制,该机制适用于丹cpu的环境下
(26)-XX:+UseParallelGC
表示使用jvm的并行垃圾回收机制,该机制适合用于多cpu机制,同时对响应时间无强硬要求的环境下,使用-
XX:ParallelGCThreads=设置并行垃圾回收的线程数,此值可以设置与机器处理器数量相等。
(27)-XX:+UseParallelOldGC表示年老代使用并行的垃圾回收机制
(28)-XX:+UseConcMarkSweepGC
表示使用并发模式的垃圾回收机制,该模式适用于对响应时间要求高,具有多cpu的环境下
(29)-XX:MaxGCPauseMillis=100
设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
USER
(30)-XX:+UseAdaptiveSizePolicy
(31) -XX:MaxMetaspaceSzie 设定大小 jdk1.8的元数据区可以使用参数
2.堆和栈的区别?
- 功能不同:栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。
- 共享性不同:栈内存是线程私有的。堆内存是所有线程共有的。
- 异常错误不同:如果栈内存或者堆内存不足都会抛出异常。
- 栈空间不足:java.lang.StackOverFlowError
堆空间不足:java.lang.OutOfMemoryError - 空间大小:栈的空间大小远远小于堆的。
3.怎么获取Java程序使用的内存?堆使用的百分比?
可以通过java.lang.Runtime类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。
1、Runtime.freeMemory() 方法返回剩余空间的字节数
2、Runtime.totalMemory()方法总内存的字节数
4.栈帧都有哪些数据?
栈帧包含:局部变量表、操作数栈、动态连接、返回值、返回地址等。
5.如何启动系统的时候设置jvm的启动参数?
二、垃圾回收
1.如何判断一个对象是否为垃圾?
引用计数法
- 堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。
- 特点:简单、无法解决循环引用问题
可达性分析算法
- 可达性分析算法又叫做跟搜索法,就是通过一系列的称之为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。
2.可达性算法中,哪些对象可作为GC Roots对象?
1、虚拟机栈中引用的对象
2、方法区静态成员引用的对象
3、方法区常量引用对象
4、本地方法栈引用的对象
3.Java中都有哪些引用类型?
强引用
Java中默认声明的就是强引用,比如:
//只要obj还指向Object对象,Object对象就不会被回收
Object obj = new Object();
obj = null; //手动置null
只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引
用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了
软引用
软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回
收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。
弱引用
弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2之后,用
java.lang.ref.WeakReference来表示弱引用。
虚引用
虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是
说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。
特点:
- 每次垃圾回收时都会被回收,主要用于监测对象是否已经从内存中删除
- 虚引用必须和引用队列关联使用, 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中
- 程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动
// 主动通知垃圾回收器进行垃圾回收
System.gc();
4.常见的垃圾回收算法都有哪些?
4.1标记清除
执行过程:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
优点:速度比较快
缺点:会产生内存碎片,碎片过多,仍会使得连续空间少
4.2标记整理
执行过程:首先标记出所有需要回收的对象,在标记完成后统一进行整理,整理是指存活对象向一端移动来减少内存碎片,相对效率较低
优点:无内存碎片
缺点:效率较低
4.3复制算法
执行过程:开辟两份大小相等空间,一份空间始终空着,垃圾回收时,将存活对象拷贝进入空闲空间;
优点:无内存碎片
缺点:占用空间多
注意:如果有很多对象的存活率较高,这时我们采用复制算法,那么效率就比较低;
4.4分代回收
概述:根据对象存活周期的不同,将对象划分为几块,比如Java的堆内存,分为新生代和老年代,然后根据各个年代的特点采用最合适的算法
;
新生代对象的存活的时间都比较短,因此使用的是【复制算法】;而老年代对象存活的时间比较长那么采用的就是【标记清除】或者【标记整理】;
5.简述Java垃圾回收机制?有什么办法主动通知虚拟机进行垃圾回收?
在Java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。