目录
一、说一下jvm的运行时数据区
程序计数器:较小的内存空间,当前线程所执行的字节码行号指示器,唯一一个无OOM区域;
虚拟机栈:虚拟机栈和线程生命周期相同,一个线程每调用一个方法就创建一个栈针。
本地方法栈:虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
堆:Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据;
二、堆和栈的区别
(1)堆的物理地址分配对象是不连续的;栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快;
(2)功能不同:栈是用来储存局部变量和方法调用,堆是用来存储java对象;
(3)堆内存是共享,栈是线程私有的;
(4)栈内存空间远小于堆内存;
(5)异常错误:栈内存或者堆内存不足都会抛出异常。堆:OOM,栈:SOF
三、怎样判断对象是否可以被回收
(1)引用计数法: 为每个对象创建一个引用计数,有对象引用时计数器+1,对象被释放时-1;计数器为0时可以被回收;解决不了循环引用的问题;
(2)可达性计算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。
作为GcRoots对象:虚拟机栈中引用的对象;方法区类静态属性的对象;方法区常量池引用的对象;本地方法栈的JNI引用的对象
但当对象不可达GCroots,这个对象也不会被立马回收,如果该对象没有重写finalize()方法或finalize()已经被执行过则直接回收,否则将对象加入F-queue队列中,在这里finalize()方法被执行,之后进行第二次标记,如果对象仍然被GC则GC,否则移除队列。
四、内存泄露与内存溢出的区别
内存泄露:(强引用所指向的对象不会被回收,可能导致内存泄漏,虚拟机宁愿抛出OOM也不会去回收他指向的对象)(1)对象是可达的(一直被引用)(2)对象不会被使用,不会被GC回收,却一直占用内存;
内存溢出:(系统已经不能再分配出你所需要的空间)(1)大量jar、class文件加载,装载类的空间不够;(2)操作大量对象导致堆内存空间用满
五、java内存是怎么分配
(1)对象优先分配在Eden区;(2)大对象直接进入老年代;(3)长期存活的对象进入老年代
(4)动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
(5)空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。
六、垃圾回收的主要方法
(1)标记清除:会产生大量不连续的内存碎片,导致以后程序分配较大对象时,没有充足的连续内存而提前一次触发GC操作;
(2)复制算法:内存分成A、B两块,A负责分配对象,B不分配,A中存活的对象复制到区域B,在清理掉A中对象。每次回收都要浪费一半的内存;
(3)标记整理:将可回收的对象移动到另一端,紧密排列,在清理掉另一端的所有区域。仍需要进行局部对象移动,一定程度上降低了效率
(4)分代收集:
经过一次minor Gc 后,将Eden和survivor中还存活的对象一次性的复制到另一块survivor空间上了,最后清理掉Eden和刚才用过的survivor空间,将此时在survivor活下来的对象的年龄设置为1,以后每个survivor区熬过一次GC,年龄就加1,当对象的年龄达到15时,就把他们移动到老年代。
七、java中有哪些引用
强引用:发生GC的时候不会被回收;
弱引用:有用但不是必须的对象,发生内存溢出之前会被回收;
软引用:有用但不是必须的对象,在下一次GC会被回收;
虚引用:无法通过虚引用获得对象。
八、解释一下Object obj=new Object()
这里涉及java堆、方法区、java栈内存区域之间的关联
Object obj
将反映到Java栈的本地变量表,obj是引用类型;
new Object()反映在java堆中,存储了Object类型的所有数据;
程序运行,类型信息就会加载在内存里,这些数据是在java方法区中。
九、什么是类的加载
类的加载就是将类的.class文件的二进制数据读入到内存中,将其放在运行时的方法区中,然后在堆里创建Class对象,用来封装在方法区内的数据结构。
双亲委派机制:类加载器收到类加载请求,自己不加载,向上委托给父类加载,父类加载不了,再自己加载。
十、请解释下类的生命周期
加载:查找并加载类的二进制文件,在堆中创建Class对象;
连接:验证、准备和解析;验证:文件格式、字节码等验证;准备:为类的静态变量分配内存,并将其初始化为 默认值;解析:把类中的符号引用转换为直接引用;
初始化:对静态变量和静态代码块执行初始化工作。
十一、请介绍一下GC垃圾收集器
Serial New 串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收
Parallel New(并行)收集器,ParNew收集器其实就是Serial收集器的多线程版本
Parallel Scavenge(并行)收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量
Serial Old(串行)收集器,新生代采用复制,老年代采用标记清理
Parallel Old(并行)收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器
十二、StackOverFlow异常有没有遇到过
栈内存溢出,一般由于栈内存的局部变量过爆了,导致内存溢出,出现递归方法,递归过深,递归没有出口。
十三、OOM异常有没有遇到过
permgen space、heap space 错误
常见原因:(1)内存加载的数据量太大:一次性从数据库中读取太多数据;(2)启动参数堆内存值太小
(3)集合类中有对对象的引用,使用后未清空,GC不能进行回收
十四、什么时候触发Full GC
(1)老年代空间不足:如果创建一个大对象,Eden区域放不下这个大对象,会直接保存在老年代中;如果老年代空间也不足,则会触发Full GC;
(2)调用system.GC();
(3)持久代空间不足:当系统中加载类、反射的类和调用的方法较多时,永久代可能会占满;