JVM体系结构概述
一、类加载器ClassLoader
相当于一个快递员,用来将物理硬盘中的class文件加载进JVM(中的方法区),JAVA自带的类加载器有三个(除了自带的,用户也可以集成ClassLoad抽象类自定义加载器)
1、启动类加载器(Bootstrap)
加载Jdk自带的类时(jre/lib/rt.jar包下的类,常用:Object、String、ArrayList)使用启动类加载器,使用C++编写;
2、扩展类加载器(Extension)
加载扩展包中的类(jre/lib/ext/*.jar),使用扩展类加载器;
3、应用程序类加载器(AppClassLoader)
加载自定义的类,使用应用程序类加载器;
双亲委派机制、沙箱安全机制
当一个类加载器收到类加载请求,它首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,父类再调用自己的父类去完成,每一个层次的类加载器都如此,因此所有的加载请求都会先传送到启动类加载器中,只有父类加载器反馈自己无法完成这个请求的时候(在它加载的路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载(在自己加载不成功时,再继续调子类,最终的子类如果还是加载不成功,则报ClassNotFindException)。
采用双亲委派的作用就是保证了沙箱安全、保证jre运行环境安全,不会被自定义的类所污染,例如自己手写一个java.lang.String类,但是在类加载的过程中,加载的还是rt.jar包中的String对象。
口说无凭,看图:
二、执行引擎 Execution Engine
执行引擎负责解释命令,提交操作系统执行。
三、本地方法接口 | 本地方法栈
本地方法:本地方法就是native关键字修饰的方法,因为方法是c语言描述的,所以方法体中没有。例如Thread类中的start()方法中的start0()方法。
本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,因为JAVA诞生的时候是C/C++横行的时候,想要立足必须有调用C/C++的程序,于是就在内存中开辟了一块区域处理标记为natative的代码。 它的具体做法是在本地方法栈(Native Method Stack)中登记native方法,在执行引擎(Execution Engine)执行的时候加载本地方法库(native libraies)。
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域很发达,比如可以使用Socket通信,也可以使用Web Service等等。
四、程序计数器(PC寄存器)
程序计数器线程独有的, 可以把它看作是当前线程执行的字节码的行号指示器,比如如下字节码内容,在每个字节码`前面都有一个数字(行号),我们可以认为它就是程序计数器存储的内容。
那么记录这些数字(指令地址)有啥用呢,我们知道 Java 虚拟机的多线程是通过线程轮流切换并分配处理器的时间来完成的,在任何一个时刻,一个处理器只会执行一个线程,如果这个线程被分配的时间片执行完了(线程被挂起),处理器会切换到另外一个线程执行,当下次轮到执行被挂起的线程(唤醒线程)时,怎么知道上次执行到哪了呢,通过记录在程序计数器中的行号指示器即可知道,所以程序计数器的主要作用是记录线程运行时的状态,方便线程被唤醒时能从上一次被挂起时的状态继续执行,需要注意的是,程序计数器是唯一一个在 Java 虚拟机规范中没有规定任何 OOM 情况的区域,所以这块区域不需要进行 GC。
五、方法区
供各线程共享的运行时内存区域。它存储了每一个类的结构信息,例如运行时常量池、字段和方法数据、构造函数和普通方法。它起到一个“模板”的作用,用于在堆中new一个对象的时候,根据方法区中的类来new。
方法区是一个规范,不同的虚拟机的实现是不一样的,最典型的就是永久代(jdk7)和元空间(jdk8)。永久代和元空间的区别在于:永久代存在于堆当中,元空间在本地内存当中。他们都不会发生垃圾回收。
六、栈
“栈管运行,堆管存储”。栈,主管Java程序的运行,是在线程创建的时候创建,它的生命周期是跟随线程的生命周期,线程结束栈内存也就释放,对于栈来说,不存在垃圾回收问题,生命周期和线程一致,是线程私有的。栈中保存8中基本类型的变量+对象的引用变量+实例方法。
每一个Java方法存在于栈中就是一个栈帧,那么栈帧中保存的又是什么呢?
局部变量表、操作数栈、动态链接、方法出口等信息。
在方法调用深度太长后,会将栈撑爆,会报StackOverFlowError错误。
七、堆
堆分成新生代和老生代(Java8以前还有个永久代),默认比例为 1 : 2,新生代又分为 Eden 区, from Survivor 区(简称S0),to Survivor 区(简称 S1),三者的比例为 8: 1 : 1,这样就可以根据新老生代的特点选择最合适的垃圾回收算法,我们把新生代发生的 GC 称为 Young GC(也叫 Minor GC),老年代发生的 GC 称为 Old GC(也称为 Full GC)。
堆是JVM中最重要的区域,主要涉及到堆的垃圾回收机制。都给你准备好啦,戳这里:https://blog.csdn.net/weixin_44236420/article/details/104811896
栈、堆、方法区 三者关系:
举例说明:Student s = new Student();
在栈中新建一个s对象,它指向堆当中新开辟的内存空间,该内存空间存放的是根据方法区中的Student.Class作为模板创建的对象。
JVM参数调优
JVM参数分类:
- 标配参数(了解)
- X参数(了解)
- XX参数(掌握)
标配参数
指的是在jdk各个版本之间很稳定,很少有大变化,例如:java -version;java -help;java -showversion。
X参数
指的是jdk解析class文件执行方式,有3种类型可选,-Xint【解释执行】、-Xcomp【第一次使用就编译成本地代码】、-Xmixed【混合模式】
XX参数
1、Bolean类型
用于给JVM 开启/关闭 某属性。
使用方式:-XX:(+/-)[某属性名]
验证是否开启成功:在程序运行的情况下,使用 jps -l 命令查看当前运行的线程及端口号,再使用 jinfo -flag [属性名称][端口号],判断该进程是否开启某属性
返回结果:
-XX:+ [属性名] 表示该属性已开启
-XX:- [属性名] 表示该属性已关闭
2、KV设值类型
用于给某属性设置某值
使用方式:-XX:属性key=属性值value
验证是否设置成功:在程序运行的情况下,使用 jps -l 命令查看当前运行的线程及端口号,再使用 jinfo -flag [属性名称][端口号],查询该进程某属性值
-Xms、-Xmx属于什么类型?
两者也是KV设值类型
-Xms=-XX:InitialHeapSize;
-Xmx=-XX:MaxHeapSize
-Xms: 堆的初始内存大小,默认为物理容量的1/64;
-Xmx: 堆最大内存大小,默认为物理容量的1/4;
-XX:+PrintGCDetails: 打印堆内GC日志,用来查看堆运行状态;
-XX:MaxTenuringThreshold:设置对象在新生代中的移动次数(默认15,达到后进老年代)
其他查看命令
java -XX:+PrintFlagsInitial//查看当前JVM所有初始值
java -XX:+PrintFlagsFinal//查看当前JVM所有修改后(最终)的值
java -XX:+PrintCommandLineFlags -version//打印命令行参数,主要用于查看使用的垃圾收集器
常用修改参数
-Xms4096m =-XX:InitialHeapSize;//设置初始堆大小(默认物理堆1/64)
-Xmx4096m =-XX:MaxHeapSize;//设置最大堆大小(默认物理堆1/4)
-Xss1024k =-XX:ThreadStackSize;//设置单个线程栈大小(linux默认1024k)
-XX:MetaspaceSize=20m;//设置方法区[永久代、元空间]大小(默认20M)
-XX:+PrintGCDetails;//设置 打印GC日志
-XX:MaxDirectMemorySize=10m//设置堆外内存大小
-Xmn; //设置新生代大小(默认Java堆的1/3),这个属性一般不改(如果与-XX:NewRatio属性值冲突,使用的是-Xmn的值)
-XX:SurvivorRatio=8;//设置eden区的新生代占比(默认为8,即eden:S0:S1=8:1:1),这个值一般不改
-XX:NewRatio=2;//设置老生代的堆空间占比(默认为2,即 新生代:老年代=1:2),一般默认
-XX:MaxTenuringThreshold=15;//设置垃圾年龄,多少岁之后从新生代进入老年代(默认15,自定义时不能大于15)
设置案例:
-Xms4096m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandFlags -XX:+PrintGCDetails -XX:+UseSerialGC
Runtime.getRuntime().availableProcessors();//处理器个数
Runtime.getRuntime().maxMemory();//JVM最大内存
Runtime.getRuntime().totalMemory();//JVM初始内存
System.gc();//手动GC,一般不要用,系统会自动GC
正式环境调内存要求:内存初始值和内存最大值尽量保持相同,避免GC争抢内存造成内存忽高忽低。
GC日志解读:
//GC类型
[GC
//YGC 前新生代内存->YGC 后新生代内存(新生代总内存)
[PSYoungGen: 0K->0K(3008K)]
//YGC 前JVM堆内存占用->YGC 后JVM堆内存占用(JVM堆总大小),YGC耗时
735K->735K(9856K), 0.0003054 secs]
//YGC用户耗时 YGC系统耗时 YGC实绩耗时
[Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC
[PSYoungGen: 0K->0K(3008K)]
[PSOldGen: 735K->627K(6848K)] 735K->627K(9856K)
[PSPermGen: 3550K->3541K(21248K)], 0.0111451 secs]
[Times: user=0.02 sys=0.00, real=0.01 secs]