[JVM]了断局: 说什么也没用,背就完了[必背]
Table of Contents
一. JVM在什么情况下会加载一个类
二. 什么时候会初始化一个类 ?
三.类加载器
四.什么情况下JVM内存中的一个对象会被垃圾回收??
五.如何进入老年代
六.CMS执行四个阶段
七. 对CMS的垃圾回收机制进行性能分析
八.几个触发老年代GC的时机
九.频繁FULL GC的常见原因 [五种]
十.为啥老年代的Full GC要比新生代的Minor GC慢很多倍,一般在10倍以上?
十一.Old GC和Full GC的触发时机
十二.Old GC执行的时候一般都会带上一次Young GC???
十三. CMS FULL GC 性能优化
十四.可能发生OOM的区域
十五. 通用模板
十六 字节码常量池 VS 运行时常量池 VS 字符串常量池
一. JVM在什么情况下会加载一个类
1.加载 : 你的代码中用到这个类的时候
2.验证: 根据Java虚拟机规范,来校验你加载进来的“.class”文件中的内容,是否符合指定的规范。
3.准备: 给类分配一定的内存空间然后给他里面的类变量(也就是static修饰的变量)分配内存空间,并且给一个默认的初始值
4.解析:
把将常量池中的符号引用转为直接引用,可以认为是一些静态绑定的会被解析,动态绑定则只会在运行是进行解析;
静态绑定包括一些final方法(不可以重写),static方法(只会属于当前类),构造器(不会被重写)。
5.初始化:执行类的初始化代码
6.使用
7.卸载
二. 什么时候会初始化一个类 ?
1) 遇到new、 getstatic、 putstatic或invokestatic这四条字节码指令时, 如果类型没有进行过初始化, 则需要先触发其初始化阶段。 能够生成这四条指令的典型Java代码场景有:
·使用new关键字实例化对象的时候。
·读取或设置一个类型的静态字段(被final修饰、 已在编译期把结果放入常量池的静态字段除外) 的时候。
·调用一个类型的静态方法的时候。2) 使用java.lang.reflect包的方法对类型进行反射调用的时候, 如果类型没有进行过初始化, 则需要先触发其初始化。
3) 当初始化类的时候, 如果发现其父类还没有进行过初始化, 则需要先触发其父类的初始化。4) 当虚拟机启动时, 用户需要指定一个要执行的主类(包含main()方法的那个类) , 虚拟机会先初始化这个主类.
5) 当使用JDK 7新加入的动态语言支持时, 如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、 REF_putStatic、 REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄, 并且这个方法句柄对应的类没有进行过初始
化, 则需要先触发其初始化。
6) 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时, 如果有这个接口的实现类发生了初始化, 那该接口要在其之前被初始化
三.类加载器
1.Bootstrap ClassLoader [启动类加载器]
他主要是负责加载我们在机器上安装的Java目录下的核心类的
加载Java安装目录下的“lib”目录中的核心类库。
2.Extension ClassLoader [扩展类加载器]
加载 " lib\ext " 目录
3.Application ClassLoader [应用程序类加载器]
加载“ClassPath”环境变量所指定的路径中的类
4.自定义类加载器
根据你自己的需求加载你的类。
四.什么情况下JVM内存中的一个对象会被垃圾回收??
什么时候会触发垃圾回收? 一旦新生代快满了,那么垃圾回收的时候
哪些变量引用的对象是不能回收的?
[ 可达性分析 ] 算法来判定哪些对象是可以被回收的,哪些对象是不可以被回收的.
1. 局部变量就是可以作为GC Roots的. 只要一个对象被局部变量引用了,那么就说明他有一个GC Roots,此时就不能被回收了。
2.静态变量也可以看做是一种GC Roots
3.方法的局部变量和类的静态变量是GC Roots。但是类的实例变量不是GC Roots。
有GC Roots引用的对象不能回收,没有GC Roots引用的对象可以回收,如果有GC Roots引用,但是如果是软引用或者弱引用的,也有可能被回收掉。
五.如何进入老年代
1.躲过15次GC之后进入老年代, -XX:MaxTenuringThreshold”来设置,默认是15岁 .
2.动态对象年龄判断
假如说当前放对象的Survivor区域里,一批对象的总大小大于了这块Survivor区域的内存大小的50%,那么此时大于等于这批对象年龄的对象,就可以直接进入老年代了.
年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄n以上的对象都放入老年代。
3.大对象直接进入老年代:“-XX:PretenureSizeThreshold”,可以把他的值设置为字节数,比如“1048576”字节,就是1MB。
4.Minor GC后的对象太多无法放入Survivor区:这个时候就必须得把这些对象直接转移到老年代去
六.CMS执行四个阶段
1.初始标记 [STW] :
标记出来所有GC Roots直接引用的对象 ;
方法的局部变量和类的静态变量是GC Roots。但是类的实例变量不是GC Roots。
2.并发标记:
系统线程可以随意创建各种新对象,继续运行.
在运行期间可能会创建新的存活对象,也可能会让部分存活对象失去引用,变成垃圾对象。
在这个过程中,垃圾回收线程,会尽可能的对已有的对象进行GC Roots追踪
但是这个过程中,在进行并发标记的时候,系统程序会不停的工作,他可能会各种创建出来新的对象,部分对象可能成为垃圾
追踪所有对象是否从根源上被GC Roots引用了,但是这个最耗时的阶段,是跟系统程序并发运行的,所以其实这个阶段不会对
系统运行造成影响的3.重新标记 [STW]
再次进入“Stop the World”阶段
重新标记下在第二阶段里新创建的一些对象,还有一些已有对象可能失去引用变成垃圾的情况
重新标记的阶段,是速度很快的,他其实就是对在第二阶段中被系统程序运行变动过的少数对象进行标记,所以运行速度很快。
4.并发清理
让系统程序随意运行,然后他来清理掉之前标记为垃圾的对象
这个阶段其实是很耗时的,因为需要进行对象的清理,但是他也是跟系统程序并发运行的,所以其实也不影响系统程序的执行
七. 对CMS的垃圾回收机制进行性能分析
第一个问题就是会消耗CPU资源。
因为最耗时的,其实就是对老年代全部对相关进行GC Roots追踪,标记出来到底哪些可以回收,然后就是对各种垃圾对象从内存里清理掉,这是最耗时的。
在并发标记和并发清理两个最耗时的阶段,垃圾回收线程和系统工作线程同时工作,会导致有限的CPU资源被垃圾回收线程占用了一部分
CMS默认启动的垃圾回收线程的数量是(CPU核数 + 3)/ 4。
用最普通的2核4G机器和4核8G机器来计算一下,
假设是2核CPU,本来CPU资源就有限,
结果此时CMS还会有个“(2 + 3) / 4”= 1个垃圾回收线程,去占用宝贵的一个CPU。
第二个问题"浮动垃圾"
在并发清理阶段,CMS只不过是回收之前标记好的垃圾对象
但是这个阶段系统一直在运行,可能会随着系统运行让一些对象进入老年代,同时还变成垃圾对象,这种垃圾对象是“浮动垃圾”
但是CMS只能回收之前标记出来的垃圾对象,不会回收他们,需要等到下一次GC的时候才会回收他们。
所以为了保证在CMS垃圾回收期间,还有一定的内存空间让一些对象可以进入老年代,一般会预留一些空间。
CMS垃圾回收的触发时机,其中有一个就是当老年代内存占用达到一定比例了,就自动执行GC。
“-XX:CMSInitiatingOccupancyFraction”参数可以用来设置老年代占用多少比例的时候触发CMS垃圾回收,JDK 1.6里面默认的值是92%。
那么如果CMS垃圾回收期间,系统程序要放入老年代的对象大于了可用内存空间,此时会如何?
Concurrent Mode Failure
此时就会自动用“Serial Old”垃圾回收器替代CMS,就是直接强行把系统程序“Stop the World”,重新进行长时间的GC Roots追
踪,标记出来全部垃圾对象,不允许新的对象产生
然后一次性把垃圾对象都回收掉,完事儿了再恢复系统线程。
第三个问题 "内存碎片"
就是老年代的CMS采用“标记-清理”算法,每次都是标记出来垃圾对象,然后一次性回收掉,这样会导致大量的内存碎片产生。
-XX:+UseCMSCompactAtFullCollection 默认打开
他意思是在Full GC之后要再次进行“Stop the World”,停止工作线程,然后进行碎片整理,就是把存活对象挪到一起,空出来大片连续内存空间,避免内存碎片
-XX:CMSFullGCsBeforeCompaction : 这个意思是执行多少次Full GC之后再执行一次内存碎片整理的工作,默认是0,意思就是每次Full GC之后都会进行一次内存整理。
八.几个触发老年代GC的时机
第一是 新生代空间不足, Minor GC 前 . 老年代可用内存小于新生代全部对象的大小,如果没开启空间担保参数,会直接触发Full GC,所以一般空间担保参数都会打开
第二是 新生代空间不足, Minor GC 前 . 老年代可用内存小于历次新生代GC后进入老年代的平均对象大小,此时会提前Full GC;
第三是新生代Minor GC后的存活对象大于Survivor,那么就会进入老年代,此时老年代内存不足。
第四 大对象直接进入老年代
第五 CMSInitiatingOccupancyFraction:
在使用CMS收集器的情况下,老年代使用了指定阈值的内存时,出发FullGC.。
如果老年代可用内存大于历次新生代GC后进入老年代的对象平均大小,但是老年代已经使用的内存空间超过了这个参数指定的比例,也会自动触发Full GC。
九.频繁FULL GC的常见原因 [五种]
1.系统承载高并发请求或者处理数据量过大,导致Young GC频繁, 而且每次Young GC过后存活对象过多,内存分配不合理.Survivor区域过小,导致对象频繁进入老年代,频繁触发FULL GC [合理分配内存,调整Survivor区域]
2.系统一次性加载过多的数据进内存,产生很多大对象,导致频繁有大对象进入老年代,导致FULL GC [dump内存快照,用MAT等工具分析]
3.发生内存泄漏,莫名创建大量对象,始终无法回收,一直占用老年代内存, 导致FULL GC[dump内存快照,用MAT等工具分析]
4.jstat 发现内存使用并不多,频繁触发GC
[Metaspace加载的类过多导致FULL GC,调用System.gc()方法触发Full GC , 最好在启动的时候设置参数禁用 -XX:DisableExplicitGC]
十.为啥老年代的Full GC要比新生代的Minor GC慢很多倍,一般在10倍以上?
Minor GC:
1.新生代执行速度其实很快,因为直接从GC Roots出发就追踪哪些对象是活的就行了,新生代存活对象是很少的,这个速度是极快的,不需要追踪多少对象。
2.采用复制算法, 直接把存活对象放入Survivor中,就一次性直接回收Eden和之前使用的Survivor了。
但是CMS的Full GC呢?
1.在并发标记阶段,他需要去追踪所有存活对象,老年代存活对象很多,这个过程就会很慢;
2.其次并发清理阶段,他不是一次性回收一大片内存,而是找到零零散散在各个地方的垃圾对象,速度也很慢;
3.最后完事儿了,还得执行一次内存碎片整理,把大量的存活对象给挪在一起,空出来连续内存空间,这个过程还得“Stop theWorld”,那就更慢了。
4.万一并发清理期间,剩余内存空间不足以存放要进入老年代的对象了,引发了“Concurrent Mode Failure”问题,那更是麻烦,还得立马用“Serial Old”垃圾回收器,“Stop the World”之后慢慢重新来一遍回收的过程,这更是耗时了。
十一.Old GC和Full GC的触发时机
(1)发生Young GC之前进行检查,如果“老年代可用的连续内存空间” < “新生代历次Young GC后升入老年代的对象
总和的平均大小”,说明本次Young GC后可能升入老年代的对象大小,可能超过了老年代当前可用内存空间
此时必须先触发一次Old GC给老年代腾出更多的空间,然后再执行Young GC
(2)执行Young GC之后有一批对象需要放入老年代,此时老年代就是没有足够的内存空间存放这些对象了,
此时必须立即触发一次Old GC
(3)老年代内存使用率超过了92%,也要直接触发Old GC,当然这个比例是可以通过参数调整的
(4) Metaspace 空间占满
十二.Old GC执行的时候一般都会带上一次Young GC???
一般Old GC很可能就是在Young GC之前触发或者在Young GC之后触发的,所以自然Old GC一般都会跟一次Young GC连带关联在一起了。
十三. CMS FULL GC 性能优化
1. CMSParallelRemarkEnabled
CMS垃圾回收器的"初始标记"阶段开启多线程并发执行
"初始标记"阶段,会进行STW,导致系统停顿,所以这个阶段开启多线程并发之后,可以提升这个阶段性能,减少STW时间
2.CMSScavengeBeforeRemark
开启在CMS重新标记阶段之前的清除尝试
CMS重新标记阶段也会STW , 在重新标记之前,先执行一个YGC,就会回收掉一些年轻代里没有引用的对象.
所以如果先提前回收掉一些对象,那么CMS的重新标记阶段就可以少扫描一些对象,这样就可以提升CMS在重新标记阶段的性能
十四.可能发生OOM的区域
1.Metaspace 空间 [Metaspace区域用来存放类信息]
设置区域大小参数: -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m
回收条件: 1.类加载器先要被回收 2.类的所有对象实例要被回收. 3.等等.....
OMM原因: 设置的Metaspace空间比较小,运行较大系统,空间不够用,导致OOM .
2. 虚拟机栈内存
每一个线程都有一个自己的虚拟机栈[栈内存]. 然后线程只要执行一个方法,就会为这个方法创建一个栈帧,将栈帧放到自己的虚拟机栈中,然后再这个栈帧里放入方法中定义的各种局部变量. 每个线程的虚拟机栈内存大小是固定的. [一般都是程序bug : 比如递归]
3.堆内存空间
3.1.JVM分配给堆内存的空间其实一般都是固定的,所以堆内存可能发生OOM [要么是系统负载搞,要么是内存泄漏]
(1). 系统承载高并发请求,因为请求量过大,导致大量对象都是存活的,所以要继续放入新的对象实在是不行了,导致OOM
(2).系统内存泄漏,就是莫名其妙弄了很多对象,结果对象都是存活的,没有及时取消对他们的引用,导致触发GC还是无法回收,此时只能引发内存溢出,因为实在是放不下更多的对象了.
3.2.有限的内存中放了过多的对象,而且大多数都是存活的,此时即使是GC之后还是大部分都存活,所以要继续放入更多对象已经不可能的,此时只能引发内存溢出问题.
十五. 通用模板
-server
-Xms4096M
-Xmx4096M
-Xmn3072M
-Xss1M
-XX:PermSize=256M ===> [ 替换 MetaspaceSize 元空间 ]
-XX:MaxPermSize=256M ===> [ 替换 MaxMetaspaceSize 元空间 ]
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX: CMSInitiatingOccupancyFraction =92
-XX:UseCMSInitiatingOccupancyOnly
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSParallelInitialMarkEnabled
-XX:+CMSScavengeBeforeRemark
-XX:+CMSParallelRemarkEnabled
-XX:+PrintHeapAtGC
-XX:DisableExplicitGC
-XX:PringGCDetails
-Xloggc:gc.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/app/logs/log_hprof/gc.hprof ----------下面的参数看情况----------
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=5
-XX:PretenureSizeThreshold=1M -XX:LargePageSizeInBytes=128m
-XX:+UseFastAccessorMethods
-XX:MaxDirectMemorySize=40M
十六 字节码常量池 VS 运行时常量池 VS 字符串常量池
常量池
常量池 是占用 class 字节码占用最大的数据项之一。也就是说该常量池是编译期产生的。里面存放着两大类的常量类型:
字面量(Literal) :
字面量类似于 Java 中的常量,如字符串,数字等
符号引用(Symbolic)
主要包括 3 类常量:
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
运行时常量池 : 字节码加载到内存中,其常量池的内容会放进运行时常量池中。运行时常量池里面除了字面量、符号引用,还有运行时对方法和字段解析出来的 直接引用字节码的常量池,它是 class 字节码文件的组成部分,而运行时常量池它是所有线程共享的一块内存区域。
字符串常量池
字符串常量池顾名思义就是用来存储字符串的,它和运行时常量池一样也是在方法区
内。
我们可以使用 String.intern 方法来操作字符串常量池。
[JVM]了断局: 说什么也没用,背就完了[必背]相关推荐
- [JVM]了断局: [ 目录 ]
[JVM]了断局: 说什么也没用,背就完了[必背] [JVM]了断局: "运行时数据区"理论梳理 [JVM]了断局: 虚拟机字节码指令表速查 [JVM]了断局: 类文件结构梳理 [ ...
- [JVM]了断局: 局部变量表和操作数栈实例分析
一.前言 本文以两段代码示例来解释说明,JVM在执行类中的方法时,[局部变量表]和[操作数栈]是如何配合工作的. 示例一: 1.代码 package com.classloading;public c ...
- python100个必背知识-python必背内容有哪些
python必背内容:1.变量,掌握变量的命名方法:2.数据类型,掌握int整型.float浮点型.bool布尔值型等各类型相互转换:3.掌握数组类型,定义在[[ ]]内,其内元素之间使用逗号分隔. ...
- python函数必背知识点_必背函数——python学习第四次总结
七个基本必背函数 join 将设置字符插入目标字符串中每个字符中间 split 按既定字符将目标字符串内全部对应分割,默认从左开始,可指定分割次数(分割后对应分割符不会返回) find 从前往后在既定 ...
- [JVM]了断局: Class文件结构梳理
预警: 这是一篇枯燥的文章,然而还是得懂. 慢慢看,头疼了就出去溜溜..... Table of Contents 一.概念 二. 示例代码 三.魔数 四.版本号 五. 常量池 六.访问标志 七.类索 ...
- [JVM]了断局: 堆外内存无法 [ -XX:MaxDirectMemorySize ] 限制
一. 前言 今天看到一句话 , 有点懵, 所以验证一下. 使用sun.misc.Unsafe的allocateMemory方法分配堆外内存.不受-XX:MaxDirectMemorySize这个JVM ...
- [JVM]了断局: G1 入门
一.概念 二.核心设计思路 三.如何设定G1对应的内存大小 四.新生代还有Eden和Survivor的概念吗? 五.G1的新生代垃圾回收 六.什么时候触发新生代+老年代的混合垃圾回收? 七.G1垃圾回 ...
- [JVM]了断局:字节码执行引擎
Table of Contents 一.前言 二.运行时栈帧结构 1.局部变量表 2.操作数栈 3.动态连接 4.方法返回地址 5.附加信息 三.方法调用 1.方法调用 2.解析 一.前言 执行引擎是 ...
- [JVM]了断局: “运行时数据区“理论梳理
Table of Contents 一.前言 二.运行时数据区 2.1.程序计数器 2.2.Java堆 2.3.方法区 2.4.运行时常量池 2.5.直接内存 2.6.Java虚拟机栈 2.7.本地方 ...
最新文章
- 制作nginx和php的rpm包
- linux数据流重定向
- JQueryEasyUI validatebox 扩展其自带验证方法
- python如何安装扩展库openpyxl和numpy_Python第三方库之openpyxl(2)
- Waymo研发经理:《自动驾驶感知前沿技术介绍》
- Recovery 流程简介
- “华尔街之狼”:预计BTC价格将反弹至10万美元
- 《麦肯锡方法》读书笔记15
- catia二次开发:IDE界面介绍
- 数据库mysql项目实战 一
- 支持向量机:Duality
- android 指纹验证api
- 服务器接上显示器操作,服务器接上显示器
- 立方体图片的在线绘制与制作
- 江南农村商业银行容器云平台建设经验分享
- mysql数据库实验+cmd界面运行基本操作总结(sql:数据增删改查,表格,视图,备份恢复)
- picpick尺子像素大小精度不够准确_如何使用像素标尺在PicPick
- EditPlus 说明
- 【研讨会现场】广州天嵌科技受邀参加2014年TI9月深圳研讨会
- t3软件怎么生成报表_t3财务报表怎么生成
热门文章
- Python遍历多个列表:ValueError: too many values to unpack (expected 2)
- JS报错解决:SyntaxError: Unexpected token 《 in JSON at position 0
- 程序员请尊重前辈的代码
- cvra机器人_PROCVRA一SE是什么意思?
- 皮尔森相关性系数的计算python代码(三)
- 蓝牙解码格式哪个最好_蓝牙耳机哪个品牌最好?百位声学工程师说了真心话!_...
- ava锁机制Synchronized方法简介
- 【爬虫】爬取B站UP的所有视频细节信息(通过UP名字)
- 毫米波雷达上险量增长超40%:头部厂商放量,伪玩家裸泳
- 打造Altium Designer 3D封装库