JVM结构与内存模型

JVM结构与内存模型

众所周知JAVA是跨平台的语言咱们通过JAVA C命令编译成的.class文件,一次编译导出运行,首先运行需要什么,我们的JAVA环境,JAVA虚拟机中(JVM),因为JVM调用底层C或者C++进行运行,不同的操作系统有不同的虚拟机,Windows系统有windows版本的,Linux有Linux的版本的底层实现不同,所以依赖不同的版本的虚拟机,.class一次编译到处运行


提示:下面的环节可能有点绕我尽可能的说清楚先不细说 后面慢慢补充


前言

接下来真正的学习,与我一块学习,加油!!

一、为什么会一次编译到处使用?

话不多说看图:
在这里插入图片描述
慢慢坐下聊聊 :
我擦!这是什么 一眼就看出来了是不是兄弟们 这不是流程吗?
在这里插入图片描述
看class 二进制文件 哇!这是什么,看不懂吧 往下看 慢慢解释 是不是很牛逼的样子,其实二进制文件都有一定的规律的,看开头的CAFEBABE(魔术数字) 这是我有道翻译的魔术数字,由于上一章讲的loadClass的类加载过程 检查,检查的就是格式每个.class文件的口头都会有 CAFEBABE,接下来我为大家一一读取,先简单的读取,往下看
在这里插入图片描述

从java文件通过编译成.class 用过java 命令运行main方法 在不同的操作系统上都能看到 main方法执行的相同的结果,不急 慢慢往下看 这个我就不演示了

二、JVM结构与内存模型

1.看代码

代码如下(示例):

public class Test {
	public static int i =7;
    public int test(){
       int a=5;
        int b=6;
        int c= a*b+10;
        return c;
    }
    public static void main(String[] args) {
        Test test=new Test();
        test.test();
    }
}

肯定又很多疑问 这是干啥 !这个类就是上面的字节码接下来教大家如何读与JVM的结构线留个悬念 接下来我用

javap -c Test.class  

这是对代码进行反汇编是不是 很熟悉 能看懂一点,这个需要依赖JVM指令手册,接下来根据代码加上反汇编加上二进制 咱们来解析
指令手册

Compiled from "Test.java"
public class com.qjc.construction.Test {
  public com.qjc.construction.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int test();
    Code:
       0: iconst_5
       1: istore_1
       2: bipush        6
       4: istore_2
       5: iload_1
       6: iload_2
       7: imul
       8: bipush        10
      10: iadd
      11: istore_3
      12: iload_3
      13: ireturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/qjc/construction/Test
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method test:()I
      12: pop
      13: return
}

下图:这个图我借用了帅气诸葛老师的图
在这里插入图片描述
接下来开始讲解程序执行的顺序: 很多概念很抽象,所以不先说,先串起来再说,然后在慢慢讲!
Test.class 通过 类装载子系统 C++装载到java虚拟机内存中,类的结构信息只有一份在Java虚拟机中,也就是类的字节码对象在虚拟机中只有一份,对象可以有多份。然后通过字节码执行引擎c++进行执行,最核心的是运行时数据区(内存模型)。new的对象放在里,也可以放在上,这块就先不解释因为有点复杂先不说,栈上面的内存区域用来放数据的,存放局部变量,就是上面的test()方法内的局部变量,每个线程都在栈上开辟一块空间会存放自己的局部变量。
栈(线程栈)(虚拟机栈):
先从栈讲起,栈执行main主线程方法,在线程栈里开辟一块空间,叫栈帧(每个线程都会开辟一块空间!栈内存小),栈帧:每个被调用的方法main()方法栈帧,test方法()栈帧都会在继续分配一块栈帧内存空间,方法里有很多局部变量main方法里Test test=new Test();,
test()方法
int a=5;
int b=6;
int c= a*b+10;
这个就是数据结构的栈,栈帧内部除了局部变量,还有很多例如: 局部变量表,操作数栈,动态链接,方法出口 test()有这些,main() 也有这些
局部变量表与操作数栈:这里要引用上面的字节码,与JVM汇编指令加类,结合着看,上面的二进制字节码他两个有关系,机器识别的是字节码文件,JVM汇编JVM会执行这个汇编指令,这个东西就是JVM的汇编语言,JVM也是一个操作系统,oracle官网下载JVM指令,或者请看下一篇JVM指令手册,我们看test()方法JVM汇编类,根据 iconst_5 这个是什么意思那?打开指令手册搜索iconst_5 将int类型常量5压入操作数栈,istore_1 将int类型值存入局部变量1,这个istore_1指的局部变量1是什么意思?你可以把他当做索引a下标为1的,将操作数栈的弹出给局部变量a,变成了 a=5在局部变量表,
bipush 6 将一个8位带符号整数压入栈 就是将6押入操作数栈,istore_2将int类型值存入局部变量2,将操作数栈的弹出给局部变量b,变成了 b=6在局部变量表。这里说一下程序计数器 是每一个线程独有的内存空间,是用来记录执行的行号我们看Code:对应的列这是行号,我们执行到数字4 这个是变动的,我们每执行一行就会变动 ,通过字节码执行引擎调用 方法区(元空间)Test.class,是由字节码执行引擎每执行一行代码程序计数器就会变动,为什么会有程序计数器那?例如另一个线程线程过来优先级更高,这个线程把他抢走了,那我们这个线程就会挂起!等优先级高的执行完毕之后CPU会让线程继续执行我们的程序,不可能在重新执行一遍,通过程序计数器,找到上次执行的位置。
(补充一下0 1 2 4 为什么是这样的, 请看 bipush 6 这个6也只占内存的 所以是这样的)
iload_1 从局部变量1中装载int类型值,将a=5押入到操作数栈,iload_2 从局部变量2中装载int类型值,将b=6押入到操作数栈,imul 执行int类型的乘法,这块的话利用了CPU的寄存器进行去做计算,我们看到的也就是JVM的内存上面的那个图,也就是30,bipush 10将一个8位带符号整数压入栈10,iadd 执行int类型的加法,也就是30+10,istore_3 将int类型值存入局部变量3,也就是c=40局部变量表,40出栈赋值给局部变量表c,ireturn 从方法中返回int类型的数据。这样看懂了吗? 没看懂不要急,到时候单独讲,老师说的好,咱们先学一个过程,等学完在去抠细节!
**动态链接:**咱们的方法都在方法区,test.test()调用,他有哪些指令,他就是一些符号,他是在程序运行时解析的一些代码,把符号引用转换成直接地址,在内存中!听着是不是很懵逼,先听个大概有很多C++的概念
方法出口: 这个test方法执行完毕还要会回到main方法里面继续执行的main()栈帧

堆:
我们new的对象都在堆里,main方法内的局部变量test 的在堆里面的对象的位置内存地址,指针指向堆内存的对象。所以说栈指向堆,堆里面存着指定堆的地址…
1.年轻代:年轻代里面包含Eden区域Survivor区占用堆的1/3 对象都是分配到Eden区,如果Eden区被放满就会产生字节码执行引擎调动minor gc,就是开启一个垃圾收集线程,如果放满了,他会怎么办? 他会触发垃圾收集,就是回收无用的对象,怎么叫无用对象,就会向线程栈中找局部变量 ,就会找对象的引用,直到找到没有被引用的,引用的会被标记为引用对象,没有没对象引用的就会被gc回收,被引用的对象,经过一次gc就会赋值到survivor区他的分代年龄会+1分代年龄会放在对象头里面(对象头先不讲) ,如果Eden区有被放满了,又会执行如上步骤,被回收,分代年龄+1移动到suvivor区例如又放满了,这两个区还是被回收,分代年龄加1,分代年龄15就会被挪到老年代里面,一般都是15也可以被设置。也可以用java的VisualVM 自己写个死循环创建对象 查看就知道了
2.老年代与GC:占用2/3 分代年龄达到15就给老年代,老年代如果被放满了会出现什么情况?先会触发full GC都会回收 Eden区survivor区老年代都会回收,如果都被引用还继续放,就会产生OOM内存溢出,在执行GC的时候会执行STW,什么是STW,STW就是STOP THE WORLD停止世界,就是停止所有线程,听起来那么狠其实暂停的是用户线程,用户线程,字节码执行器开启的是后台线程,什么是用户线程,用户线程就是我们的用户一个个请求进来就会开启一个线程,他会暂停这些线程,导致用户访问会有卡顿情况,这个STW会影响用户体验的,FULL GC回收时间较长 JVM调优就是优化GC的执行时间长度,与频率。先这么理解JVM调优,STW可以被取消吗?取消让用户不断的访问不好吗?我们在GC的过程中找非垃圾对象,线程会继续执行,线程执行结束局部变量就会被释放,被释放指针就没有了,还没释放,指针就没有了,最终总不能在回头重新遍历加载一遍几百万个对象咋办,STOP THE WORLD,赋值Survivor区 STW机制所以为了不断的GC 为什么说频率了时间长度了吧!!!

方法区(元空间):

javap -v Test.class 打印出很详细的,常量池:但是太复杂了回头我理解透了在讲! 常量+静态变量+类信息:
1.静态变量: public static int i =7; 存在方法区例如说public static User u=new User(),User u是在方法区的,但是咱们new
出来的对象在哪里,在堆里,这两个关系又是指针引用关系,方法区放的是什么?那就是堆内存对象的地址!这就是方法区和堆内存的地址
2.类信息: 每个类在加载(将类读到内存)时都会创建一个字节码对象,且这个对象在一个JVM内存中是唯一的.此对象中存储的是类的结构信息:这里还会细讲
单独抽出来讲 对象头对象信息

在这里插入图片描述
方法区里面:(元空间)直接内存,也就是物理内存,如果咱们不设置的话,元空间会有动态水平伸缩机制,如果不设置的话,他会认为你这所有的物力内存他都会调用,每次回收,达到21M没有回收,他会扩容扩大到30或者40M这个是不确定的 ,如果不设置的话默认21M放满就会创建fullGC 然后就会卡,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般我会将这两个值都设置为256M。这就是一块调优!!!

本地方法栈: 这个就是调用本地方法去实现的底层是由c++去实现的, new Thread().start();
private native void start0(); 这个方法就是C语言实现的,调用本地机器的C语言实现的,就会找对应的实现JVM.dll文件在windows,跨语言调用不同的底层实现,本地方法栈也是需要内存的,每个线程都会有自己的程序计数器,我们的紫色区域,是每一个线程都会有的

2. 运行时数据区JVM参数设置

在这里插入图片描述
看到了如上的参数了没有这些都是可以分配大小的,主要就这三个区域
-Xss:每个线程的栈大小 给一个线程用的一个线程会在线程栈里面分配栈帧的开辟一块空间存放空间参考上面的栈帧,例如递归反复的调用,mian方法调用test(),test调用test()都会调用开辟空间,就会出现栈溢出Stack,大小默认1M但是,512K或者1M差不多最合适了 ,-Xss设置越小count值越小,说明一个线程栈里能分配的栈帧就越少,但是对JVM整体来说能开启的线程数会更多,是根据操作系统也有关系,所以很模糊的,xss设置的越小能开启的线程越多
-Xms:初始堆大小,默认物理内存的1/64
-Xmx:最大堆大小,默认物理内存的1/4
-Xmn:新生代大小
-XX:NewSize:设置新生代初始大小
-XX:NewRatio:默认2表示新生代占年老代的1/2,占整个堆内存的1/3。
-XX:SurvivorRatio:默认8表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存。
关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N
-XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。
-XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M左右,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。这个跟早期jdk版本的-XX:PermSize参数意思不一样,-XX:PermSize代表永久代的初始容量。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值