JVM是Java运行基础
1. 知识点详解
1、JVM内存模型
- 线程独占:栈,本地方法栈,程序计数器
- 线程共享:堆,方法区
2、栈
又称方法栈,线程私有的,线程执行方法时都会创建一个栈帧,用来存储局部变量表,操作栈,动态链接,方法出口等信息。调用方法时执行入栈,方法返回时执行出栈。
3、本地方法栈
与栈类似,也是用来保存执行方法的信息,执行Java方法是使用栈,执行Native方法时使用本地方法栈
4、程序计数器
保存着当前线程执行的字节码位置,每个线程工作时都有独立的计数器,只为执行Java方法服务,执行Native方法时,程序计数器为空
5、堆
JVM内存管理最大的一块,堆被线程共享,目的是存放对象的实例,几乎所有的对象实例都会放在这里,当堆没有可用空间时,会抛出OOM异常。根据对象的存活周期不同,JVM把对象进行分代管理,有垃圾回收器进行垃圾的回收管理
6、方法区
- 又称非堆区,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器优化后的代码等数据。1.7的永久代和1.8的元空间都是方法区的一种实现
- JMM是定义程序中变量的访问规则,线程对于变量的操作只能在自己的工作内存中进行,而不能直接对主内存操作。由于指令重排序,读写的顺序会被打乱,因此JMM需要提供原子性,可见性,有序性保证
2. 说说类加载与卸载
加载过程
其中验证,准备,解析合称链接
- 加载机制-双亲委派模式
双亲委派模式,即加载器加载类时先把请求委托给自己的父类加载器执行,直到顶层的启动类加载器。
父类加载器能够完成加载则成功返回,不能则子类加载器才自己尝试加载优点: - 避免类的重复加载 - 避免Java的核心API被篡改
- 分代回收
分代回收基于两个事实:大部分对象很快就不使用了,还有一部分不会立即无用,但也不会持续很长时间
年轻代->标记-复制 老年代->标记-清除 - 回收算法
a. G1算法
- 1.9后默认的垃圾回收算法,特点保持高回收率的同时减少停顿。采用每次只清理一部分,而不是清理全部的增量式清理,以保证停顿时间不会过长
- 其取消了年轻代与老年代的物理划分,但仍属于分代收集器,算法将堆分为若干个逻辑区域,一部分用作年轻代,一部分用作老年代,还有用来存储巨型对象的分区
- 同CMS相同,会遍历所有对象,标记引用情况,清除对象后会对区域进行复制移动,以整合碎片空间
- 年轻代回收:并行复制采用复制算法,并行收集,会StopTheWorld
- 老年代回收:会对年轻代一并回收
- 初始标记完成堆root对象的标记
3. 简述一下JVM的内存模型
JVM内存分为线程私有区和线程共享区
线程私有区
- 程序计数器
当同时进行的线程数超过CPU数或其内核数时,就要通过时间片轮询分派CPU的时间资源,不免发生线程切换。这时,每个线程就需要一个属于自己的计数器来记录下一条要运行的指令。如果执行的是Java方法,计数器记录正在执行的Java字节码地址,如果执行的是native方法,则计数器为空 - 虚拟机栈
线程是私有的,与线程在同一时间创建。管理Java方法执行的内存模型。每个方法执行时都会创建一个栈帧拉存储方法的变量表,操作数栈,动态链接方法,返回值,返回地址等信息。栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法,-Xss参数可以设置虚拟机栈大小)。栈的大小是可以固定的,或者是动态扩展的。如果请求的栈深度大于最大可用深度,则抛出stackOverflowError;如果栈式可动态扩展的,但没有内存空间提供支持,则抛出OOM异常 - 本地方法栈
与虚拟机栈作用类似。但它不是为Java方法服务的,而是本地方法(C语言)。由于规范对这块没有强制要求,不通虚拟机实现方法不同
线程共享区
- 方法区
线程共享的,用于存放被虚拟机加载的类的元数据信息,如常量,静态变量和即时编译器编译后的代码。若要分代,算是永久代(老年代),以前类大多static的,很少被卸载或收集,先回收废弃常量和无用的类。其中运行时常量池存放编译生成的各种常量。 - 堆
存放对象实例和数组,是垃圾回收的主要区域,分为新生代和老年代。刚创建的对象在新生代的Eden区中,经过GC后进入新生代的S0区中,再经过GC进入新生代的S1区中,15次GC后仍存在就进入老年代。这是按照一种回收机制进行划分的,不是固定的。若堆的空间不够实例分配,则OOM
4. 说说堆和栈的区别
栈是运行时单位,代表着逻辑,内含基本数据类型和堆中对象引用,所在区域连续,没有碎片;堆是存储单位,代表着数据,可被多个栈共享(包括成员中基本数据类型,引用和引用对象),所在区域不连续,会有碎片
1、功能不同
栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象。无论是成员变量局部变量,还是类变量,他们指向的对象都存储在堆内存中
2、共享性不同
栈内存是线程私有的。堆内存是所有线程共享的
3、异常错误不同
如果栈内存或者堆内存不足都会抛出异常。栈空间不足:java.lang.StackOverFlowError.
堆空间不足:java.lang.OutOfMemoryError
4、空间大小
栈的空间大小远远小于堆的
5. 什么时候会触发FullGC
除直接调用System.gc外,触发FullGC执行的情况有如下四种。
旧生代空间不足
旧生代空间只有在新生代对象转入及创建为大对象,大数组时才会出现不足的现象,当执行FullGC后空间仍然不足,则抛出如下错误:java.lang.OutOfMemoryError:Java heap space为避免以上两种状况引起的FullGC,调优
时应尽量做到让对象在Minor GC阶段被回收,让对象在新生代多存活一段时间及不要创建过大的对象及数组Permanet Generation空间满
6. 说说对象分配规则
- 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC
- 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法手机内存)
- 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,直到达到阈值对象进入老年区
- 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象的大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
- 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于,检查HandlePromotionFailure设置,如果true则只进行Monior GC,如果false则进行Full GC
7. 如何判断对象可以被回收
判断对象是否存活一般有两种方式:
- 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题
- 可达性分析:从GC Roots开始向下搜索,搜索走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象
8、JVM的永久代中会发生垃圾回收么?
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。
9.你知道那些垃圾回收算法
GC最基础的算法有三种:标记-清除算法,复制算法,标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法
- 标记-清除算法:分为“标记”和“清除”两个阶段:首先标记处所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象
- 复制算法:
- 标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
- 分代收集算法(Generation Collection),把Java堆分为新生代和老年代,这样就可以按照各个年代的特点采用最适当的收集算法
10. 调优命令有哪些
Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo
- jps
11. 常见调优工具有哪些
常用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool),GChisto
12.Minor GC 与Full GC分别发生在什么时候
新生代内存不够用时发生MGC ,JVM内存不够时发生FGC
13. 你知道哪些JVM性能调优参数?
- 设定堆内存大小:-Xmx:堆内存最大限制
- 设定新生代大小。新生代不宜太小,否则会有大量对象涌入老年代
- -XX:NewSize:新生代大小
- -XX:NewRatio 新生代和老生代占比
- -XX:SurvivorRatio:伊甸园空间和幸存者空间的占比
- 设定垃圾回收器:年轻代用-XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC