01_JVM原理与最佳实战总结流程图版
2021-08-26 15:08:35 1 举报
JVM学习总结
作者其他创作
大纲/内容
Region(新生代-Eden区)
Java虚拟机栈
2、将.java文件编译成.class文件
Minor GCYoung GC
JVM运行内存图
.java文件
Region
工作线程
Bootstrap ClassLoader
类加载器
.class文件
Region(老年代)
5、给类分配一定的内存空间,也就是类变量分配空间,并设置一个默认的初始值
垃圾回收线程
类加载
1、自底向上检查类是否已经加载
程序计数器
die Object6
survivor2(或from space)
Region(未分配)
方法区
Region(新生代)
Person p = 堆地址引用
被哪些变量引用的对象是不能回收的?通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
记录指令位置
从上面的这张图里面可以看出,对象在内存中的结构主要包含以下几个部分:Mark Word(标记字段):对象的Mark Word部分占4个字节,其内容是一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。Klass Pointer(Class对象指针):Class对象指针的大小也是4个字节,其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址对象实际数据:这里面包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节。对齐:最后一部分是对齐填充的字节,按8个字节填充。
Extension ClassLoader
Load CLASSPATH或-Djava.class.path所指定的目录下的类和jar包
Load JRE\\lib\t.jar或者-Xbootclasspath选项指定的jar包
ReplicaManager replicaManager
main线程
小于
初始化
老年代的标记-清除-整理算法
Region(新生代-Survivor区-S2)
否
调用方法压栈
老年代可用的内存空间, 是否大于新生代所有对象的总大小?
未垃圾回收
开始
判断\"-XX:-HandlePromotionFailure”参数是否设置
通过java.lang.ClassLoader的子类自定义加载class
本地库接口
进行Minor GC
JVM配置1、我们的系统需要部署多少台机器?2、每台机器需要多大的内存空间?3、每台机器上启动的JVM需要分配多大的堆内存空间?流程1、梳理业务流程2、梳理出创建对象的地方3、梳理一个业务中每秒会创建多少个对象,每个对象占用多大空间?4、一个完整的业务流程需要处理多久,也就是创建的对象会在内存里存活多久?5、梳理整个系统每秒创建多少个对象,占用多大空间6、计算出1小时进行一次Minor GC 堆内存应该设置为多大?7、如果业务搞活动,访问量增加10倍会怎么样?参考011、案例实战:每日百万交易的支付系统,如何设置JVM堆内存大小?
堆
回收垃圾之后
JVM堆内存详细划分
2、自顶向下尝试加载类
JVM堆设置1. -Xms: Java堆内存的大小2. -Xmx: Java堆内存的最大大小3. -Xmn: Java堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大了4. -XX:PermSize:永久代大小5. -XX:MaxPermSize:永久代最大大小6. -Xss:每个线程的栈内存大小设置如下-Xms512M -Xmx512M -Xmn256M -Xss1M
JVM运行时数据
说明:此设计可避免多层级的加载器结构重复加载某些类
准备
验证
Virtual-伸缩区
......
isLocalDataCorrupt方法栈帧
Region(新生代-Eden区)
Major GC
是
Tenured
说明:所谓STW就是stop the word,就像现实世界突然静止一样,在Java的世界就是工作线程停止工作的意思。
进行Full GC
Region(新生代-Survivor区-S1)
.......
Full GC:Major GC时会触发Minor GC 所以称Major GC为Full GC
2、并发标记:从上一步被标记的对象开始,进行可达性分析组成“关系网”,不需要STW
Load JRE\\lib\\ext\\*.jar或 -Djava.ext.dirs指定目录下的jar包
年轻代内存划分
main()线程Java虚拟机栈
Young区被划分为三部分,一个Eden区和两个大小严格相同的Survivor区,其中Survivor区间中,某一时刻只有其中一个是不使用的,另外一个留做垃圾收集时复制对象用,在Youn区间变满的时候,minor GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,仍然存活于Survivor的对象将被移动到Tenured区间。从图中可以清楚的看到他们的关系是8:1。那为什么Eden占用这么多呢?因为对象都会在Eden区创建。每次只使用Eden区和一个Survivor区,当这两个区满了之后就会将还存活的对象复制到另一个空白区(MINOR GC),大家是不是在想那空间怎么会够用呢?其实年轻代的对象有98%都是朝生夕死的,因为大部分产生垃圾的地方就是java类中的方法里,方法里创建的对象,在方法结束一般就成为垃圾对象。所以根本不用担心不够用,这也是为什么比例是8:1而不是1:1的原因。而且!就算是不够用,我们不是还有年老代吗!
默认新生代对堆内存的占比是5%最多占比不会超过60%
Object5
年老代 | Tenured
程序员
线程的栈内存-Xss
java程序运行流程
软引用:就是把\"ReplicaManager” 实例对象用一个\"SoftReference\" 软引用类型的对象给包裹起来了,此时这个\"replicaManager\" 变量对\"ReplicaManager” 对象的引用就是软引用了。正常情况下垃圾回收是不会回收软引用对象的,但是如果你进行垃圾回收之后,发现内存空间还是不够存放新的对象,内存都快溢出了此时就会把这些软引用对象给回收掉,哪怕他被变引用了,但是因为他是软引用,所以还是要回收。
span style=\"font-size: inherit;\
新生代-Xmn
新生代使用的垃圾回收算法是复制。未回收垃圾 图中分为了两个部分,每次只使用其中的一部分(这里不是完全按照刚刚伊甸园和空白区的占用比例来讲,可以理解为通用版)。当这部分满了后,就会将还存活的复制到另一个区,再将这个区清空,如 回收垃圾之后图。
加载
4、根据Java虚拟机规范,来校验你加载进来\".class\"文件中的内容,是否符台指定的规范
可达性分析的基本思路就是:通过将一些称为\"GC Roots\"的对象作为起始点,从这些节点开始搜索,搜索和该节点发生直接或者间接引用关系的对象,将这些对象以链的形式组合起来,形成一张“关系网”,又叫做引用链。最后垃圾收集器就回收一些不在这张关系网上的对象。连接GC Roots对象的object是确定还存活的对象,而右边的die obj由于和GCROOTS没有关系,所以会标记为可回收的对象。目前主流的商用虚拟机用的都是类似的方法。那什么对象才能作为“GC Roots”呢?在java中,有四种对象可以作为“GC Roots” 1:栈帧中的引用对象,也就是方法里创建的引用的对象。(栈中的) 2:静态属性引用的对象。(方法区中的) 3:常量引用的对象。(方法区中的) 4:本地方法栈中JNI引用的对象。(本地方法栈中的)
G1的核心设计思路:把内存拆分为大量小Region,以及追踪每个Region中可以回收的对象大小和预估时间,最后在垃圾回收的时候,尽量把垃圾回收对系统造成的影响控制在你指定的时间范围内,同时在有限的时间内尽回收尽可能多的垃圾对象。G1是如何做到对垃圾回收导致的系统停顿可控的?G1清楚每个Region里的对象有多少是垃圾,如果对这个Region进行垃圾回收,需要耗费多长时间,可以回收掉多少垃圾?在G1中,每一个Region时可能属于新生代,但是也可能属于老年代的,这个由G1来控制。G1中Region数量及每个Region的大小 1、默认情况下自动计算和设置,先给整个堆内存设置一个大小, 比如说用” -Xms\"和\"-Xmx\" 来设置堆内存的大小。2、然后JVM启动的时候一旦发现你使用的是G1垃圾回收器,此时会自动用堆大小除以2048(使用“-XX:+UseG1GC\
永久代JDK1.8之前版本-XX:PermSize-XX:MaxPermSizeJDK1.8及以后版本-XX:MetaspaceSize-XX:MaxMetaspaceSize
Customer ClassLoader
新生代 | 年轻代 | Young
字节码执行引擎
连接
加载器的双亲委派机制
老年代(新生代设置完后的剩下的就是老年代)
Eden
Java 堆内存
堆内存
GC Roots
Object3
Java中对象不同的引用类型强引用:就是最普通的代码,一个变量引用一个对象,只要是强引用的类型,那么垃圾回收的时候绝对不会回收这个对象的。
线程共享区域:所有线程共享同一个JDK1.8之前叫方法区,JDK1.8叫Metaspace 元数据空间方法区:主要是放从\".class\"文件里加载进来的类,还会有一些类似常量池的东西放在这个区域里。如Person.class就存放在这个里面堆:存放代码中创建的对象线程独享:每个线程里都有一份程序计数器:记录当前字节码指令执行的位置虚拟机栈:保存每个方法内的局部变量等数据本地方法栈:存放native方法的局部变量表之类的信息字节码执行引擎:执行我们写的代码编译出来的代码指令JDK1.6及之前,有永久代,字符串常量池和运行时常量在方法区JDK1.7 有永久代,但已经逐步“去永久代”,字符串常量池称支堆中,运行时常量池还在方法区中JDK1.8及之后,无永久代,字符串常量池在堆中,运行时常量池在元空间
main方法栈帧
虚拟机栈
触发Minor GC的条件?1、Eden区里内存空间都满了会触发Minor GC,比如Eden区一共有100MB,已经占用了96MB,再来一个对象有5MB,创建对象大小>Eden剩余可用大小,则触发Minor GC。2、进行Major GC 时会触发Minor GC。
JVM
如:public class PersonDemo{ public static void main(String[] args){ Person p = new Person(); xx(); } public static void xx(){ System.out.println(\"比赛\"); }}
XX方法栈帧
大于
解析
弱引用:就是把\"ReplicaManager” 实例对象用一个\"WeakReference\" 软引用类型的对象给包裹起来了,此时这个\"replicaManager\" 变量对\"ReplicaManager” 对象的引用就是虚引用了。弱引用就跟没引用是类似的,如果发生垃圾回收,就会把这个对象回收掉。
未回收垃圾
对象进入老年代的规则 1、躲过15次GC之后进入老年代对象每次在新生代里躲过一次GC被转移到一块Survivor区域中,此时他的年龄就会增长一岁。默认的设置下,当对象的年龄达到15岁的时候,也就是躲过15次GC的时候,他就会转移到老年代里去。这个具体是多少岁进入老年代,可以通过JVM参数\"-XX:MaxTenuringThreshold\" 来设置,默认是15岁,大家看下图。 Max Tenuring Threshold 字面意思:进入老年代的最大阀值。2、动态对象年龄判断进入老年代年龄1 +年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄n以上的对象都放入老年代。3、大对象直接进入老年代\"-XX:PretenureSizeThreshold\" 参数,把他的值设置为字节数,比如”1048576\" 字节,就是1MB。意思就是,如果你要创建一个大于这个大小的对象, 比如一个超大的数组,或者是别的东西,此时就直接把这个大对象放到老年代里去。压根儿不会经过新生代。4、Minor GC后的对象太多无法放入Survivor区,进入老年代在Minor GC之后发现剩余的存活对象太多了,没办法放入另外一块Survivor区,这个时候就必须得把这些对象直接转移到老年代
垃圾收集器整理Serial(单词意思:连续的)收集器:一个单线程收集器,在进行回收的时候,必须暂停其他所有的工作线程,直到收集结束。缺点:因为要完全暂停线程,所以用户体验不佳。但是由于新生代回收得较快,所以停顿的时间非常少,而且没有线程切换的开销,因此也简单高效。通过 -XX:+UseSerialGC参数启用。ParNew(单词意思:标准的)收集器:这个是Serial收集器的多线程版本,适用于多核CPU的设备。但对于单核的设备来说,需要进行线程之间的切换,效率反而没有单线程的高。通过-XX:ParallelGCThreads参数限制收集的线程数,-XX:+UseParNewGC参数启用。Parallel Scavenge(单词意思:平行清除)收集器:该收集器是我们文章中的所有例子的默认年轻代收集器。他的关注点和其他的收集器不同,其他的关注点是尽可能的缩短Full GC的时间。而该收集器关注的是一个可控的吞吐量。吞吐量=运行代码的时间/(运行代码的时间+GC的时间),通过参数-XX:MaxGCPauseMillis设置最大GC的停顿时间和-XX:GCTimeRatio 设置吞吐量的大小。-XX:+UseParallelGC参数启用。主要适合在后台运算而不需要太多交互的任务。Serila Old收集器:该收集器是Serial收集器的老年代版,同样是一个单线程的收集器,优劣势和Serial收集器一样,这里就不多说了。Parallel Old收集器:在我们之前文章的代码例子中默认的年老代收集器,也是Parallel Scavenge收集器的老年代版本。关注点也和Parallel Scavenge收集器一样,注重系统的吞吐量,适合于CPU资源敏感的场合。CMS(Concurrent Mark Sweep)收集器:是一种以最短停顿时间为目标的收集器。当应用尤其重视服务的响应速度,希望系统能有最短的停顿时间,该收集器非常适合。
老年代的内存大小,是否大于之前每一次Minor GC后进入老年代的对象的平均大小。
Java堆内存-Xms-Xmx
new ReplicaManager()
使用
isCorrupt =false
3、重新标记:修改在上一步标记中有了变动的对象,需要STW
方法结束出栈
元空间 | 方法区
进行Minor GC之后的有几种可能?第一种可能,Minor GC过后,剩余的存活对象的大小,是小于Survivor区的大小的, 那么此时存活对象进入Survivor区域即可。第二种可能,Minor GC过后,剩余的存活对象的大小,是大于Survivor区域的大小,但是是小于老年代可用内存大小的,此时就直接进入老年代即可。第三种可能,很不幸,Minor GC过后,剩余的存活对象的大小,大于了Survivor区域的大小,也大于了老年代可用内存的大小。此时老年代都放不下这些存活对象了,就会发生\"Handle Promotion Failure\"的情况,这个时候就会触发一次“FullGC\"。Full GC就是对老年代进行垃圾回收,同时也一般会对新生代进行垃圾回收。疑问:老年代的先还是年代的先?先进行老年代GC,因为是年代空间放不下了才进行的Full GC。因为这个时候必须得把老年代里的没人引用的对象给回收掉,然后才可能让Minor GC过后剩余的存活对象进入老年代里面。如果要是Full GC过后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会导致所谓的\"OOM”内存溢出。什么情况下Minor GC后的对象会进入老年代?1、默认躲过15次GC之后进入老年代。2、当前放对象的Survivor区域里,一批对象的总大小大于这块Survivor区域的内存大小的50%,那么此时大于等于这批对象年龄的对象,就可以直接进入老年代。3、大对象直接进入老年代。
4、并发清除:清理掉之前标记为垃圾的对象,不需要STW
方法调用流程
survivor1(或to space)
3、类加载
6、将符号引用替换为直接引用
xx()方法栈桢
XX线程
未垃圾回收后
Kafka.classReplicaManager.class
JVM各内存区域参数设置
survivor区 | 存活区
复制算法流程
main方法栈帧方法内的局部变量
loadReplicasFromDisk方法栈帧
老年代空间分配担保规则
可达性分析内存图
本地方法库
说明:堆一共分成了33个Region,新生代约占60%一共20个Region,其中Eden区16个,Survivor区共占4个。老年代约占用40%一共13个Region
本地方法栈
元空间(perm 永久代1.8之前的名称)
Application ClassLoader
执行类的初始化代码
方法区 | Metaspace
Object4
1、初始标记:标记出来所有GC Roots直接引用的对象,需要STW
Object1
1、编写Hello.java文件
hasFinishedLoad =false
CMS收集器工作过程
说明:绿色为活着的对象黄色为死去的对象这里是用一般的植物颜色来配的,蓝色象征着水,绿色象征生命,黄色象征枯萎。
未完成
Object2
main()方法栈桢
卸载
0 条评论
下一页