[ JVM ] 螺丝刀学习笔记之 —— 重学JVM概览
Posted 削尖的螺丝刀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[ JVM ] 螺丝刀学习笔记之 —— 重学JVM概览相关的知识,希望对你有一定的参考价值。
这里是螺丝刀本人在学习JVM专栏时,结合自己理解对一些重点概览做的提炼总结,为方便自己学习和记忆,也更加欢迎感兴趣的童鞋一起交流探讨。
JVM是如何运行JAVA文件的?
-
Java文件在打包成jar或者war后会变成Class文件,在容器里面调用JVM,时候就会加载class文件
- 加载的过程: 加载 —— 连接(校验,准备(在这里会设置内存空间,并给static赋默认值,finalstatic直接赋值放在常量池,这个必须记住),解析) —— 初始化(在这里会调用静态方法) —— 使用 —— 销毁
- 加载方式: 双亲委派
- 加载时机:
- main方法中
- new 对象的时候
- 加载对象的时候发现有父类没加载也会加载
-
Tomcat的web容器是如何加载Java文件的?
JVM内存模型的一些介绍
- class文件在1.8以前是放在永久代的方法区,1.8以后取消了永久代,放在了元空间(系统内存大小)
- 线程私有:
- 程序计数器: 因为我们编译好的代码都是在字节码文件中的,而字节码文件中标明了各代码的执行顺序,JVM会根据 字节码引擎运行程序计数器,执行字节码中的代码,这个程序计数器是各线程私有的
- 虚拟机栈: 为什么叫栈呢?因为线程每执行一个方法就调用对应的栈帧会做一次压栈。栈帧里有:
- 栈帧中保存了局部变量表(这也是为什么局部变量是线程私有的,线程安全),这里面的变量指向堆中的具体对象。
- 操作数栈
- 动态链接
- 方法出口
- 本地方发栈: 里面的所有代码都不是JAVA写的,而是C写的native方法。
在说到内存模型的时候,一定脑子里要有一个动的图,比如
1.类会被加载到metaSpace,然后初始化等 —— 2.线程启动会有程序计数器指定方法的执行的顺序 ——> 3.每个线程中有栈帧(包含
局部变量表、操作数栈、动态链接、方法出口
),每个方法执行的时候都会和局部变量被压入栈中。4 ——> 每个栈帧中的局部变量都会指向堆中的具体对象。 ——> 5. 如果是执行的本地native方法则会在本地方法栈中做压栈。
一个方法执行完后会怎样?
- 虚拟机栈中的栈帧全部会出栈,这时没有对象再指向堆中了,也就是说没有了GCROOT(局部变量可作为GCROOT)的引用,可以被回收
为什么要区分内存空间?
- 因为内存资源是有限的,我们要做合理的区分和管理,比如有些对象是朝生夕死的,所以就要放再年轻代中做多次GC来判定是否该去老年代存储。
一个Java对象在堆内占多少内存空间?
- 首先,内存空间占用分为两块(1.对象本身信息、2.对象中的变量数据所占用的空间)
对其填充: jvm内存占用是8的倍数,所以结果要向上取整到8的倍数
方法区(mataSpace内会不会进行回收?)
关于垃圾回收(题目为:对象在JVM如何分配的?如何流转的?)
- 回收时间,空间快满(Eden和S1都满了)的时候会发生回收,分配是会先在年轻代(Eden)分配,如果满了回收后的晋升机制如下
- 新生代回收后的晋升机制:
- 年龄回收机制: 一次回收代表一次年龄增长,如果躲过15次回收就代表15岁了,在JVM中,新生代默认躲过15次回收的会被放入老年代(可通过-XX: MaxTenuringThreshold设置这个阈值)。
- 大对象回收机制: 特别大的对象(默认超过1M,-XX: PretenureSizeThreshold可设置)会直接进入老年代
- 动态年龄判断机制 : Suviver中的数据量总大小 超过了这块Suviver**区域的50%**会直接进入老年代
- 空间担保机制: (暂时这样写,之后可能要改和完善),如果超过了整个年轻代可回收的数据量,则全部会直接进入老年代,如果老年代也满了则会发生MajorGC或者OOM
- 新生代回收后的晋升机制:
偏门问题:方发栈中的对象会被回收吗? —— 当出栈以后,局部变量就直接从内存中清理掉了
关于参数设置:
-
如何设置?
- 开发环境,IDEA中有一个Debug Configuration,设置好参数启动就好。
- 线上,如果是jar包启动,那就java -jar 带上参数即可。
- 线上,如果是SpringBoot启动
-
关键参数:
- Xms(初始堆内存大小)、Xmx(最大堆内存大小)【这两个一般设为同样大小】
- Xmn(年轻代大小)
- Xss(栈大小)
- MetaspaceSize(初始元空间大小)、MaxMetaspaceSize(最大元空间大小)【这两个一般设为同样大小】
-
广义上来说:一般是系统内存的一半分配给JVM(因为操作系统要用),比如8G的话,可以3G分给堆,2G分给年轻代,256M分给元空间(因为元空间存的大多是Class类信息),1M分给栈【一般元空间和栈是不会特别去预估设置的,在这个范围即可】
-
狭义上来说: 这个要具体根据场景来定义的,比如算出高峰期、低峰期,然后每秒大概用多少内存,然后放大多少倍估算等,但是这个据我了解一般大公司都有固定的模板,在这个模板的基础上进行内存的设置和调优,这个我确实没具体的经验。
题外话: 建议不要手动触发,依托合理的内存设置以及参数优化,让系统自行运转(默认自行调节)
关于STW(Stop the World)
- JVM在回收的时候不能再写入对象,这个就是Stop the World,虽然都有STW,但是不同垃圾回收器对STW的影响不同
关于GC的选择:
- Serial和Serial Old:分别对应新生代和老年代,单线程的运行,回收的时候会阻塞系统的其他线程,现在Java系统功能几乎不用。
- ParNew(新生代,多线程,标记复制) + CMS(老年代,多线程,且可和工作线程尽量同时工作处理,标记清除),这一套几乎是现在线上生产系统的标配组合。
- G1(整体标记整理。局部标记复制) —— 可统一手机新生代和老年代,不再具体区分这两代,而是动态的区分和设置。
JVM中有哪些垃圾回收算法,每个算法各自的优劣?
- 标记复制 —— 当S1满了以后会做清除,然后存活的10%对象复制到新的S2中去(但是内部命名还是把这个新的位置改为S1了),所以S1/S2中永远有一块是空的也就是10%的空闲位置。
- 因为在年轻代中,Eden和S1、S2的分配比例为8:1:1,因为很多对象都是朝生夕死的,我看过的专栏文章上说大概率超过1毫秒就没有引用了,最终存活的对象只有10%,所以这样分配,保证有Eden+S1,这个标记复制算法的最大好处是 —— 有90%的空间是被使用的,而10%的空间用来做空闲转换空间
- 年轻代发生该算法的时机 —— Eden和S1都满了
- 标记整理
- 在完成标记-清除的算法基础上,将所有存活的对象向一端移动,这样不会有空间碎片(一根指针区分开已用和未用内存,作为指针碰撞 [ 新来对象,则向未用方向分配新来对象的内存个大小数 ] 的内存分配手段)
- 老年代发生该算法的时机 —— 1.年轻代满了直接晋升老年代时判断是否要MajorGC。2.老年代确实满了发生MajorGC
- 标记清除
- 将所以可回收的对象标记,然后统一清除。清除的空间会被空闲空间表维护,作为内存分配的手段(最基础法,后面算法都是对其的改进)
以上是关于[ JVM ] 螺丝刀学习笔记之 —— 重学JVM概览的主要内容,如果未能解决你的问题,请参考以下文章