JVM相关整理

JVM

1说一下JVM内存模型?

1、JVM内存模型:线程独占:栈,本地方法栈,程序计数器 线程共享:堆,方法区

2、栈:又称方法栈,线程私有的,线程执行方法是都会创建一个栈阵,用来存储局部变量表,操作栈,动态链接,方法出口等信息.调用方法时执行入栈,方法返回式执行出栈.

3、本地方法栈与栈类似,也是用来保存执行方法的信息.执行Java方法是使用栈,执行Native方法时使用本地方法栈.

4、程序计数器保存着当前线程执行的字节码位置,每个线程工作时都有独立的计数器,只为执行Java方法服务,执行Native方法时,程序计数器为空.

5、堆JVM内存管理最大的一块,对被线程共享,目的是存放对象的实例,几乎所欲的对象实例都会放在这里,当堆没有可用空间时,会抛出OOM异常.根据对象的存活周期不同,JVM把对象进行分代管理,由垃圾回收器进行垃圾的回收管理

6、方法区:又称非堆区,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器优化后的代码等数据.1.7的永久代和1.8的元空间都是方法区的一种实现

2 简述类加载的过程?

加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载

验证 -> 准备 -> 解析 这三步合起来称为 连接

1.加载

​加载简单来说分为三步。

第一步:获取二进制字节流也就是上面的class文件。

第二步:将静态的存储结构转换为方法区中的运行时数据结构。

第三步:生成一个对象放入java堆中,做为对方法区的引用。

(类的加载就是将class文件中的二进制数据读取到内存中,然后将该字节流所代表的静态数据结构转化为方法区中运行的数据结构,并且在堆内存中生成一个java.lang.Class对象作为访问方法区数据结构的入口)

2.验证

验证主要是检验如下的几项是否正确

​ class文件的表示(魔数),class文件的版本号,class文件的每个部分是否正确(字段表、方法表等),验证常量池(常量类型、常量类型数据结构是否正确,utf-8是否标准),元数据验证(父类验证,继承验证,final验证),字节码(指令)验证,符号引用验证(是否能根据符号找到对应的字段、表、方法等)

如果一项不对,就会验证失败。

3.准备

准备阶段为类变量分配内存 和设置类变量初始化。这个过程中,只对static类变量进行内存分配,这个时候只是分配内存,没有进行复制,所有的类变量都是初始化值。如果是final的话,会直接对应到常量池中。会在准备阶段直接赋值。

4.解析

解析阶段是读符号引用进行解析。将符号引用解析为直接引用(指向目标的指针或者偏移量)。主要涉及到的解析有类,接口,字段,方法等。

5.初始化

初始化就是执行方法的过程, 对静态变量,静态代码块进行初始化,对类进行初始化。

3 说一下类加载器的种类?

  1. 启动类加载器(BootStrap ClassLoader):该类并不继承ClassLoader类,其是由C++编写实现。用于加载JAVA_HOME/jre/lib目录下的类库。
  2. 扩展类加载器(ExtClassLoader):该类是ClassLoader的子类,主要加载JAVA_HOME/jre/lib/ext目录中的类库。
  3. 用类加载器(AppClassLoader):该类是ClassLoader的子类,主要用于加载classPath下的类,也就是加载开发者自己编写的Java类。
  4. 自定义类加载器:开发者自定义类继承ClassLoader,实现自定义类加载规则

4 什么是双亲委派机制?有什么优点?

1. 当加载一个类时,先判断此类是否已经被加载,如果类已经被加载则返回;

2. 如果类没有被加载,则先委托父类加载(父类加载时会判断该类有没有被自己加载过),如果父类加载过则返回;如果没被加载过则继续向上委托;

3. 如果一直委托都无法加载,子类加载器才会尝试自己加载

优点:

1、避免类的重复加载

2. 避免Java的核心API被篡改

5 如何打破双亲委派机制?

(一)为什么要打破双亲委派机制?

有时我们需要多次加载同名目录下的类,比如:当我们在Tomcat上部署多个服务时,不同服务上可能依赖了不同版本的第三方jar,如果此时使用双亲委派机制加载类,会导致多个服务中第三方jar只加载一次,其他服务中的其他版本jar将不会生效,导致请求结果异常。为了避免这种情况,我们需要打破双亲委派机制,不再让父类[应用类加载器]加载,而是为每个服务创建自己的子类加载器。

(二)如何打破双亲委派机制?

打破双亲委派有两种方式:(1)不委派【SPI机制】;(2)向下委派。

Tomcat使用父类加载器加载了公用的jar,对于非公用的jar则使用自己的子类加载器进行单独加载。打破双亲委派需要重写findClass()和loadClass()方法。

6 如何判断对象可以被回收?

判断对象是否存活一般有两种方式:

引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,但无法解决对象相互循环引用的问题。

可达性分析:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。

7说一下分带垃圾回收的过程?

分代回收器有两个分区:老年代和新生代,新生代默认的空间占比总空间的 1/3,老年代的默认占比是 2/3。

新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

把 Eden + From Survivor 存活的对象放入 To Survivor 区;

清空 Eden 和 From Survivor 分区;

From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。

每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老年代。大对象也会直接进入老年代。

老年代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。

8你知道哪些垃圾收集算法?

GC最基础的算法有三种: 标记 -清除算法、复制算法、标记-压缩算法

“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存分代收集算法,

“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

9 垃圾回收器有哪些?

Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。

SerialOld老年代收集器,单线程收集器,单线程,将废弃的对象干掉,只留幸存 的对象。

ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本。

Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。最高效率的利用CPU。

Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,将幸存的对象复制到预先准备好的区域。

CMS收集器,CMS(Concurrent Mark Sweep),致力于获取最短回收停顿时间,使用标记-清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。

G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。

总结:

新生代回收器:Serial、ParNew、Parallel Scavenge

老年代回收器:Serial Old、Parallel Old、CMS

整堆回收器:G1

10 什么情况下触发垃圾回收?

一般就分为 Minor GC 和 Full GC 两种情况。

年轻代发生垃圾回收的时机(Minor GC):

当 Eden 区没有足够空间分配时

整堆触发垃圾回收的时机 (FULL GC):

当年轻代晋升到老年代的对象大小比目前老年代剩余的空间大小还要大时。

当老年代的空间使用率超过某阈值时

当元空间不足时(JDK1.7永久代不足)

调用 System.gc() 时,系统建议执行 Full GC,但是不必然执行

11说一下对象的4种引用情况?

强引用:

最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。

Object obj = new Object()”

当一个对象被强引用变量引用时,它处于可达状态,是不可能被垃圾回收器回收的,当内存不足,就算是出现了 OOM 也不会对该对象进行回收,打死都不收。因此强引用有时也是造成 Java 内存泄露的原因之一

软引用:

软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference 类来实现,可以让对象豁免一些垃圾收集。

软引用用来描述一些还有用,但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。

弱引用:

弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

弱引用需要用java.lang.ref.WeakReference类来实现。

对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,都会回收该对象占用的内存。

虚引用:

虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。

虚引用,顾名思义,就是形同虚设,与其他几种引用都不太一样,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。

虚引用需要java.lang.ref.PhantomReference类来实现。

如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(RefenenceQueue)联合使用。

虚引用的主要作用是跟踪对象垃圾回收的状态。仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。

PhantomReference 的 get 方法总是返回 null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入 finalization 阶段,可以被 GC 回收,用来实现比 finalization 机制更灵活的回收操作。

换句话说,设置虚引用的唯一目的,就是在这个对象被回收器回收的时候收到一个系统通知或者后续添加进一步的处理。

12 OutOfMemoryError和StackOverflowError,这两者的区别是什么,怎么解决?

1、OutOfMemoryError 常见场景:

Java.lang.OutOfMemoryError: PermGen space

程序中使用了大量的jar或class,使java虚拟机装载类的空间不够。

解决:增加java虚拟机中的XX:PermSize和XX:MaxPermSize参数的大小,

如:-XX:PermSize=64M -XX:MaxPermSize=128m

java.lang.OutOfMemoryError: Java heap space

java虚拟机创建的对象太多,在进行垃圾回收之前,虚拟机分配的到堆内存空间已经用满了。

解决:设置堆的起始大小、最大空间,

如:-Xms256m -Xmx1024m

java.lang.OutOfMemoryError: unable to create new native thread

环境提供的服务器配置偏低,而项目本身为了性能,大量的使用的线程,因为线程的开辟比new Object是需要很大空间的。创建线程数超过了操作系统的限制。

解决:减少程序的线程数量;或者增大服务器的线程限制数量。

2、StackOverflowError 常见场景:

StackOverflowError 是一个java中常出现的错误:在jvm运行时的数据区域中有一个java虚拟机栈,当执行java方法时会进行压栈弹栈的操作。在栈中会保存局部变量,操作数栈,方法出口等等。jvm规定了栈的最大深度,当执行时栈的深度大于了规定的深度,就会抛出StackOverflowError错误。

java.lang.StackOverflowError 这种异常,大多因为方法递归调用不合理。

解决方案:

​控制递归合理结束。

把递归调用函数改用while或者for循环来实现 。 

增大栈的大小值。(如 -Xss100m ) ​

改用堆内存。(把局部变量改成全局静态变量)

13 你知道哪些JVM性能调优的参数?

设定堆内存大小

-Xmx:堆内存最大限制。

设定新生代大小。 新生代不宜太小,否则会有大量对象涌入老年代

-XX:NewSize:新生代大小

-XX:NewRatio 新生代和老生代占比

-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比

设定垃圾回收器

-XX:+UseParNewGC年轻代用

-XX:+UseConcMarkSweepGC老年代用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心对元&鑫鑫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值