Java经典面试-jvm

类加载过程

系统加载class类型的文件主要是:加载->连接->初始化连接又分为 验证->准备->解析

加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了

加载:

1.通过全类名获取指定此类的二进制字节流

2.将字节流所代表的静态存储结构转化为方法区的运行时数据结构

3.在内存中生成一个代表该类的class对象,作为方法区这些数据的访问入口

验证:

文件格式验证:是否符合class文件格式规范,

元数据验证:进行语义分析,是否该类继承了不允许继承的类

字节码验证->符号引用验证

准备:

准备阶段是正式为类变量分配内存并设置类变量的初始值的阶段,这些内存都将在方法区中分配

解析:

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。

初始化:

初始化阶段是执行初始化方法 <clinit> ()方法的过程,是类加载的最后一步,这一步 JVM 才开始真正执行类中定义的 Java程序代码(字节码)。

卸载

卸载类需要满足 3 个要求:

  1. 该类的所有的实例对象都已被 GC,也就是说堆不存在该类的实例对象。

  1. 该类没有在其他任何地方被引用

  1. 该类的类加载器的实例已被 GC

类加载器分类

JVM内置了三个重要的ClassLoader,除了BootstrapClassLoader其他类加载器均是由Java实现且全部继承自java.lang.ClassLoader

1.BootstrapClassLoader(启动类加载器):最顶层的加载类,由 C++实现,负责加载 %JAVA_HOME%/lib目录下的 jar 包和类或者被 -Xbootclasspath参数指定的路径中的所有类。

2.ExtensionClassLoader(扩展类加载器):要负责加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类

3.ApplicationClassLoader(应用程序类加载器):面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类

jvm内存区域划分

线程私有

程序计数器

虚拟机栈

本地方法栈

线程共享

方法区

直接内存(非运行时数据区的一部分)

堆栈的区别

1). 堆内存用来存放由new创建的对象和数组

2). 栈内存用来存放方法或者局部变量等

3). 堆是先进先出, 栈是先进后出

4). 共享性的不同:

栈内存是线程私有的

堆内存是所有线程共有的

jvm垃圾回收

Java自动内存管理最核心的功能就是内存中对象的分配和回收

Java堆是垃圾收集器管理的主要区域

从垃圾回收的角度来说,现在的收集器基本都采用分代垃圾收集算法,因此Java堆被划分为了不同的区域

新生代 新建的对象在Eden区分配,当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC,会将新生代中还存活着的对象复制进一个Survivor中,然后对Eden和另一个Survivor进行清理.如果存活的对象在survivor中放不下,直接把对象转移到老年代中

老年代 长期存活的对象将进入到老年代

永久代(1.7)|元空间(1.8)

所有新生成的对象优先放在年轻代的(大对象可能被直接分配在老年代,作为一种分配担保机制),年轻代按照统计规律被分为三个区:一个Eden区,两个 Survivor区.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中.因此可以认为年老代中存放的都是一些生命周期较长的对象

如何判断一个对象是否应该被回收,即死亡对象的判断方法

1.引用计数法

给对象中添加一个引用计数器

  • 每当有一个地方引用它,计数器就加1

  • 当引用失效,计数器减1

  • 任何时候计数器为0的对象就是不可能再被使用的

JVM没有使用这个算法,其最主要的原因是它很难解决对象之间相互循环引用的问题

所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC回收器回收他们
public class ReferenceCountingGc {
    Object instance = null;
    public static void main(String[] args) {
        ReferenceCountingGc objA = new ReferenceCountingGc();
        ReferenceCountingGc objB = new ReferenceCountingGc();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
    }
}

2.可达性分析

这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收

垃圾收集算法

标记-清除

该算法分为"标记"和"清除"两个阶段:首先标记出不需要被回收的对象,在标记完成后统一回收掉所有没有被标记的对象。(他是最基础的收集算法,后续的都是对其的改进)

不足:

1.效率问题

2.空间问题(标记清除后会产生大量不连续的碎片)

内存整理前

内存整理后

标记-复制

为了解决效率问题,标记-复制算法出现。它可将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另外一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收

内存整理前

内容整理后

标记-整理

根据老年代的特点提出的一种标记算法,标记过程仍然与"标记-清除"算法一样,但后续步骤不是直接对回收的对象进行回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存

回收前状态

回收后状态

比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集

jvm内存溢出问题如何排查

首先常见的内存溢出有两种:方法区溢出、堆内存溢出

方法区溢出:JVM会报如下类似错误:java.lang.OutOfMemoryError: PermGenSpace,Perm区的最大内存大小可以通过-XX:MaxPermSize=指定。引起这类内存溢出原因一般有两个,一个是常量池太大,一个是需要加载的CLASS类太多

堆内存溢出:在JVM可使用的最大堆内存可以在启动的时候通过-Xmx参数指定。堆内存溢出是最为常见的内存溢出问题,发生堆内存溢出时,JVM会报告如下错误:java.lang.OutOfMemoryError: java heap space

常用定位方法:

通过visualVM程序监控JVMJDK1.6中自带的可视化监控工具,这个工具比较实用,功能也比较强大。可以监控线程信息,堆内存信息,还可以实时导出堆信息以及强制GC。监控本地JVM直接启动该工具后就可以监控,远程监控需要启动jstatd和jrxml

启动jstatd方法:新建jstatd.all.policy文件,添加如下内容,tools.jar包的路径根据实际情况修改:

grant codebase "file:/home/ndmc/tomcat/jdk/jre/lib/tools.jar"{

permission java.security.AllPermission;

};

执行启动命令./jstatd-J-Djava.security.policy=jstatd.all.policy,默认端口为1099,后面可跟-p指定其他端口号

使用jrxml远程监控JVM的时候需要加上以下参数:

-Dcom.sun.management.jmxremote

-Dcom.sun.management.jmxremote.port=8849

-Dcom.sun.management.jmxremote.ssl=false

-Dcom.sun.management.jmxremote.authenticate=false

-Djava.rmi.server.hostname=hostip

监控效果如下:

  • 在监控过程中可以手动的GC和DUMP堆内存,还可以设置Heap dump on OOME: enabled,使得在堆内存溢出的时候可以dump heap。不过根据实际使用,建议最高在堆内存即将溢出的时候就应该手动dump heap,因为等到OOM的时候常常程序已经挂死,heap已经导不出来了

  • 导出dump信息后就可以通过jprofiler工具或者HeapAnalyzer做分析。这两个工具都很强大,网上有很多的使用指导。可以初步分析出到底是什么对象在占用内存,以及相应的引用链,到这一步在结合源代码在定位内存溢出问题就相对容易了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值