校招面试知识点总结------------JVM

一、java内存局域

1、JVM的内存区域划分

线程安全:堆,方法区
线程不完全:虚拟机栈,本地方法栈,程序计数器
1)程序计数器
解释:一小块内存空间,记录着当前程序运行到哪里了
使用的原因:由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。
特点
每条线程都有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。
可能抛出的异常:
当执行Native方法,计数器值为空,抛出OutOfMemoryError异常

2)Java虚拟机栈
概念:描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息,每一个方法从调用到执行
完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
特点:生命周期和线程相同
可能抛出的异常
1、线程请求的栈深度大于虚拟机所允许的深度,会抛出StackOverflowError异常。
2、如果Java虚拟机可以动态扩展,扩展时无法申请到足够的内存,会抛出OutoOfMemoryError异常。

3)、本地方法栈
什么是本地方法
本地方法是由其他语言编写的,编译成和处理器相关的机器代码。
一个本地方法就是一个java调用非java代码的接口。
概论:与虚拟机栈所发挥的作用非常相似,也会抛出上述两个异常。

4)Java堆
什么是Java堆
Java堆的唯一目的就是存放对象实例,几乎所有的对象实例都是在这里分配内存。
Java堆是垃圾收集器管理的主要区域,很多时候称为GC堆。
特点
1、对于大多数应用,是java虚拟机所管理的内存中最大的一块
2、在虚拟机启动时创建
3、Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可
可能抛出的异常
如果在堆中没有内存完成实例分配,并且堆也无法再扩展,会抛出OutoOfMemoryError异常

5)方法区
什么是方法区
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
特点
1、不需要连续的内存和可以选择固定大小和可扩展
2、选择不实现垃圾收集
可能抛出的异常
如果当方法区无法满足内存分配需求时,会抛出OutoOfMemoryError异常
什么是运行时常量池
它是方法区的一部分,所线程共享。虚拟机加载Class后把常量池中的数据放入到运行时常量池。
可能抛出的异常
如果当常量池无法申请到内存,会抛出OutOfMemoryError异常。

6)直接内存
什么是直接内存
并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,在NIO类中,引入了通道和缓冲区的I/O方式
其工作原理在于,使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆的DirectByteBuffer对象对这块内存引用进行操作,在某些场景显著提高性能
可能抛出的异常
如果各个内存区域总和大于物理内存限制,从而导致动态扩展时,会抛出OutoOfMemoryError异常

二、GC机制

GC算法应该分为这几部分来讲,首先明白它解决的问题
1)哪些内存需要回收?
2)什么时候回收?
3)如何回收?

1、如何判断对象是否存活?
方法一:引用计数法。为每个对象维护一个引用数,若出现一次引用则引用数+1,引用数为0则表示这个对象“死亡”。
缺点:很难解决对象之间循环引用的问题。
方法二:可达性分析法。指定一系列对象作为“GC roots”,从这些对象出发,沿着他们的引用链向下搜索,搜索不到的对象则称这个对象不可达,也就是不可用的。

可作为GC roots 的对象:
1)虚拟机栈引用的对象
2)本地方法栈引用的对象
3)方法区静态变量引用的对象
4)方法区常量引用的对象

引用强度 : 强引用>软引用>弱引用>虚引用

finalize()方法
任何一个对象的 finalize()方法都只会被系统调用一次。

若对象在进行可达性分析后发现没有与 GC roots 相连接的引用链,那么他将会被第一次标记并进行一次筛选,筛选的条件是该对象是否有必要执行 finalize()方法,当对象没有重写finalize()方法或者 finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没必要执行。

若该对象被判定为有必要执行 finalize 方法,则这个对象会被放在一个 F-Queue 队列,finalize 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-queue 中的对象进行第二次小规模的标记,若对象要在 finalize 中成功拯救自己—只要重新与引用链上的任何一个对象建立关联即可,那么在第二次标记时他们将会被移出“即将回收”集合。

2、Minor GC和Full GC
MinorGC发生的条件(时间):当新生代区的Eden区满了,就会触发Minor GC。
FullGC发生的条件:
1、老年代的空间不足
2、通过MinorGC进入老年代的对象的平均大小大于老年代的可用内存。
3、大对象直接进入老年代,老年代可用内存不足时。

3、GC收集算法
1)标记-清除算法:
首先标记所有需要回收的对象,然后再统一回收所有的对象
缺点:
产生大量不连续的内存空间。
标记和清除的效率都很低

2)标记-整理算法:
标记完对象后再整理,将存活对象移至一端,再清理另一边的对象
缺点:
效率比标记-清除算法更低。

3)复制算法:
将内存空间分为两块,每次只使用其中的一块,每次要清理的时候将有对象的那一块的对象全都复制到另一块上,完成后清理前一块的所有对象。
缺点:将内存缩小一半,内存使用率太低。
现代商业虚拟机一半采用如下策略:
将内存分为8:1:1,其中Eden空间占8,两块Survivor空间分别占1,每次只适用其中的Eden和一块Survivor。这样的话内存使用空间就提升到了90%。

4)CMS收集器的工作流程
1、初始标记 : 标记与GC roots之间相关联的对象, 暂停其他所有线程 ,时间短
2、并发标记 :进行GC Roots Traing的过程 ,并发
3、重新标记 :修正并发标记时由于程序继续工作到底的对象引用关系发生变化的情况 ,暂停其他所有线程 ,时间比初始标记稍长,
4、并发清理 :并发清理。

三、Out of Memory异常

1.程序计数器是唯一一个在 Java 虚拟机规范中没有规定任何 oom 情况的区域。
2.在 java 虚拟机规范中,对于 java 虚拟机栈,规定了 2种异常。
1)若线程请求的栈深度>虚拟机所允许的深度,则抛出 Stack Overflowerror 异常
2)若虚拟机可以动态扩展,若扩展时无法申请到足够的内存空间,则抛出 oom 异常。
3.java 虚拟机栈为执行 java 方法,本地方法栈为虚拟机使用 native 方法服务,本地方法栈也会抛出 Stack Overflowerror 和 oom。
4.Java 堆可以处于物理上连续的内存空间,只要逻辑上是连续的即可。可固定,可扩展。若堆中没有内存完成实例分配,并且堆也无法再扩展,则会抛出 oom。
5.直接内存不是运行时数据区的一部分。

堆上的OOM测试
这里写图片描述
Xms:堆初始大小。Xmx:堆最大大小
Xmn:分给新生代的大小。
Xss:设置栈的大小

四、类加载

类从加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括:
加载—验证—准备—解析—初始化—使用—卸载。
1、其中验证–准备–解析称为链接。

在加载阶段,虚拟机要完成下面3件事:
1)通过一个类的全限定名获取定义此类的二进制字节流。
2)将这个字节流所表示的静态存储结构转化Wie方法区运行时的数据结构
3)在内存中生成一个代表这个类的class对象,作为方法区的各种数据的入口

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

准备阶段是正式为类变量分配内存并设置变量的初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。
(注意不是实例变量,而是初始值,若public static int a=123,准备阶段过后a的值为0,而不是123,要初始化后才是123,但是若被final修饰,则在准备阶段就变为123了)。

解析阶段是虚拟机将常量池中的符号引用变为直接引用的过程。

初始化阶段才真正开始执行类中定义的 Java 程序代码(或者说是字节码)。初始化阶段是执行类的构造器(())方法的过程。init()方法是由编译器自动收集类的 all 类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。编译器收集的顺序是由语义在源文件中出现的顺序决定的。init()方法与类的构造函数不同,他不需要显示的调用父类的构造器,虚拟机会保证在子类的init()方法执行之前,父类的init()方法已经执行完毕。因此在虚拟机中第一个被执行的init()方法的类肯定是 java.lang.object。

2、通过一个类的全限定名来获取定义此类的二进制字节流,实现这个动作的代码就是“类加载器”
比较这两个类是否相同,只有这两个类由同一个类加载器加载的前提下才有意义,否则即使这个类来源于同一个class文件,被同一个虚拟机加载,只要加载他们的加载器不同,他们就是不同的类。

3、类加载器
从Java虚拟机的角度来说,只存在两种不同的类加载器:
1)启动类加载器
这个类加载器使用 c++实现,是虚拟机自身的一部分。
2)其他类加载器
这些类加载器都由 Java 实现,且全部继承自 java.lang.ClassLoader。

从 JAVA 开发人员角度,类加载器分为:
1)启动类加载器
这个加载器负责把 \lib目录中或者–Xbootclasspath下的类库加载到虚拟机内存中,启动类加载器无法被 Java 程序直接引用
2)扩展类加载器
扩展类加载器:负责加载\lib\ext 下或者 java.ext.dirs 系统变量指定路径下 all 类库,开发者可以直接使用扩展类加载器
3)应用程序类加载器
负责加载用户路径 classpath 上指定的类库,开发者可以直接使用这个类加载器,若应用程序中没有定义过自己的类加载器,一般情况下,这个就是程序中默认的类加载器。

双亲委任机制
若一个类加载器收到了类加载请求,它*首先不会自己去尝试加载这个类*,而是把所这个请求委派给父类加载器去完成,每一层的加载器都是如此,因此所有的加载请求最终都应该传送到顶级的启动类加载器。只有当父类加载器反馈自己无法加载时(他的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
双亲委派模型好处:
eg,object 类。它存放在 rt.jar 中,无论哪个类加载器要加载这个类,最终都是委派给处于模型顶端的启动类加载器加载,因此 object 类在程序的各种加载环境中都是同一个类。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值