JVM入门到精通详解

Class文件结构
  • 视图文件结构-jclasslib
    在这里插入图片描述

  • Magic Number:【CA FE BA BE】 java文件

  • Minor_Version:小版本号

  • Major_Version:大版本号

  • constant_pool_count:常量池数量

  • constant_pool:常量池表 (constant_pool_count-1)

  • access_flags:类的访问权限

  • this_class:当前类的名称

  • super_class:父类的名称

  • interface_count:接口的数量

  • interfaces:接口名称表

  • fields_count:字段数量

  • fields:字段表

  • methods_count:方法数量

  • methods:方法表

  • attributes_count:属性数量

  • attributes:属性表

JVM内存结构

在这里插入图片描述

类加载子系统
  • Loading 加载阶段

    • 1、类加载器

      • bootstrap :启动类加载器,加载 $JAVA_HOME/jre/lib/*.jar核心类库;
      • extension:标准扩展类加载器,加载$JAVA_HOME/jre/lib/ext/*.jar或者-Djava.ext.dirs指定;
      • application:应用程序(系统类)加载器,加载ClassPath中指定的jar包以及目录中的class;
      • custom:自定义类加载器,继承ClassLoader抽象类,重写findClass方法,如果重写loadClass可以打破双亲委派机制;
    • 2、双亲委派机制
      在这里插入图片描述

      • 防止重复加载同一个.class。通过委托上一层检查,加载过了,就不用再加载一遍。保证数据安全。
      • 保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。
    • 3、 解释模式(-Xint)、编译模式(-Xcomp)、混合模式(-Xmixed,热点代码检测(HotSpot),默认-XX:CompileThreshold=10000)

  • Linking 连接阶段

    • 1、verification 验证:验证文件是否符合JVM规范
      • 文件格式验证、元数据验证、字节码验证、符号引用验证
    • 2、preparation 准备:为类的静态变量分配内存,并将其赋默认值
    • 3、resolution 解析:将类、方法、属性的符号引用替换为直接引用,常量池中各种符号引用解析为指针、偏移量等内存地址的直接引用
  • Initializing 初始化

    • 静态成员变量赋初始值。
    • 当有继承关系时,先初始化父类再初始化子类,所以创建一个子类时其实内存中存在两个对象实例。
    • 当有成员变量时,new创建对象时,申请内存空间,成员变量赋默认值,调用构造器后成员变量赋初始值。
运行时数据区
  • 程序计数器(Program Counter Register )

    程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。由于 Java 虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为「线程私有」的内存。

    • 如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址
    • 如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)
  • 虚拟机栈(JAVA Virtual Machine Stack)

    • 与程序计数器一样,Java 虚拟机栈也是线程私有的,它的生命周期与线程相同。
    • 虚拟机栈描述的是 Java 方法执行的线程内存模型:每个方法被执行的时候,Java 虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
    • 局部变量表存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身)和 returnAddress 类型(指向了一条字节码指令的地址)。
    • 异常情况
      如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;
      如果 Java 虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出 OutOfMemoryError 异常。
    • Demo
      在这里插入图片描述
      • 指令在这里插入图片描述
      • bipush 8:把8压入操作数栈(Operand Stack);
      • istore_1:出栈,把栈中8这个值赋值给局部变量(Local Variable Table)表位置是1的变量i,变量等于8;
      • iload_1:把局部变量表(Local Variable Table)中位置是1的那个变量,压入操作数栈(Operand Stank);
      • iinc 1 by 1:把局部变量表中(Loacal Variable Table)位置是1的那个变量加1,变量等于9;
      • istore_1:出栈,把栈中8这个值赋值给局部变量(Local Variable Table)表位置是1的变量i,变量等于8;
      • return:返回
  • 本地方法栈(Native Method Stacks)
    本地方法栈与虚拟机栈所发挥的作用是非常相似的,虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务。本地方法栈则是为虚拟机使用到的本地(Native)方法服务。本地方法可以通过本地方法接口来访问虚拟机内部的运行时数据区。

    异常情况与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出 StackOverflowError 和 OutOfMemoryError 异常。

  • 堆(Heap)
    堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,java中所有的对象实例都在这里分配内存。

    • 异常情况
      如果在 Java 堆中没有内存完成实例分配,并且堆也无法再扩展时,Java 虚拟机将会抛出 OutOfMemoryError 异常。
  • 方法区(Method Area)
    方法区与堆一样,是各个线程共享的内存区域,它用于存储已JVM加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

    • Perm Space (<1.8)
      字符串常量位于PermSpace
      FGC不会清理
      大小启动的时候指定,不能变
    • Meta Space (>=1.8)
      字符串常量位于堆
      会触发FGC清理
      不设定的话,最大就是物理内存
    • 运行时常量池
      运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
    • 异常情况
      如果方法区无法满足新的内存分配需求时,将抛出 OutOfMemoryError 异常。
执行引擎

执行引擎是Java虚拟机核心的组成部分,它是用于负责装载字节码到其内部,但是字节码并不能直接在操作系统上运行,那么执行引擎就是将字节码指令解释/编译为对应平台上的本地机器指令。简单来说,JVM执行引擎充当了将高级语言翻译为机器语言的翻译者。

  • 解释器
    当Java虚拟机启动时会根据预定义对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“解释”为对应平台的本地机器指令执行。响应速度快,但是总体效率较低。
  • JIT编译器
    虚拟机将源代码直接编译成本地机器平台相关的机器语言。寻找热点代码(就是高频执行的代码)将其放入元空间中,也就是元空间中存放的JIT缓存代码。响应速度稍慢,但是总体效率很高。有寻找热点代码的特性。
    HotSpot虚拟机内置了两个即时编译器,分别称为Client Compiler和Server Compiler。

Java语言是半编译半解释型语言。不要理解成是先编译后解释,是Java在执行Java代码时,HotSpot虚拟机默认是解释执行与编译执行二者结合起来进行的。
在这里插入图片描述
默认就是使用的Service模式、JIT编译和解释器执行混合模式(这样的模式执行效率最高)。
方法计数器,默认阀值,在Client模式下是1500次,Server是10000次,可以通过参数“-XX:CompileThreshold”来设定。

本地方法接口

一个Native Method就是Java调用非Java代码的接口(Java Native Interface),在定义一个native method的时候,并不提供实现体,其实现体是由非Java语言在外面实现的。

CPU内存结构图

在这里插入图片描述

缓存一致性协议

CPU中每个缓存行(caceh line)使用4种状态进行标记(使用额外的两位(bit)表示):

  • M: 被修改(Modified)
    该缓存行只被缓存在该CPU的缓存中,并且是被修改过的(dirty),即与主存中的数据不一致,该缓存行中的内存需要在未来的某个时间点(允许其它CPU读取请主存中相应内存之前)写回(write back)主存。
    当被写回主存之后,该缓存行的状态会变成独享(exclusive)状态。

  • E: 独享的(Exclusive)
    该缓存行只被缓存在该CPU的缓存中,它是未被修改过的(clean),与主存中数据一致。该状态可以在任何时刻当有其它CPU读取该内存时变成共享状态(shared)。

    同样地,当CPU修改该缓存行中内容时,该状态可以变成Modified状态。

  • S:共享的(Shared)
    该状态意味着该缓存行可能被多个CPU缓存,并且各个缓存中的数据与主存数据一致(clean),当有一个CPU修改该缓存行中数据,其它CPU中该缓存行可以被作废(变成无效状态(Invalid))。

  • I: 无效的(Invalid)
    该缓存是无效的(可能有其它CPU修改了该缓存行)。

乱序问题

CPU为了提高指令执行效率,会在一条指令执行过程中(比如去内存读数据),去同时执行另一条指令,前提是,两条指令没有依赖关系,乱序执行。

内存屏障
  • 1、硬件内存屏障 X86

    sfence: store| 在sfence指令前的写操作,必须在sfence指令后,写操作前完成。

    lfence:load | 在lfence指令前的读操作,必须在lfence指令后,读操作前完成。

    mfence:store/load | 在mfence指令前的读写操作,必须在mfence指令后的读写操作前完成。

  • 2、原子指令,如x86上的”lock …” 指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序。

  • 3、JVM级别如何规范(JSR133)

    • LoadLoad屏障:
      对于这样的语句Load1; LoadLoad; Load2,
      在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

    • StoreStore屏障:
      对于这样的语句Store1; StoreStore; Store2,
      在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

    • LoadStore屏障:
      对于这样的语句Load1; LoadStore; Store2,
      在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

    • StoreLoad屏障:
      对于这样的语句Store1; StoreLoad; Load2,
      在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

伪共享问题

读取缓存以cache line为基本单位,目前64bytes
位于同一缓存行的两个不同数据,被两个不同CPU锁定,产生互相影响的伪共享问题,使用缓存行的对齐能够提高效率。

Java内存模型

java内存模型 - 同步操作与规则
在这里插入图片描述

Java内存模型-同步八种操作

  • lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态
  • read(读取) : 作用于主内存的变量 , 把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入):作用域工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量
  • use (使用) :作用于工作内存的变量 , 把工作内存中的一个变量值传递给执行引擎use (使用) : 作用于工作内存的变量 , 把工作内存中的一个变量值传递给执行引擎
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量
  • store (存储):作用于工作内存的变量 , 把工作内存中的一个变量的值传送到主内存中 , 以便随后的write的操作
  • write (写入): 作用于主内存的变量, 它把store操作从工作内存中一个变量的值传送到主内存的变量中
  • unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来 , 释放后的变量才可以被其他线程锁定

Java内存模型-同步规则

  • 如果要把一个变量从主内存中复制到工作内存, 就需要按顺序地执 行read和load操作 , 如果把变量从工作内存中同步回主内存中, 就要按顺序地执行store和write操作. 但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行
  • 不允许read和load、 store和write操作之一单独出现
  • 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中
  • 一个新的变量只能在主内存中诞生, 不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。 即就是对一个变量实施use和store操作之前 , 必须先执行过了assign和load操作
  • 一个变量在同一时刻只允许一条线程对 其进行lock操作 , 但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值, 在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
  • 如果一个变量事先没有被lock操作锁定 , 则不允许 对它执行unlock操作 ; 也不允许去unlock一个被其他线程锁定的变量
  • 对一个变量执行unlock操作之前 , 必须先把此变量同步到主内存中(执行store和write操作)
对象内存中的布局
  • 对象的创建过程

    • loading 加载
    • linking 链接 (verification验证、preparation准备、resolution解析)
    • initializing 初始化
    • 申请对象内存,成员变量赋默认值
    • 成员变量初始化,实例代码块初始化,构造方法执行,优先调用父类构造器
  • 虚拟机配置
    java -XX:+PrintCommandLineFlags -version
    在这里插入图片描述

    • -XX:InitialHeapSize=132046336(初始堆大小)
    • -XX:MaxHeapSize=2124486656 (最大堆大小)
    • -XX:+UseCompressedClassPointers class指针
    • -XX:+UseCompressedOops 普通对象指针
    • -XX:-UseLargePagesIndividualAllocation
    • -XX:+UseParallelGC
  • 普通对象

    • 对象头:MarkWord,8个字节

    • ClassPointer:-XX:+UseCompressedClassPointers 指针开启压缩4个字节,不开启为8字节

    • 实例数据 instance:

      • -XX:+UseCompressedOops【 Oops:Ordinary Object Pointers普通对象指针】:指针开启压缩4个字节,不开启为8字节
    • padding:对齐,8的倍数

  • 对象头MarkWrod
    在这里插入图片描述

    • lock:无锁(01)->偏向锁(01)–>轻量级锁(00)–>重量级锁(10)–>GC标记(11)
    • biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
    • age:4位的Java对象年龄,最大值15。
    • identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。
    • thread:持有偏向锁的线程ID。
    • epoch:偏向时间戳。
    • ptr_to_lock_record:指向栈中锁记录的指针,62,表示指向轻量级锁。
    • ptr_to_heavyweight_monitor:指向管程Monitor的指针,62,表示指向重量级锁。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java从入门精通需要学习的内容很多,包括Java基础知识,数据结构,算法,Java集合框架,Java8新特性,操作系统,网络原理,Java多线程及并发编程,Java的IO模型及网络编程,JVM,Mysql,JavaWeb等等。建议按照以下步骤学习Java: 1. 从Java基础知识开始学习,包括语法、变量、运算符、控制流等等。可以通过教材、在线教程或者视频课程学习。 2. 掌握数据结构和算法,这是编程的基础。了解常见的数据结构如数组、链表、栈、队列、树等,以及常见的算法如排序、查找、递归等。 3. 学习Java集合框架,包括List、Set、Map等常用的数据结构。了解它们的特点、用法和性能。 4. 深入学习Java的高级特性,如Java8新特性、Lambda表达式、函数式编程等。这些特性可以提高代码的简洁性和可读性。 5. 学习操作系统和网络原理,了解计算机系统的运行原理和网络通信的基本知识。 6. 掌握Java多线程和并发编程,这是开发高性能应用的基础。 7. 学习Java的IO模型和网络编程,掌握如何进行文件读写和网络通信。 8. 深入了解JVM,包括内存管理、垃圾回收等。这对于优化性能和解决内存泄漏问题非常重要。 9. 学习数据库Mysql,了解数据库的基本概念、SQL语言和常见的数据库操作。 10. 学习JavaWeb开发,包括Servlet、JSP、MVC框架等。掌握常见的Web开发技术和框架。 11. 可以通过阅读书籍、参加培训班、实践项目等方式加深对Java的理解和应用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值