JVM内存模型

VIP01

一.javac编译    java运行(idea的项目运行其实也是执行的这两步)

第一步:编写代码

public class Math {
    public static final int initData = 666;

    public int comput(){
        int a = 1;
        int b = 2;
        int c =(a + b)*10;
        return c;
    }

    public static void main(String[] args) {
        Math math = new Math();
        math.comput();
        System.out.println("学习jvm");
    }
}

第二步:根目录下执行javac,编译生成.class文件

 E:\项目demo\SpringAiToolDemo\src\main\java\com\example\tuling\jvm> javac Math.java

 E:\项目demo\SpringAiToolDemo\src\main\java\com\example\tuling\jvm> javap -v Math
 

第三步:退回上级,执行java命令,运行

E:\项目demo\SpringAiToolDemo\src\main\java> java com.example.tuling.jvm.Math

查看字节码: E:\项目demo\SpringAiToolDemo\src\main\java\com\example\tuling\jvm> javap -c Math
javac 就是一个编译器,它的工作就是将Java源代码编译成JVM字节码。而后续的 java 命令,则是启动JVM,由JVM的类加载器读取这些 .class 文件,并由解释器即时编译器将其转换成本地机器码来执行。

个人理解:jvm是解释器风格架构,他就是个解释器,他读取编译后的字节码集,然后解释并且执行;;;

二. .java到.class的过程

词法分析:识别出关键字  关键字 标识符 运算符等等

语法分析:词法分析产生的标记流构建成一颗抽象语法书(AST)

语义分析:对AST进行上下文相关性质的审查,确保代码言之有理

字节码生成:遍历AST,生成二进制的class文件

步骤

输入

输出

核心任务

词法分析

字符流

标记流

拆解单词,识别关键字

语法分析

标记流

抽象语法树(AST)

检查语法结构是否正确

语义分析

AST

装饰后的AST

检查逻辑含义(类型、作用域)

字节码生成

装饰后的AST

.class 文件

生成JVM可执行的二进制指令

三.类加载的过程

当java命令运行某个类的main函数启动程序时,首先需要通过类加载把主类加载到jvm

加载----

  • 类加载器在类路径中查找 Person.class 文件

  • 通过IO读入字节码文件的二进制数据

  • 在方法区创建类的运行时数据结构(类信息、常量、静态变量等)

  • 在堆内存中创建一个 java.lang.Class 对象,作为访问方法区数据的入口

验证----检查 Person.class 文件是否是一个合法的、未被篡改的字节码文件

准备----为静态变量分配内存并设置默认值

解析----将符号引用转换为直接引用

初始化---

使用----

卸载------

四.类加载器和双亲委派机制

引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如
rt.jar、charsets.jar等
扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR
类包
应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那
些类
自定义加载器:负责加载用户自定义路径下的类包

这里类加载其实就有一个双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再
委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的
类加载路径中查找并载入目标类。
比如我们的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载
器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里找了半天
没找到Math类,则向下退回加载Math类的请求,扩展类加载器收到回复就自己加载,在自己的
类加载路径里找了半天也没找到Math类,又向下退回Math类的加载请求给应用程序类加载器,
应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了就自己加载了。。
双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载

从下到上查询,从上到下加载;

VIP02

一.jvm的内存模型

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html

程序计数器:

线程的执行指针,指向当前线程正在执行的那条 JVM 指令的地址,线程私有

虚拟机栈:

每个线程都有一个虚拟机栈,线程创建的同时会创建,存储栈帧,局部变量,中间结果方法的调用和返回结果,

  • 局部变量表:存放方法内定义的基本数据类型变量、对象引用等。

  • 操作数栈:用于执行计算时的中间结果存储。

  • 方法调用与返回:参与动态链接、方法返回地址的处理等。

操作:push(压入) pop(弹出)

容量与可配性:

  • 固定大小:每个线程的栈在创建时就确定了一个固定容量,之后无法改变。

  • 动态伸缩:栈的容量可以根据程序运行的需要(例如方法调用深度增加)进行动态扩展和收缩。

同时,规范建议 JVM 实现应向开发者提供以下控制参数:

  • -Xss:通常用于设置线程栈的初始大小

  • 对于动态伸缩的栈,还可以设置其最大(-Xmx?)和最小(-Xms?)容量限制。(注意:虽然参数名类似堆内存参数,但栈有自己独立的设置,具体参数因 JVM 实现而异)。

与 Java 虚拟机栈相关的两种经典错误:

  • StackOverflowError(栈溢出错误)

    • 触发条件:当一个线程调用方法过深,所需的栈空间(即不断压入的栈帧)超过了栈的最大容量限制(无论是固定大小的限制还是动态扩展的最大值)时抛出。

    • 常见场景无限递归是最典型的例子。

  • OutOfMemoryError(内存溢出错误)

    • 触发条件:这与内存资源本身有关,分为两种情况:

      1. 在动态扩展栈时,整个系统的内存已经不足,无法满足此次扩展的需求。

      2. 在创建一个新线程时,系统没有足够的内存来为它分配初始的 Java 虚拟机栈

所有线程共享,类的实例和数组内存  空间管理垃圾回收器负责   异常内存溢出

方法区

  1. 线程共享:与堆类似,方法区是所有线程共享的内存区域

  2. 存储内容

    • 运行时常量池(区别于字符串常量池)

    • 类的元数据:字段和方法信息

    • 方法代码:包括普通方法、构造函数和特殊初始化方法的字节码

  3. 逻辑归属:逻辑上属于堆,但物理上可由虚拟机自主决定

  4. 内存管理

    • 可不实现垃圾收集(但现代JVM已实现对方法区的回收,如类的卸载)

    • 内存不需要连续

    • 支持固定大小或动态调整

  5. 异常机制:当类加载、常量池创建等操作需要内存但方法区无法满足时,抛出OutOfMemoryError

技术演进:在JDK 8之前,方法区的实现形式为"永久代";从JDK 8开始,被"元空间"取代,元空间使用本地内存而非虚拟机内存,大大降低了OutOfMemoryError的风险。

运行时常量池

  1. 数据来源:运行时常量池来源于class文件中的constant_pool表,是其在JVM运行时的表现形态

  2. 常量类型

    • 静态常量:编译期确定的数值字面量(如final int x = 10

    • 动态引用:需要在运行时解析的方法引用、字段引用等

  3. 功能定位

    • 类似于传统编程语言的符号表

    • 但比典型符号表功能更强大,包含数据类型更丰富

  4. 内存分配

    • 方法区分配内存

    • 类加载过程中构建完成

  5. 异常机制

    • 当类加载过程中,方法区内存不足以构建常量池时,抛出OutOfMemoryError

    • 这是类加载失败的一种常见原因

三。字符串常量池和运行时常量池

  • 运行时常量池:是 类级别 的,每个被加载的类都有一个,内容来源于 class 文件中的 constant_pool,包含多种类型的符号引用。

  • 字符串常量池:是 JVM 级别 的,全局只有一个,只存储一种特殊的数据:字符串对象的直接引用

vip05

1.垃圾收集算法

标记复制算法  年轻代 minor gc-----------------------------问题浪费空间

内存分两块,通过 可达性分析算法找到存活对象,赋值到另一块,左半块一次性清除

标记清除算法   老年代使用------------------效率低   碎片化

存活对象标记出来   没有存活的全部清除

标记整理算法   --------------清除完  空间完整

标记过程和可达性分析算法一样,然后把存活的对象向一端移动,边界外对非存活对象清除

分代收集理论:年轻代  对象也不多,标记复制        

2.垃圾收集器

serial收集器   串行

停掉用户线程,stw,一个线程垃圾回收

优点简单    新生代:复制算法    老年代:标记整理算法       单核效率比并行快

收集内存区域:几十兆到几百兆               现在很少用

palraller收集器:并行   拍若来哦

停掉用户线程,stw,多个线程垃圾回收,线程数可以通过参数配置,默认和cpu核数相同

新生代:复制算法    老年代:标记整理算法   (JDK8默认的新生代和老年代收集器)

两个G三个G可以,但是stw时间有点长

Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)2-3g。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间 ,用户体验感高

parNew收集器   趴牛

在palraller改的  可以与CMS兼容   使用在年轻代

CMS收集器    只能在老年代(-XX:+UseConcMarkSweepGC(old))

concurrent mark sweep      内存大点

缩短stw时间

初始标记:stw,记录gcroot直接引用对象

并发标记:并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但不需要停掉用户线程,可以与垃圾收集线程一起执行,因为用户程序继续运行,可能导致已经标记过的对象状态改变

重新标记:stw,修复并发标记  状态已经改变的(可能有问题)对象;重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对
象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三色标记里的增量更新算法(见下面详解)做重新标记

并发清理:未标记的对象清除掉   开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理(见下面三色标记算法详解)

并发重置:  重置本次GC过程中的标记数据。

缺点:

  • 用户资源和垃圾收集线程争抢资源,吞吐量低
  • 无法处理浮动垃圾(在并发标记和并发清理阶段又产生垃圾,这种浮动垃圾只能等到下一次gc再清理了);----并发清理,清理没标记的对象,用户线程执行中,产生新的垃圾,这次清理不了,下次会清理
  • 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生,当然通过参数-
  • XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理
  • 执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段会出现,一边回收,系统一边运行,也许没回收完就再次触发full gc,也就是"concurrent mode failure",此时会进入stop the world,用serial old垃圾收集器来回收

cms参数

  • 1. -XX:+UseConcMarkSweepGC:启用cms
  • 2. -XX:ConcGCThreads:并发的GC线程数
  • 3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
  • 4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次
  • 5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)---------------------------------------大对象
  • 6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设
  • 定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
  • 7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引
  • 用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段
  • 8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
  • 9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;

实战

4-5g    ParNew+CMS        超过8===G1

垃圾收集底层算法实现

在并发标记的过程中,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。
这里我们引入“三色标记”来给大家解释下,把Gcroots可达性分析遍历对象过程中遇到的对象, 按照“是否访问过”这个条件标记成以
下三种颜色:
黑色: 表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描
过, 它是安全存活的, 如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。 黑色对象不可能直接(不经过
灰色对象) 指向某个白色对象。
灰色: 表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过。
白色: 表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若
在分析结束的阶段, 仍然是白色的对象, 即代表不可达。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值