[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概览的主要内容,如果未能解决你的问题,请参考以下文章

[JVM] 螺丝刀学习笔记之 —— 重学JVM概览

[ ElasticSearch ] 螺丝刀学习笔记之 —— ElasticSearch(7.0UP学习概览)

[ ElasticSearch ] 螺丝刀学习笔记之 —— ElasticSearch(7.0UP学习概览)

[ ElasticSearch ] 螺丝刀学习笔记之 —— ElasticSearch(7.0UP学习概览)

java之JVM学习--简单理解编译和运行的过程之概览

大数据技术之_30_JVM学习_01_JVM 位置+JVM 体系结构概览+堆体系结构概述+堆参数调优入门+JVM 的配置和优化+Tomcat 的配置和优化