- 栈管运行,堆管存储
一一对应 1. 一个java进程对应一个jvm实例 2. 一个jvm实例对应一个Runtime类 3. 一个Runtime类对应一个堆空间1. 基本概念
1. Java堆区在Jvm启动后就会被创建,其空间大小就确定了 2. JVM管理的最大的一块内存空间, 空间大小可以调节 3. 堆可以出于物理上不连续的内存空间,但在逻辑上它应该视为连续的 4. 所有的线程共享java堆 细分:(线程私有的缓冲区(Thread Local Allocaation Buffer(TLAB), 堆主存储))2. 核心概念
1. 所有的 对象实例 和 数组 都在堆上分配内存 2. 数组和对象永远不会存储在栈上,因为栈帧中只保存引用,这个引用 指向对象或者数组在堆中的位置 3. 方法结束后,堆中对象不会马上被移除,仅在垃圾收集的时候才会被移除 4. 堆,是GC执行的重点区域3. 内存细分
- 逻辑上分为三部分: 新生代,老年代,元空间
新生代: Young Generation Space(Eden space, Survivor spaca) 老年代: Tenure Generation Space 元数据区:Meta Space二、堆空间大小 1. 基本设置
- Java堆区用于存储Java对象实例,堆的大小在JVM启动的时候就用已经设定好了
# 1. 参数设置 -Xms:256k 1. 表示堆区的起始内存(新生代+老年代) 2. -X: jvm的参数 3. ms: memory start -Xmx: 1028k 1. 表示堆区的最大内存 2. mx:memory max # 2.异常 - 堆区的内存大小超过了-Xmx指定的最大内存时候, 就会抛出 OutOfMemoryError OutOfMemoryError: Java heap space # 3.最佳实践 -Xms和-Xmx配置相同的值? Yes! -Xms1000M -Xmx1000M 能够在java垃圾回收机制处理完堆区后,不需要重新分隔计算堆区的大小,提高性能 (避免不必要的扩缩容,增大系统压力) # 4. 默认情况 初始内存大小: 物理电脑内存大小/64 最大内存大小: 物理电脑内存大小/4
package com.nike.erick.d06;
public class Demo05 {
public static void main(String[] args) {
// 初始内存,
long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
// 最大内存
long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
System.out.println("初始JVM堆内存:" + initialMemory + "M");
System.out.println("最大JVM堆内存:" + maxMemory + "M");
System.out.println("本地物理内存:" + initialMemory * 64 / 1024 + "G");
System.out.println("本地物理内存:" + maxMemory / 1024 * 4 + "G");
}
}
2. 新生代和老年代
1. 存储在JVM中的java对象被划分为两类
1.1 生命周期短的瞬时对象,创建和消亡都很快
1.2 生命周期比较长,极端情况下和JVM的生命周期一样(Runtime类)
2. JVM堆区划分
2.1 年轻代: Eden, Survivor0和Survivor1(from区和to区)
2.2 老年代
2.1 内存大小
- 默认配置
- 一般不会去修改, 如果该项目中的很多的对象时间都很长,则考虑修改
- 大多数的java都是在Eden中被new出来的, 同时也会在Eden中被销毁
1. 新生代和老年代堆中比例 -XX:NewRation=2: 新生代占1,老年代占用2 -XX:NewRation=4: 新生代占1,老年代占用4 # 如何查看参数 jps : 得到端口号 jinfo -flag NewRatio 40402 : 得到比例 2. 新生代老年代按照指定数字分配 -Xmn100M 显示指定,以这个为准 3. 新生代中Eden 和另外两个Survivor默认比例是。 8:1:1 -XX:SurvivorRatio=8 :调整为 8:1:1 # 如何查看 jps : 得到端口号 jinfo -flag SurvivorRatio 40402 : 查看比例2.2 对象分配过程
1. 新对象在Eden创建,如果满了(可达性算法),则触发 Minor GC(Young GC)
2. 回收不用的对象, 有用的对象复制到 S0中,并加上版本号
3. 第二次Eden满了后,继续触发 Minor GC
4. 将Eden中存活对象和S0的对象放到S1中,其中S0的也去判断哪些可用,可用的版本号加1
( 谁空放谁 )
5. from区到to区域时候,如果版本号达到15(阈值),则会Promotion, 放在老年代
-XX:MaxTenuringThreshold=15: 手动设置阈值
假如Survivor满了,同时没有达到阈值,也会放在老年代
tips: Survivor不会触发minor gc, 但是存在垃圾回收过程
- 特殊情况
- JVM在进行GC时,并非每次都对上三个内存区域一起回收
- 大部分回收都是指新生代
- 垃圾回收的时候,会有STW, 导致用户线程的暂停(标记清除)
HotSpot VM, 垃圾回收分为两类: 部分收集(Partial GC) 和 整堆收集(Full GC)
1. 部分收集: 不是完整收集整个JAVA堆中的垃圾收集
1.1 Minor GC: 收集新生代(Eden, S0, S1)
1.2 Major GC: 收集老年代
- CMS GC 会单独收集老年代
1.3 Mixed GC: 收集整个新生代以及部分老年代
- 目前只有G1 GC会有这样的行为
2. 整堆收集(Full GC): 收集整个java堆和方法区
3.1 Minor GC
- 当年轻代Eden空间不足时,就会触发Minor GC, S0/S1满时不会触发 - Java对象大多朝生夕死, Minor GC非常频繁, 回收速度比较快 - Minor GC会引发STW, 暂停其他用户线程3.2 Major GC
- 发生在老年代的GC - Major GC触发时,一般至少触发一次Minor GC 1. 先尝试Minor GC, 如果空间还是不足,则出发Major GC - Major GC 的速度一般比Minor GC慢10倍以上, STW的时间会更长 - Major GC后,内存还不足,就 OOM3.3 堆分代思想
- 为什么要进行堆内存分代
优化GC 性能 1. 如果没有分代,那所有对象都在一块, gc 时候需要所有区域扫描 2. 分代后 - 朝生夕死的对象,存放在新生代 - 生命周期长的对象,方法老年代 # 感觉和数据库分表的思想有点像啊4. 内存分配策略



