栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java jvm虚拟机大白话(总结篇)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java jvm虚拟机大白话(总结篇)

利用离职闲暇时间通过大概3个星期的样子看完了 儒猿技术窝  。建议其他的JAVA小伙伴也去看一下,系统的学习一下 JVM这一块的东西,只有看透了,吃透了。才会写出更加优质高效的项目。我没有收up主的广告钱哈,只是打心底觉得这个不错。好了回归主体:

JVM类加载机制

一个类从加载到使用,一般会经历

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

核心的主要关注:验证,准备,解析,初始化

验证

就是验证 .class类 是否符合JVM规范,后续才能交给JVM来运行。

准备

这个环节其实就是给加载的类,分配内存空间。 给一些变量赋初始值。

比如int 就默认给 0

解析

其实这个环节就是把 符号引用替换成直接引用

初始化

准备解决已经给他申请了内存空间,和默认值,但是真正执行还是在初始化环节

比如执行 static方法 之类的

如果执行类,依赖于某个类的话,就会提前加载那个依赖类,如何在继续执行

类加载器有那些? 启动类加载器

Bootstrap ClassLoader:他主要是负责加载Java安装 (lib)目录下的核心类

扩展类加载器

Entension ClassLoader: 主要加载Java安装 (libext)目录的类

应用程序类加载器

Application ClassLoader:负责加载 "ClassPath"环境变量所指定的路径的类,

其实就是加载 我们写好的 Java代码。

自定义类加载器

根据我们的需求,加载我们的类,类似于 class代码加密,需要自己实现 类加载器,来解密执行。

 什么是双亲委派机制?

原理:先找父亲去加载,不行的话再由儿子来加载。

这样的话,可以避免多层级的加载器结构重复加载某些类。

JVM中有哪些内存区域,分别都是用来干嘛的? 方法区

主要存放从".class"文件里加载进来的类,还会有一些类似常量池的东西放在这个区域里。

JDK1.8以后叫 "metaspace" 元数据空间。

程序计数器

记录当前执行的字节码指令的位置,也就是记录目前执行到了哪一条字节码指令。

jVM支持多线程,每个线程都会有自己的一个程序计数器,专门记录当前这个线程执行到了那一步了。

虚拟机栈

用来保存每一个方法内的局部变量等数据,这个区域就是Java虚拟机栈。

如果线程执行了一个方法,就会对这个方法调用创建对应的一个栈帧。

作用:调用执行任何方法时,都会给方法创建栈帧然后入栈。

除了程序计数器以外,还搭配了一个Java虚拟机栈内存区域来存放每个方法中的局部变量表。

堆内存

作用:存放我们在代码中创建的各种对象。

JVM的垃圾回收机制是用来干嘛的?为什么要垃圾回收?

为什么要垃圾回收:我们在Java堆内存里面创建的对象,都是占用内存资源的,而且内存资源有限。

JVM本身是有垃圾回收机制的,他是一个后台自动运行的线程。

面试:什么是垃圾对象?

当方法的栈帧出栈的时候,局部变量算法没有检测到又"GC Roots",这个对象就是垃圾对象,需要被回收掉。

JVM分代模型 年轻代(新生代)

大部分的正常对象实例化以后,都会优先分配到年轻代里面。

顾名思义,年轻代是 创建和使用完以后立马就要回收。

针对新生代垃圾回收算法

老年代

当新生代对象,在15次回收以后都没有被回收掉,系统会自动把该对象转移到JVM堆内存的老年代当中。

创建之后需要一直长期存在对象的,被称为老年代。

永久代

就是:永久代其实就是方法区,存放一些类信息。

存在一下情况永久代才会被垃圾回收:

1.首页该类的所有实例对象都已经从java堆内存里被回收

2.加载这个类的classloader已经被回收

3.该类的对象没有任何引用

满足以上三个条件就可以回收该类。

面试:什么时候触发新生代的垃圾回收?

当分配对象给新生代分配内存的时候发现内存空间不足的时候,就会触发一次新生代内存空间的垃圾回收,称之为"Minor GC" ,有的时候我们叫"Young GC",会尝试把那些没有人引用的垃圾对象,给回收掉。腾出更多的空间,然后放一个新的对象到新生代里面去。

面试:什么时候触发老年代的垃圾回收?

第一种:"Minor GC"之前,一通检查发现有可能"Minor GC"之后要进入老年代的对象太多,老年代放不下,此时需要提前触发"Full GC"然后在带着进行"Minor GC";

第二种:"Minor GC"之后,发现剩余对象太多放入老年代都放不下。

其实对象分配这一块还有很多复杂的机制比如:

1.新生代垃圾回收之后,因为存活的对象太多,导致大量的对象直接进入老年代。

2.超大的对象不经过新生代存放,直接进入老年代。

3.动态对象年龄判断机制。

4.空间担保机制。

面试题:什么情况下JVM内存中的一个对象会被垃圾回收?

JVM使用了一种可达性分析算法,判断哪些对象可以回收,那些对象不可以回收。

可达性分析算法:分析每个对象有谁在引用他,然后一层一层往上判断,看是否有一个"GC Roots"。

JVM规范中,局部变量、静态变量 是可以作为“GC Roots”

Java中对象不同的引用类型: 强引用
public static ReplicaManager replicaManager=new ReplicaManager()

这个就是强引用类型,那么垃圾回收绝对不会去回收这个对象。

软引用

正常情况下垃圾回收是不会回收软引用对象,但是如果你进行垃圾回收后,发现内存空间不足,内存快溢出的情况下,此时就会把这些软引用对象给回收掉,哪怕他被变量引用了,但是因为他是软引用,所以还是要回收。

弱引用

这个比较好解释:就跟没有引用类似,如果发送了垃圾回收,就会把这个对象回收掉。

总结:

比较常用的就是 强引用和软引用,强引用就是代表绝对不能回收的对象,软引用代表可有可无,如果内存实在不足了,就可以回收他。

有GC Roots引用对象是不会回收他,除非是 软引用和弱引用。但是没有 GC Roots引用的对象是会回收的。

假设没有GC Roots引用的对象,是一定立马被回收嘛?

java里面有一个 finailze()方法可以拯救自己

JVM中有哪些垃圾回收算法,每个算法各自的优劣? 复制算法

针对新生代垃圾回收算法

原理:新生代的内存分俩块,栈帧里面创建了一个对象,就会分配在新生代其中一块内存空间里。只使用一块内存,待那块内存快满的时候,就把里面存活的对象一次性转移到另外一块内存区域,保证没有碎片。接着一次性回收原来那块内存区域的垃圾对象,再次空一块内存区域。俩块内存区域就这么重复着循环使用。

 

缺点:

假设新生代1G的内存空间,那么只有900M的内存空间是可用的,另外的10M是为了转移存活对象而准备的。最明显的缺点就是:内存使用效率太低。

优点:

性能、内存碎片、效率的控制,都非常的好。

复制算法的内存区域划分 Eden区和Survivor区:

新生代内存区域分为三块:

1个Eden区,2个Survivor区 默认其中Eden区占80%内存空间,每一块Survivor区各占10%,比如说Eden区有800M内存,每一块Survivor区就100M内存。

平时使用的,就是Eden区和一块Survivor区,那么相当于就是有900M的内存是可用使用的。

对象刚开始分配在Eden区,如果Eden区快满了,此时就会触发垃圾回收。

会把Eden区中存活的对象一次性转移到一块空着的Survivor区,接着Eden区就会被清空,等待再次把对象分配到Eden区,此时就会有Eden区和Survivor区里面是有对象的,其中Surivor区里面放着是上次 Minor GC后存活的对象。

接着新对象继续分配到Eden区和另外那块开始被Survivor区,然后始终保持一块Survivor区是空着的,就这样一直循环使用这三块内存区域。

这样做最大的好处是只有10%的内存是闲置的,90%内存都被使用上了,无论是性能还是内存碎片的控制,还是效率,都非常的好。

新生代转移到老年代规则 1.躲过15次GC后进入老年代:

这个具体多少岁进入老年代,可以通过JVM参数 "-XX:MaxTenuringThreshold" 来设置,默认是15岁。

当新生代里面的对象,都执行了15次GC以后 还没有回收到,就会进入老年代。

2.动态对象年龄判断

假设当前Survivor区域里面,一批对象的总大小大于这块Survivor区域的内存大小的50%,那么此时大于等于这批对象年龄的对象,可以直接进入老年代。

要理清一个概念,实际这个规则运行的时候规则逻辑:年龄1+年龄2+年龄n的对象总和超过了Survivor区域的50%,此时就会把年龄n以上的对象都放入老年代。

3.大对象之间进入老年代

这个具体多少岁进入老年代,可以通过JVM参数 "-XX:PretenureSizeThreshold" 来设置为字节数,比如:“1048576”字节,就是1M。

为了解决一个大对象,在Survivor区里面来回复制多次才能进入老年代,那么大的对象在内存里面来回赋值,很耗时间。

4.Minor GC后对象太多无法放入Survivor区

 老年代空间分配担保规则

如果新生代大量对象存活下来,确实自己也放不下Survivor区域的话,必须转移到老年代区。

在执行任何一次"Minor GC"之前,JVM都会检查一下老年代可用的区域,是否大于新生代所以对象的总大小。

为什么?因为在极端情况下,GC过后所有的对象存活下来了,那不是新生代所有对象全部都要进入老年代。

1.如果发现老年的内存大小是大于新生代所有的对象大小时,就可以大胆的对新生代发起一次"Minor GC"了,因为即使GC之后所有对象都存活,Survivor区放不下,也可以转移到老年代去。

2.如果1没有满足的话,就看老年代的内存大小,是否大于之前每次"Minor GC"后进入老年代的对象的平均大小。

举个例子,之前每次Minor GC后,平均都有10MB左右的对象会进入老年代,那么此时老年代可用的内存大于10MB的话,说明很可能这次"Minor GC"过后也是差不多10MB左右的对象会进入老年代,此时老年代空间是够的。

如果2也没有满足的话 或者 "-XX:-HandlePromontionFailure" 参数没设置,此时就会触发一次 "Full GC",就是对老年代进行垃圾回收,尽量腾出一些内存空间,然后在执行"Minor GC"

如果2满足的话,可以冒点风险尝试一下 "Minor GC",此时进行"Minor GC"有几种可能。

第一种可能:"Minor GC"过后,剩余的存活对象的大小,是小于Survivor区的大小的,那么此时存活的对象进入Survivor区域即可。

第二种可能:"Minor GC"过后,剩余的存活对象大小,是大于Surivor区域的大小,但是小于老年代可用内存大小的,此时就直接进入老年代即可。

第三种可能:"Minor GC"过后,很不幸,前俩种(第一种可能、第二种可能)都不满足,就会发生"Handle Promotion Failure"的情况,这个时候就会触发一次"Full GC"。对老年代进行垃圾回收,同时也对一般新生代进行垃圾回收。因为这个时候必须把老年代里的没人引用对象给回收掉,然后才可能让"Minor GC"过后剩余的存活对象进入老年代里面。

要是"Full GC"过后,老年代还是没有足够的空间"Minor GC"过后的剩余存活对象,那么此时就会导致所谓的 "OOM"内存泄漏 。

老年代垃圾回收算法 老年代触发垃圾回收一共分俩种情况:

第一种:"Minor GC"之前,一通检查发现有可能"Minor GC"之后要进入老年代的对象太多,老年代放不下,此时需要提前触发"Full GC"然后在带着进行"Minor GC";

第二种:"Minor GC"之后,发现剩余对象太多放入老年代都放不下。

标记整理算法

老年代采取的是 "标记整理算法",这个过程说起来比较简单。

JVM中都有哪些常见的垃圾回收器,各自的特点是什么? Serial和Seral Old垃圾回收器

分别用来回收新生代和老年代的垃圾对象

工作原理:就是单线程运行,垃圾回收的时候会停止我们自己写的系统和其他工作工程,让我们系统直接卡死不动,然后让他们垃圾回收,这个现在一般写后台Java系统几乎不用。

ParNew和CMS垃圾回收器

ParNew现在一般都是用在新生代的垃圾回收器,CMS是用在老年代的垃圾回收器,他们都是多线程并发的机制,性能更好,现在一般都是线上生产系统的标配组合。

G1垃圾回收器

统一收集新生代和老年代,采用了更加优秀的算法和设计机制。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/353590.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号