利用离职闲暇时间通过大概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垃圾回收器统一收集新生代和老年代,采用了更加优秀的算法和设计机制。



