1、什么情况下会发生栈内存溢出(说白了就是内存不够了)?
内存泄漏: 一个不再被程序使用的对象或变量还在内存中占有存储空间。 一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
栈是线程私有的,栈的生命周期和线程一样,每个方法在执行的时候就会创建一个栈帧,它包含局部变量表、操作数栈、动态链接、方法出口等信息,局部变量表又包括基本数据类型和对象的引用;
当线程请求的栈深度超过了虚拟机允许的最大深度时,会抛出StackOverFlowError异常,比如方法递归调用可能会出现该问题;
调整参数-xss去调整jvm栈的大小
2、JVM内存划分是怎样的?
jvm将虚拟机分为5大区域,程序计数器、虚拟机栈、本地方法栈、java堆、方法区;
程序计数器:线程私有的,是一块很小的内存空间,作为当前线程的型号指示器,用于记录当前虚拟机正在执行的线程指令地址;
虚拟机栈:线程私有的,每个方法执行的时候都会创建一个栈帧,用于存储局部变量表、操作数、动态链接和方法返回等信息,当线程请求的栈深度超过了虚拟机允许的最大深度时,就会抛出StackOverFlowError;
本地方法栈:线程私有的,保存的是native方法的信息,当一个jvm创建的线程调用native方法后,jvm不会在虚拟机栈中为该线程创建栈帧,而是简单的动态链接并直接调用该方法;
堆:java堆是所有线程共享的一块内存,几乎所有对象的实例和数组都要在堆上分配内存,因此该区域经常发生垃圾回收的操作;
方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据。即永久代,在jdk1.8中不存在方法区了,被元数据区替代了,原方法区被分成两部分;
1:加载的类信息;
2:运行时常量池;加载的类信息被保存在元数据区中,运行时常量池保存在堆中。
3、JVM中一次完整的GC是什么样子的?对象如何晋升到老年代?
java堆 = 新生代+老年代;
新生代 = Eden + Suivivor(S0 + S1),默认分配比例是8:1:1;
当Eden区空间满了的时候,就会触发一次Minor GC,以收集新生代的垃圾,存活下来的对象会被分配到Survivor区,大对象(需要大量连续内存空间的对象)会直接被分配到老年代;
如果对象在Eden中出生,并且在经历过一次Minor GC之后仍然存活,被分配到存活区的话,年龄+1,此后每经历过一次Minor GC并且存活下来,年龄就+1,当年龄达到15的时候,会被晋升到老年代;
当老年代满了,而无法容纳更多对象的话,会触发一次full gc;full gc存储的是整个内存堆(包括年轻代和老年代);
Major GC是发生在老年代的GC,清理老年区,经常会伴随至少一次minor gc;
4、Java中的垃圾回收知道吗?双亲委派模型是怎么回事?
Java中有四种垃圾回收算法,分别是标记清除法、标记整理法、复制算法、分代收集算法;
1.标记清除法
第一步:利用可达性去遍历内存,把存活对象和垃圾对象进行标记;
第二步:在遍历一遍,将所有标记的对象回收掉;
特点:效率不行,标记和清除的效率都不高;标记和清除后会产生大量的不连续的空间分片,可能会导致之后程序运行的时候需分配大对象而找不到连续分片而不得不触发一次GC;
2.标记整理法
第一步:利用可达性去遍历内存,把存活对象和垃圾对象进行标记;
第二步:将所有的存活的对象向一段移动,边界以外的对象都回收掉;
特点:适用于存活对象多,垃圾少的情况;需要整理的过程,无空间碎片产生;
3.复制算法
将内存按照容量大小分为大小相等的两块,每次只使用一块,当一块使用完了,就将还存活的对象移到另一块上,然后在把使用过的内存空间移除;
特点:不会产生空间碎片;内存使用率极低;
4.分代收集算法
根据内存对象的存活周期不同,将内存划分成几块,java虚拟机一般将内存分成新生代和老生代,在新生代中,有大量对象死去和少量对象存活,所以采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;
老年代中因为对象的存活率极高,没有额外的空间对他进行分配担保,所以采用标记清理或者标记整理算法进行回收。
使用标记清除算法的垃圾回收器 比如 CMS
使用标记整理算法的比如 老年代的 serial old