JVM--你想要的都在这里

本文详细解析JVM运行原理,涵盖程序计数器、虚拟机栈、本地方法栈、方法区及堆的职责与管理,介绍对象生命周期、垃圾回收算法与回收器,并探讨内存分配流程与GC线程交互。

一 是什么--功能

1.1 软件层面机器码的翻译--》代码到机器指令的一次转换

1.2 内存管理

image.png

 

二 JVM运行时数据区

image.png     image.png

2.1程序计数器(线程独享:一个线程一个):指向当前线程正在执行的字节码指令的地址,行号(原因:线程执行是在CPU上,CPU是抢占式的,线程有可能没有执行完就被抢夺了,所以需要一个地方去存储线程执行地址)

 

2.2虚拟机栈(线程独享):存储当前线程运行所需要的数据,指令,返回地址。

栈帧存放一个方法的全部信息,一个栈帧一个方法,局部变量存放变量信息,操作数栈存放引用地址,以及需要运算操作的数。动态链接指的是例如@autowired注入service,指向service实例。出口就是返回地址,正常 /异常:走异常处理流程

image.png

 

tip:递归调用会有多少个栈帧存在--只有一个栈帧存在,递归调用栈帧太多,容易导致栈溢出

 

2.3本地方法栈:native方法就是本地方法,使用c/c++语言编写的。

 

2.4 方法区(线程共享):类信息(类的源信息),常量,静态变量,JIT(just in time,即时编译技术,动态生成类)

运行时常量池:存放编译器生成的各种字面量和符号引用。

 

2.5 堆heap(线程共享):
image.png

a.对象的生命周期不一样,所以要分代,新生代和老年代的默认比例是1:2.

 

b.1.8版本之前有永久代,1.8版本之后替换为了meta space,是一个链表,能够动态扩容。但是并不一定就好。因为动态扩容很可能会导致内存溢出。

1.7版本之前,java虚拟机会将存放class和meta的信息存放在方法区,即永久代指的就是方法区

1.7版本讲这些数据放到了堆上。

1.8版本使用了meta space(放在了主存,而不是虚拟机中)存放这些数据

 

为什么会用meta space取代永久代:永久代经常内存溢出,报错java.lang.outofmemoryError:permGen

 

c.新生代区域划分:eden:s0:s1----8:1:1,参数-XX:SurvivorRatio设置

为什么:复制回收算法,8:1:1是因为有90%的空间可利用

复制回收算法是对象先放在eden中,eden不够用了,将新数据放到s0或s1中,eden清空。

 

d.98%的对象会在minor gc的时候被回收掉,如果新生代空间不够,会向老年代分配担保

 

e.major GC(老年代的GC)要比minor GC慢十倍,所以最好在新生代进行回收。

 

f.新生代到老年代的条件

image.png

例外:大对象直接分配到老年代中。

 

g 老年代内存不够怎么办

分配担保:minor GC之前,判断老年代最大可用连续空间是否大于新生代所有对象总空间,如果不满足,判断老年代最大可用连续空间是否大于历代晋升老年代的平均大小,如果不满足或者HandlePromotionFailure设置不允许担保失败,则进行full GC(minor GC + major GC)

 

h.各代大小设置

image.png

 

 

三 什么样的对象需要被GC

1.判断算法:

a.引用计数法:不会去用因为存在相互引用

b.可达性分析:GC Root到对象有没有链接,所以下图:B不能被回收,C可以被回收

 

2.GC Root可以有以下组成:

虚拟机栈中本地变量表引用的对象

方法区中类静态变量引用的对象、常量引用的对象

本地方法栈中JNI引用的对象  

image.png

 

3.不可达进入finalize方法,可以再次挽回(可以用GC Root再指向一下)

 

4.引用--强引用,软引用,弱引用,虚引用

1、强引用:

   java默认的引用就是强引用,尽管jvm内存不足,在gc的时候也不会进行回收,会抛出内存溢出的异常。

   Person p = new Person();就是一个强引用

 

 2、软引用:

   软引用在jvm内存充足的时候,不会被回收,在不充足的时候会被回收,例子如下

   WeakReference<Person> personSoftReference = new WeakReference<Person>(new Person());

 

 3、弱引用:

   弱引用在gc的时候会被回收。

 

 4、虚引用

   虚引用创建后就被回收,感觉没啥子用,回收的时候只会通知一下;

 

四.回收算法--垃圾回收器就是算法的实现

1.标记-清除算法

image.png

缺点:a.标记和清除过程比较复杂  b.内存碎片比较多

 

2.复制回收算法

 

3.标记-整理算法:边标记边整理--没有内存碎片

image.png

 

五 垃圾回收器

image.png

image.png

 

年轻代:

1.serial:单线程

2.parnew:多线程

3.parallel Scavenge(全局):以提高吞吐量为主

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)

image.png

 

老年代:

1.serial old:serial的老年代版本,CMS备用预案,可以和任何新生代回收器配合,concurrent mode failusre时使用标记-整理算法

 

2.parallel old:只能和parallel新生代配合,标记-整理算法

 

3.CMS:标记-减少回收停顿时间,使用标记-清除算法

a.初始标记:找到GC ROOT直接能关联到的对象,

b.并发标记: 继续往下找(GC tracing)

c.重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录

d.并发清理

 

image.png

 

 

老年代和新生代:G1

 

六 堆内存分配流程

1.每个线程在堆上的内存由指针引用,堆上已分配内存区与空闲区会用一个指针隔开,在堆上分配内存就是将指针向空闲区移一个对象大小的区域,这个过程就是指针碰撞。

而指针碰撞的方式不适用于多线程,于是采用freeList--空闲列表方式,将空闲区通过一个列表管理起来--freelist.

具体采用哪种方式要看使用的是哪个垃圾回收器--也就是这款垃圾回收器是否带有compact功能--整理压缩算法,如果是标记-整理算法,那么就可以使用指针碰撞的形式。如果是标记-清除算法,则使用空闲列表方式。

 

 

2.对象一开始一般会在新生代分配内存,如果是多个线程并发,则会通过CAS也就是加锁去分配内存。

而如果线程太多·CAS竞争太激烈,也会消耗CPU。所以JVM提供TLAB,在堆里每个线程都有一个自己的thread local allocation buffer(TLAB).所以线程为对象创建内存,会在thread local allocation buffer分配内存

image.png                                                                                                                                                                                                                                                                                                                                            七.应用线程与GC线程的交互:因为GC是STW

1.安全点:时间周期比较长的点,例如循环跳转,异常跳转,方法调用等。

方式:a.抢占式:发出GC时,查看当前点是否在安全点,如果不在安全点,继续运行,等待安全点

b.主动式:GC运行时,设置一个标志flag,当应用线程走到安全点时,查看flag,如果GC在跑,就停下来,如果不在跑,就继续走。

2.安全区域saferegion,应用线程没有在走,就设置一个saferegion,如果发生GC,是处于sageregion中,则继续GC.  

<think>嗯,用户问的是关于JVM参数-XX:InitialHeapSize的作用和配置方法。这个问题涉及到JVM内存管理的核心知识,需要结合引用内容详细说明。 用户可能正在调优Java应用性能,或者遇到了内存相关的问题。从提问方式看,ta对JVM参数有一定了解但需要具体指导,属于中级开发者水平。需要特别注意引用[1]和[3]都提到了-Xms参数,这其实是关键点。 首先明确概念:InitialHeapSize就是堆内存的初始大小。引用[1]的示例输出显示了这个参数的实际效果,而引用[3]的调优示例用-Xms100m这种更常见的写法。这里要强调两种写法的等价性,因为很多开发者可能不知道-XX:InitialHeapSize和-Xms其实是同一个配置的两种写法。 配置方法需要分三部分说明:命令行配置、默认值规则、关联参数。特别要注意引用[4]提到的MaxDirectMemorySize关系,虽然用户没问但应该提醒避免内存总和超限。另外引用[2]提到的PrintFlagsInitial对验证默认值很有用。 注意事项部分要包含引用[1][2]都强调的版本差异问题,还有引用[4]隐含的生产环境风险。用户可能正在开发环境调试,但必须提前告知生产环境的注意事项。 最后生成的问题要覆盖延伸需求:性能分析(引用[3]的GC日志参数相关)、监控方法(引用[1]的打印参数技巧)、常见错误(结合引用[4]的内存溢出场景)。这样既解答当前问题,又预判了用户后续可能遇到的困难。</think>### JVM `-XX:InitialHeapSize` 参数详解 #### **作用** `-XX:InitialHeapSize` 用于设置 **JVM 堆内存的初始大小**。JVM 启动时,会立即分配此参数指定的内存量作为初始堆空间,避免运行时频繁调整堆大小,从而提升启动性能。若未显式配置,JVM 会根据系统内存自动计算默认值(通常为物理内存的 1/64)[^1][^3]。 #### **配置方法** 1. **命令行直接指定**(单位:字节/B,支持 `k`/`m`/`g` 后缀): ```bash java -XX:InitialHeapSize=512m -jar your_app.jar ``` 2. **等价简写参数 `-Xms`**(更常用): ```bash java -Xms512m -jar your_app.jar # 效果等同 -XX:InitialHeapSize=512m ``` #### **关键规则** - **与 `-Xmx` 的关系**: - 建议将初始堆大小(`-Xms`)和最大堆大小(`-Xmx`)设为相同值,避免运行时堆扩容引发性能波动[^3]。 - 示例:`-Xms2g -Xmx2g` 表示固定分配 2GB 堆内存。 - **默认值依赖**: 未设置时,JVM 基于系统内存自动计算,可通过 `-XX:+PrintFlagsFinal` 查看实际值[^1][^2]。 #### **验证配置生效** 添加 JVM 参数 `-XX:+PrintCommandLineFlags`,启动时输出生效值: ```bash java -XX:InitialHeapSize=512m -XX:+PrintCommandLineFlags -version # 输出示例: # -XX:InitialHeapSize=536870912 // 512MB 的字节值 # ...其他参数... ``` #### **注意事项** 1. **容器环境**: 在 Docker/K8s 中运行时,`-Xms` + `-XX:MaxDirectMemorySize`(堆外内存)的总和不可超过容器内存限制,否则可能触发 OOM Kill[^4]。 2. **生产建议**: - 避免依赖 JVM 默认值,显式配置 `-Xms` 和 `-Xmx` 以稳定性能[^3]。 - 结合 `-XX:+HeapDumpOnOutOfMemoryError` 在 OOM 时生成堆转储文件[^3]。 3. **参数兼容性**: 不同 JVM 版本(如 OpenJDK/OracleJDK)的默认值或行为可能差异,需测试验证[^2]。 --- ### 相关问题 1. **如何监控 JVM 堆内存的实际使用情况?** (涉及工具:`jstat`、GC 日志分析、`-Xlog:gc*` 参数等) 2. **`-Xms` 和 `-Xmx` 设置不一致会导致哪些性能问题?** (堆扩容/收缩的开销、GC 频率变化等) 3. **容器中如何为 JVM 分配合适的内存?** (堆内存、元空间、堆外内存的比例调整与风险规避[^4]) 4. **如何通过 GC 日志优化堆大小配置?** (分析 Full GC 频率、老年代占用等指标[^3]) > 引用来源: > [^1]: JVM 参数打印与实际应用场景说明 > [^2]: JVM 参数查询方法与兼容性注意事项 > [^3]: 生产环境内存调优参数示例与策略 > [^4]: 堆外内存配置与容器环境风险警告
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值