当前位置: 首页 > news >正文

【JavaSE】JVM

文章目录

  • 定义
  • 类加载器
    • 类加载器
    • 双亲委派机制
  • 沙箱安全机制
  • `native`关键字
      • Native Method Stack
      • PC寄存器
  • 方法区
  • 队列
    • 学习思路
    • 功能、特点
    • 栈+堆+方法区的关系:
  • HotSpot和堆
    • HotSpot
  • 新生区、永久区、堆内存调优
    • 新生区
    • 养老区(Old)
    • 永久区(元空间)
      • 永久区历程
      • 什么情况永久区会出问题:
      • 方法区
    • 堆内存调优
      • 查看堆内存
      • 问题及解决方式
  • JPofiler 分析OOM
  • GC垃圾回收
    • 概念
    • 引用计数法
    • 复制算法
    • 标记清除算法
    • 标记压缩算法
    • 总结
  • JMM
    • 什么是JMM?
    • `volatile`关键字

定义

JVM是java虚拟机,体现了java 的运行环境;

整体的一个流程是将java编译成为Class类,加载到类加载器Class Loader,之后在加载到JVM中

流程如下:

画板

类加载器

类加载器

  1. 虚拟机自在的加载器
  2. 启动类(根)加载器
  3. 扩展类加载器
  4. 应用程序(系统)加载器

.getClassLoader:查看加载器

DemoJVM01 demoJVM01 = new DemoJVM01();
DemoJVM01 demoJVM02 = new DemoJVM01();
DemoJVM01 demoJVM03 = new DemoJVM01();System.out.println(demoJVM03.hashCode());
System.out.println(demoJVM02.hashCode());
System.out.println(demoJVM01.hashCode());System.out.println("---end");Class<? extends DemoJVM01> aClass = demoJVM01.getClass();
System.out.println(" Class Loader:"+aClass.getClassLoader());
System.out.println(" Class Loader`s Parent:"+aClass.getClassLoader().getParent());
System.out.println(" Class Loader`s Parent`s Parent:"+aClass.getClassLoader().getParent().getParent()); // rt.jar
 Class Loader:sun.misc.Launcher$AppClassLoader@18b4aac2Class Loader`s Parent:sun.misc.Launcher$ExtClassLoader@3d04a311Class Loader`s Parent`s Parent:null

双亲委派机制

如果在jar包中如果有一模一样名字的方法的情况,新定义的内容不会生效,这称之为双亲委派机制

app >> ext >> rt (Boot)

双亲委派机制:

  1. 类加载器收到类加载的请求;
  2. 将这个请求向上委托 给父类加载器去完成,一直向上委托,直至启动类加载器;
  3. 启动加载器检查 是否能够 加载当前这个类,能加载就结束,使用当前的加载器,否则 抛出异常,通知子加载器进行加载;
  4. 重复步骤3 直至 Class Not Found;

沙箱安全机制

沙箱机制 (sandbox),将java代码限定到虚拟机(JVM)之中,并且严格限制代码对本地系统的资源访问。通过这样的措施来保证代码的有效隔离。

native关键字

private native void start0();,start()调用了start0()方法,使用了native关键字,使用调用了C语言的库,java的功能已经实现不了了。

JNI的作用:扩展Java的使用,融合不同的语言 给Java使用;

  • C+±- → Java
  • C++++ → C#

使用Java驱动本地东西或者调用计算机的时候native 会出现,但大多时候都是使用C++去实现;

Native Method Stack

PC寄存器

程序计数器:Program Counter Register

每一个线程都有一个程序计数器,是线程所私有的,就是一个指针,指向方法区的方法字节码(用于存储指向一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间;

方法区

Method Area, 方法区 是被所有线程共享,所有字段和方法字节码,以及一些特殊的方法:接口,构造函数;

简单的说,所有定义的方法的信息都会保存在该区域,此区间属于共享区间;

静态变量、常量、类信息(构造方法,接口定义)、运行时的常量池存在方法区中,但是 实例变量存在堆内存中, 和方法区无关;

static、final、Class、常量池;

队列

类似于管道流程,

线程的执行就是队列,新进先出(FIFO)原则,

学习思路

程序=数据结构+算法;

程序=框架+业务逻辑~:淘汰!

类似于桶的样态;先进后出(FILO)原则,

main方法会放在最底下,执行的方法在上面执行之后会将执行过的弹出;

画板

如果空间设置不对,会出先栈溢出异常;递归自调会出现这种状态;

递归自调用如果没有终止递归的方法就会出现栈溢出;

存放的是进行引用的地方;

功能、特点

栈内存,主管程序的运行,生命周期和线程同步;

线程结束,占内存就会释放,对于栈来说,不存在垃圾回收问题;当线程结束,站就会结束;

栈中保存着 8大基本类型+对象引用+实例的方法;

栈的运行原理:通过栈帧实现;

栈满了 会出先StackOverflowError 栈溢出错误,

栈+堆+方法区的关系:

画板

HotSpot和堆

HotSpot

查看版本 java -version

C:\Users\杨>java -version
openjdk version "17.0.16" 2025-07-15
OpenJDK Runtime Environment Temurin-17.0.16+8 (build 17.0.16+8)
OpenJDK 64-Bit Server VM Temurin-17.0.16+8 (build 17.0.16+8, mixed mode, sharing)C:\Users\杨>

已经没有HotSpot了;

堆(Heap),真正放置实例内容的地方 就是堆;

类加载器 读取了类的文件后,_常量,类,方法 ,变量 _等都会保存到堆中;

堆内存细分为三个区域

  • 新生区:young
  • 养老区:old
  • 永久区:Perm

GC(垃圾回收)有轻量级和重量级两种GC机制:

  • 轻量级的GC在新生区,在堆容量特别大的情况下会 进行重量级GC;
  • GC的垃圾回收,主要在伊甸园区和养老区;
  • 如果内存特别大的时候,会出现OOM(OutOfMemoryError),堆内存溢出;
  • jdk1.8之后,永久存储区的名字 >> (源空间)

OOM的出现的场景,一直往新生区的加对象 直至加到上限,就会出现这个问题;

动态的不断生成反射类,一直加载直到内存满都会出现OOM;

新生区、永久区、堆内存调优

jdk1.8之后 元空间 和 JVM 分离了;

新生区

存储新创建的对象,大部分对象生命周期较短,通过垃圾回收(GC)快速清理无用对象。

分为伊甸园(Eden区)、幸存者0区(Survivor0)和幸存者1区(Survivor1)。

对象在Eden区创建,经历GC后存活的对象被移至幸存区,多次存活后晋升至老年代(Old Generation)。 ‌

养老区(Old)

在新生区的对象 经过伊甸园区,之后到幸存0区,幸存1区,当新生区全都满了之后,回去执行一次重GC,之后还能存在的情况才会到达养老区;

直至最后到达养老区满了之后会到达顶峰。在增加会出先OOM;

99%的对象到不了永久区,都是临时对象,大部分对象都是临时对象;

永久区(元空间)

永久区 所占用区域是常驻内存中,用来存放jdk携带的Class对象,interface元数据,存储的是Java运行的一些环境;

这个区域不会存在GC,在我们关闭虚拟机的时候,会去释放这个区域的内存;

永久区历程

  • jdk1.6之前:名字称之为永久代,常量池是在方法区之中;
  • jdk1.7:永久代慢慢退化了,将常量池移动到堆中;
  • jdk1.8:彻底去永久代,常量池到元空间中,持久代已经变成元空间了;

存储类元数据(如类定义、常量池)、静态变量等,属于方法区的替代实现。

什么情况永久区会出问题:

  • 启动类启动时加载了大量的外部jar;
  • tomcat在启动时部署了太多的应用;
  • 在程序执行时,编写了大量的反射类,不断的被加载,直到内存满,出现OOM;

方法区

方法区是与所有的内存区共享的,但是存在在堆之中,又称之为非堆

方法区这个存在在元空间之中,在逻辑上我们理解是有的,但是物理上是不占用的;

堆内存调优

查看堆内存

package Jbm;public class DemoJVM01 {public static void main(String[] args) {// 获取JVM内存信息long maxMemory = Runtime.getRuntime().maxMemory();// 获取JVM初始化总内存long totalMemory = Runtime.getRuntime().totalMemory();System.out.println("maxMemory"+maxMemory+"Byte\t"+(maxMemory/1024/1024)+"MB");System.out.println("---");System.out.println("totalMemory"+totalMemory+"Byte\t"+(totalMemory/1024/1024)+"MB");System.out.println("---end");}
}
maxMemory3736076288Byte	3563MB
---
totalMemory253231104Byte	241MB
---end

默认分配的总内存是电脑内存的1/4,初始化的内存是1/64

可以手动配置堆内存扩大;

问题及解决方式

:::info
有碰到过OOM(堆内存溢出)吗?

  1. 配置内存,扩大内存看下显示是否恢复;
  2. 分析内存,查看代码的那个地方出现了问题;

:::

JPofiler 分析OOM

出现OOM怎么办,如何排除?

  • 使用内存分析工具,看第几行代码出错:MAT,Jpofiler;
  • debug,查看代码出错的地方;

MAT、Jprofiler的作用:

  • 分析Dump内存文件,快速定位内存泄漏;
  • 获得堆中的数据;
  • 获得最大的对象;

GC垃圾回收

概念

JVM在进行GC时,并不是对三个区域统一回收,大部分时候,回收都是新生代

  • 新生代;
  • 幸存区:0,1;
  • 老年区

GC的两种:

  • 轻GC :普通GC
  • 重GC:全局GC

GC的题目

  1. JVM的内存模型和分区~ 详细到每个区放什么?
  2. 堆里面的 分区有哪些?伊甸园区 幸存区 form to,老年区,说说他们的特点!
  3. GC算法有哪些?标记清除法,标记压缩法没复制算法,引用计数器,具体怎么用的?
  4. 轻GC和重GC分别是什么时候发生?

引用计数法

将内存中的地址进行标注,标注如果又被引用的继续保留,如果有的地址被引用为0,则进行垃圾回收

复制算法

使用幸存区 0区和1区进行GC;

幸存区的form和to的判断标准:【谁空谁是to】;

流程:

  1. 每次GC之后,都会将伊甸园区的对象移动到幸存区;
  2. 在伊甸园区清空from区,变成to区,
  3. from区和to区来回切换,将两个区互相复制,期间为保持一个干净的空间,from会不断清空切换为to;
  4. 当GC次数到达一定的次数之后,会将对象复制到养老区;

可以使用 -XX: -XX:MaxTenuringThreshoid=9999 调整GC的任期时间,JVM调优的方式;

伊甸园区和幸存from区有内容,移动到To区,之后To是满的,变成From区;

From区将所有的东西copy到to区后,From变成了To;

  • 好处:没有内存碎片
  • 坏处:浪费了内存空间,多了一一般的空间被使用;
  • 在对象存活度较低的情况下,使用复制算法最佳!

标记清除算法

对 用过的对象做个标记;

扫描所有的对象,对对象进行标记;

之后清除 所有的没有标记的对象,进行清除;

  • 优点:不需要额外的空间;
  • 缺点:两次扫描,严重浪费时间,会产生内存碎片;

标记压缩算法

使用压缩,对内存空间再次扫描,向一段 移动存活的对象。

多了一层移动的成本

标记清除算法+标记压缩算法=标记清除压缩算法。

总结

  • 内存效率:复制算法 > 标记清除算法 > 标记压缩算法(时间复杂度)
  • 内存整齐度:复制算法=标记压缩算法 > 标记清除算法
  • 内存利用率:标记压缩算法=标记清除算法 > 复制算法

最优的算法?

只有最合适的算法,没有最好的算法; —> 分代收集算法

年轻代:

  • 存活率底
  • 复制算法√

老年代:

  • 区域大,存活率高
  • 标记清除(内存碎片不是很多)+标记压缩算法

JMM

什么是JMM?

JMM(Java Mermory Model)java内存模型,是java的缓存一致性协议,用于定义读写的规则。

JMM定义了线程的工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,期间的每一个线程都是有个自己的本地内存(Local Memory)。

画板

在JVM中只有一个主内存,线程执行后 会copy出来自己的一个内存区域,这是自己的工作区域;

之后数据会变得不一样,为了解决 数据不一致,共享内存的内容可见性的问题,出现了volatile关键字;

volatile关键字

volatile是Java中保证多线程可见性和禁止指令重排序的关键字‌,主要用于解决共享变量的内存同步问题。

JMM是一种抽象的观念;

http://www.dtcms.com/a/466085.html

相关文章:

  • 部分网站dns解析失败wordpress 图片预加载
  • django 网站开发案例公众号微信
  • 数据库进阶实战:从性能优化到分布式架构的核心突破
  • MySQLEXPLAIN命令详解从执行计划看SQL性能优化
  • leetcode 506 斐波那契数
  • Linux 命令:mount
  • JavaWeb——Servlet生命周期
  • JavaWeb——(web.xml)中的(url-pattern)
  • 企业网站建设合作协议范文天津城市建设大学网站
  • 新专业加速落地!设备采购先行,工业视觉人才培养破局。
  • FastAPI 入门:从环境搭建到实战开发的完整指南
  • Redis的String详解
  • MySQL事务隔离级别详解从读未提交到可串行化
  • 网站域名注册空间app外包
  • 赣州网站推广公司微网站建设是什么
  • 图扑 HT 架构下 AR 应用开发与行业解决方案实现
  • 测试实战心得
  • 网页网站设计价格为什么我做的视频网站播放不了
  • 前端框架深度解析:Vue.js 3 从 Composition API 到生态升级,解锁企业级开发新能力
  • DataX适合全量同步和简单的增量场景
  • 实体门店怎么使用小程序?实体店如何做小程序店铺?
  • 服装公司网站建设方案渭南做网站博创互联
  • 基于GPS/PTP/gPTP的自动驾驶数据同步授时方案
  • 福田网站建设龙岗网站建设龙岗网站建设龙岗网站建设中关村手机之家官网
  • solr负查询失效
  • GSPO如何消除高方差且不依赖routing replay
  • 南宁电子推广网站河南网站建设技术公司
  • 泰安房产网站建设设计网页公司哪家好
  • R语言基础保姆教程01--从工具到数据类型
  • MySQL索引失效揭秘:隐式类型转换的规则与案例!