JVM模块
1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?
a) Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。 Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性
2.Java内存结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T4kEnorV-1575020072188)(file:///C:/TEMP/msohtmlclip1/01/clip_image002.png)]
a) 方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈和程序计数器是运行时线程私有的内存区域
i. Java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
ii. 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
iii. 程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器
iv. Java栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。Java栈描述的是Java****方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程
v. 本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是Java栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务
3.解释内存中的栈(stack)、堆(heap)和方法区(method area)的用法
a) 通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM****中的栈空间;
b) 而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为From Survivor和To Survivor)、Tenured;
c) 方法区是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据(常量池是方法区的一部分,使用String类的intern()方法可以在程序运行时将字符串常量放到字符串常量池中)
d) 栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发****OutOfMemoryError
4.名词解释
a) GC:垃圾收集器
b) Minor GC:新生代GC,指发生在新生代的垃圾收集动作,所有的Minor GC都会触发全世界的暂停(stop-the-world),停止应用程序的线程,不过这个过程非常短暂。
c) Major GC/Full GC:老年代GC,指发生在老年代的GC
d) JVM:Java Virtual Machine(Java虚拟机)的缩写
5.JVM内存分配规则
a) 众所周知,所有通过new创建的对象的内存都在堆中分配,堆被划分为新生代和老年代,新生代又被进一步划分为Eden和Survivor区,而Survivor由FromSpace和ToSpace组成。
b) 新生代:新创建的对象都是用新生代分配内存,Eden空间不足时,触发Minor GC,这时会把存活的对象转移进Survivor区
c) 虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,直到达到阀值对象进入老年代
d) 老年代:老年代用于存放大对象(大对象是指需要大量连续内存空间的对象)以及经过多次Minor GC之后依然存活的对象
e) 每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于则检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC
6.什么是类的加载
a) 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且外提供了访问方法区内数据结构的接口
7.类加载器
a) JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入class文件中的类
b) 类加载器采用委托机制:当类加载器需要加载类的时候,先请示其Parent(即上一层加载器)在其搜索路径载入,如果找不到,才在自己的搜索路径搜索该类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K3wOmY26-1575020072192)(file:///C:/TEMP/msohtmlclip1/01/clip_image003.png)]
c) 启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库
d) 扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器
e) 应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器
8.描述一下JVM加载class文件的原理机制
a) 类的加载主要有三个步骤:读入class文件、连接(验证、准备、解析)、初始化(初始化静态变量,静态代码块)
b) 类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证(文件格式、元数据、字节码、符号引用验证)、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:
i. 如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
ii. 如果类中存在初始化语句,就依次执行这些初始化语句(初始化静态变量,静态代码块)
9.java对象创建过程
a) 类加载检查–>分配内存–>初始化零值–>设置对象头–>执行init方法
i. 类加载检查:虚拟机遇到一条new指令时,先检查这个指令的参数能否在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,则先进行类的加载过程
ii. 分配内存:有两种方式
-
指针碰撞:假设Java堆中的内存是规整的,用过的内存在一边,空闲的在另一边,中间有一个指针作为分界点的指示器,所分配的内存就把那个指针向空闲那边挪动一段与对象大小相等的距离
-
空闲列表:如果Java堆中的内存不是规整的,虚拟机必须维护一个列表,记录哪些内存块可用的,分配时从列表中找到一块足够大的空间划分给对象,并更新列表的记录
-
在划分可用空间时,会遇到线程安全的问题。常用解决方案:把内存分配的动作安装现场划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB)。那个线程需要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。是否使用TLAB,-XX:+UseTLAB参数来设定
iii. 初始化零值:将分配到的内存空间都初始化为零值,如果用TLAB,则在TLAB分配时初始化为零值
iv. 设置对象头:主要设置类的元数据信息、对象的哈希码、对象的GC分代年龄等信息
v. 执行init方法初始化
10.类的生命周期
a) 类的生命周期包括这几个部分,加载、连接、初始化、使用和卸载,其中前三部是类的加载的过程,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e5CkoNc2-1575020072197)(file:///C:/TEMP/msohtmlclip1/01/clip_image005.jpg)]
i. 加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
ii. 连接,连接又包含三块内容:验证、准备、初始化。
-
验证,文件格式、元数据、字节码、符号引用验证;
-
准备,为类的静态变量分配内存,并将其初始化为默认值;
-
解析,把类中的符号引用转换为直接引用
iii. 初始化,为类的静态变量赋予正确的初始值
iv. 使用,new出对象程序中使用
v. 卸载,执行垃圾回收
11.Java对象结构
a) Java对象由三个部分组成:对象头、实例数据、对齐填充
i. 对象头由两部分组成:
-
第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)
-
第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度
ii. 实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)
iii. 对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)
12.Java对象的定位方式
a) 句柄访问:Java堆中划分出一块内存来当做句柄池。Java栈上对象的reference存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息
b) 直接指针访问:Java堆需要放置对象访问类型数据的相关信息,Java栈上对象的reference中存储的直接就是对象地址
Sun(Oracle) JDK和OpenJDK使用直接指针访问
两者的优缺点:
句柄访问:reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改
直接指针访问:速度快,它节省了一次指针定位的时间开销,由于对象的访问在JAVA中非常频繁,因此这类开销积少成多后也是非常可观的执行成本
13.如何判断一个对象是否可以回收
a) 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。优点:简单;缺点:无法解决对象相互循环引用的问题
b) 可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象
14.Java中可以作为GC Roots的对象
a) 虚拟机(栈帧中的本地变量表)中引用的对象
b) 方法区中类静态属性引用的对象
c) 方法区中常量引用的对象
d) 本地方法栈中JNI(即一般说的native方法)中引用的对象
15.JVM的永久代中会发生垃圾回收么?
a) 垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)
b) Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区
16. 引用的分类
a) 强引用:GC时不会被回收
b) 软引用:描述有用但不是必须的对象,在发生内存溢出异常之前被回收
c) 弱引用:描述有用但不是必须的对象,在下一次GC时被回收
d) 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用PhantomReference实现虚引用,虚引用用来在GC时返回一个通知
17.GC是什么?为什么要有GC?
a) GC是垃圾收集的意思
b) 内存处理是容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java没有提供垃圾回收的显示操作方法
c) Java提供System.gc()等方法请求(不是要求哦)JVM进行垃圾回收,但JVM不一定执行
18.Object.finalized()方法的作用
a) gc 只能清除在堆上分配的内存(纯java语言的所有对象都在堆上使用new分配内存),而不能清除栈上分配的内存(例如java调用c程序,而该c程序使用malloc分配内存时).如果某些对象被分配了栈上的内存区域,那gc就管不着了,对这样的对象进行内存回收就要靠finalize().,finalized可以在垃圾回收之前做一些清除工作
b) finalized()什么时候被调用
i. 对象被GC时会先调用finalized()方法
ii. 程序退出时,所有对象都会调用finalized()方法
iii. 显示调用finalized()方法
19.回收方法区
a) 方法区回收价值很低,主要回收废弃的常量和无用的类
b) 如何判断无用的类:
i. 该类所有实例都被回收(Java堆中没有该类的对象)
ii. 加载该类的ClassLoader已经被回收
iii. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方利用反射访问该类
20. 垃圾收集算法
a) GC最基础的算法有三种: 标记-清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法
i. 标记-清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象
ii. 复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉
iii. 标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
iv. 分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法
21.垃圾回收器
a) G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征
b) Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量(Java8使用的垃圾回收器)
22. Minor GC与Full GC分别在什么时候发生?
a) 新生代内存不够用时候发生MGC也叫YGC,JVM内存不够的时候发生FGC
23. 你知道哪些JVM性能调优
a) 设定堆内存大小
b) 设定新生代大小。 新生代不宜太小,否则会有大量对象涌入老年代
c) 设定垃圾回收器,新生代用-XX:+UseParNewGC;老年代用-XX:+UseConcMarkSweepGC