JVM面试题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34645958/article/details/82047846

1.通过new()创建对象的的过程?

(1)加载:通过类的全限定名获取类的二进制字节流,将字节流代表的静态存储结构转化为方法区的运行时数据结构。在内存生成这个类的class对象

(2)连接:包括三步验证,准备,解析

                                   验证:保证class文件的字节流中包含的信息符合当前虚拟机的要求,且不危害虚拟机自身安全

                                   准备:正式为类变量分配内存并设置类变量初始值

                                   解析:将常量池的符号引用替换为直接引用

(3)初始化:根据程序员的意愿初始化类变量

2.一个对象的内存划分是怎样的?

可分为3块区域:对象头,实例数据,对齐填充

(1)对象头由Mark world和类型指针组成

         mark World包括hash码,GC分代年龄,锁状态标志等。

         类型指针来确定这个对象是哪个类的实例

(2)实例数据是对象存储的真正的有效信息

(3)hostspot虚拟机要求对象的大小必须是8字节的整数倍

3.java程序执行的过程是怎样的?

首先java源代码文件会被编译器编译为字节码文件,然后由JVM的类加载器加载各个类的字节码文件,然后交由jvm执行引擎执行,在整个执行过程中会将需要用到的数据和相关信息存储在运行时数据区(也就是我们说的jvm内存)

   

4.谈谈jvm内存的划分?

jvm内存可划分为线程共享区和线程独占区

线程独占区有:java栈,本地方法栈,程序计数器

线程共享区有堆和方法区

(1)java栈:     存放一个个栈帧,每个栈帧对应被调用的一个方法,栈帧中包括局部变量表,操作数栈,指向运行时常量池的引用,方法返回地址等

(2)本地方法栈:与java栈的作用和原理相似,只不过java栈为java方法服务,本地方法栈则是执行本地方法

(3)程序计数器:保存程序当前执行的指令地址

(4)堆:用来存储对象和数组

(5)方法区:存储类信息,静态变量,常量以及编译后的代码 

5.运行时常量池和字符串常量池的区别?(intern)

new string()的时候会调用intern方法,如果字符串常量池中不存在,就会创建一个,有则返回已存在的

Paste_Image.pngPaste_Image.png

                                 JDK1.6                                                                         JDK1.7

     JDK1.6:字符串常量池和运行时常量池都在方法区

     JDK1.7:字符串常量池被移动到堆中

public class Test {
    public static void main(String[] args) {
        String s1 = new StringBuilder().append("aa").append("bb").toString();
        System.out.println(s1.intern() == s1);
        String s2 = new StringBuilder().append("ja").append("va").toString();
        System.out.println(s2.intern() == s2); 
    }
}

   JDK1.6:false   false                         JDK1.7:true  false(java会在类加载的时候被放到字符串常量池,代码中有java..)

6.内存泄漏和内存溢出的区别?

内存泄漏:分配出去的内存无法回收(可达却无用的对象无法被gc回收)

内存溢出:程序要求的内存超出了系统能分配的范围(如栈满进栈,栈空出栈)

7.判断对象存活的算法?

(1)引用计数法:给对象添加一个引用计数器,每当有一个地方引用它,引用计数器值加1,否则减1,为0时不能再被使用

(2)可达性分析法:到GC Root可达,则为不可被回收的对象

                                  (GC Root:虚拟机栈,本地方法栈,方法区静态属性,方法区常量所引用的对象)

8.垃圾收集算法有哪些?

(1)标记-清除算法:首先标记出所有的对象,标记完成后统一回收

                                     (1效率问题:标记和清除两个过程效率都不高(2空间问题:产生碎片

(2)复制算法:将内存划分为一块较大的Eden空间(80%)和两块较小的Survivor空间(10%),每次使用Eden和其中的一块Survivor,当回收时,将两者中存活的对象一次性复制到另一块Survivor,并清空刚才用到的空间,如果这块Survivor不够,则启用分配担保机制,将多处的对象存储再老年代

(3)标记-整理算法:首先标记出所有的对象,回收时让存活的对象都向一端移动,直接清理端边界外的内存

(4)分代收集算法:将java堆分为新生代和老年代,垃圾回收时,新生代每次都有大量对象死去,所以采用复制算法,老年代存活的对象较多,使用标记-清除或标记-整理

9.常见的垃圾收集器?

(1)Serial收集器:单线程收集器

(2)ParNew收集器:,新生代收集器,Serial收集器的多线程版本

(3)Parallel Scavenge收集器:新生代收集器,使用复制算法。是用来实现最大吞吐量(代码运行时间/(代码运行时间+垃圾收集的时间))

(4)Serial Old收集器:Serial收集器的老年代版本

(5)Parallel Old收集器:ParNew收集器的老年代版本(一般使用Parallel Scavenge + Parallel Old)

(6)CMS收集器:是一种以获取最短停顿时间为目标的收集器。分为4个步骤:初始标记,并发标记,重新标记,并发清除

                                采用 标记-清除算法

                                初始标记:标记GC Root能直接关联到的对象 (stop the world)

                                并发标记:进行Gc Root Tracing的过程

                                重新标记:修正并发标记期间用户线程序继续工作而导致的标记的变动(stop the world)

                                并发清除:清除未被标记的对象

(7)G1收集器:可以独立的管理整个gc堆。步骤:初始标记,并发标记,最终标记,筛选回收(首先对各个Region价值排序)

                            从整体上看是:标记-整理算法    ,  从局部上看(两个region)采用复制算法

10.什么情况下对象进入老年代?

(1)大对象直接进入老年代(需要大量连续空间的对象)。常见的大对象就是很长的字符串和数组

(2)长期存活的对象进入老年代。每个对象有一个年龄计数器。每熬过一次moinor gc,年龄就增加一岁。当年龄增加到一定程度(默认为15)就会晋升到老年代(通过MaxTenuringThreshold设置)。

(3)动态年龄判断:如果survivor空间某个年龄对象的大小大于survivor空间的一半,年龄大于或等于的直接进入老年代

(4)空间分配担保:复制算法中,survivor中无法容纳的对象将通过分配担保机制直接进入老年代                              

猜你喜欢

转载自blog.csdn.net/qq_34645958/article/details/82047846