04-JVM虚拟机
1. JVM虚拟机概述
1.4 对象的创建流程与内存分配
1.4.1 创建流程

1.4.2 对象内存分配方式
内存分配的方法有两种:不同垃圾收集器不一样
-
指针碰撞(Bump the Pointer)
-
空闲列表(Free List)
| 分配方法 | 说明 | 收集器 |
|---|---|---|
| 指针碰撞(Bump the Pointer) | 内存地址是连续的(新生 代) | Serial 和 ParNew 收集器 |
| 空闲列表(Free List) | 内存地址不连续(老年代) | CMS 收集器和 Mark-Sweep 收集 器 |

指针碰撞示意图:

1.4.3 内存分配安全问题
在分配内存的时候,虚拟机给A线程分配内存过程中,指针未修改。此时B线程同时使用了同样一块内存。是不是就出现了线程的安全性问题?
在JVM中有两种解决办法:
-
CAS 是乐观锁的一种实现方式。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
-
TLAB本地线程分配缓冲(Thread Local Allocation Buffer即TLAB):为每一个线程预先分配一块内存
JVM在第一次给线程中的对象分配内存时,首先使用CAS进行TLAB的分配。当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配。
对象内存分配流程【重要】:

1.4.4 对象怎样才会进入老年代?重点
内存担保机制是个怎么回事?
对象内存分配:
-
新生代:新对象大多数都默认进入新生代的Eden区
-
进入老年代的条件:四种情况
- 存活年龄太大,默认超过15次【-XX:MaxTenuringThreshold】
- 动态年龄判断:MinorGC之后,发现Survivor区中的一批对象的总大小大于了这块Survivor区的50%,那么就会将此时大于等于这批对象年龄最大值的所有对象,直接进入老年代
- 举个栗子:Survivor区中有一批对象,年龄分别为年龄1+年龄2+年龄n的多个对象,对象总和大小超过了Survivor区域的50%,此时就会把年龄n及以上的对象都放入老年代。
- 为什么会这样?希望那些可能是长期存活的对象,尽早进入老年代。
- -XX:TargetSurvivorRatio可以指定
- 大对象直接进入老年代:前提是Serial和ParNew收集器
- 举个栗子:字符串或数组
- -XX:PretenureSizeThreshold一般设置为1M
- 为什么会这样?为了避免大对象分配内存时的复制操作降低效率。避免了Eden和 Survivor区的复制
- MinorGC后,存活对象太多无法放入Survivor
空间担保机制:当新生代无法分配内存的时候,我们想把新生代的老对象转移到老年代,然后把新对象放入腾空的新生代。此种机制我们称之为内存担保。
-
MinorGC前,判断老年代可用内存是否小于新时代对象全部对象大小,如果小于则继续判
-
断判断老年代可用内存大小是否小于之前每次MinorGC后进入老年代的对象平均大小
-
如果是,则会进行一次FullGC,判断是否放得下,放不下OOM
-
如果否,则会进行一些MinorGC:
- MinorGC后,剩余存活对象小于Survivor区大小,直接进入Survivor区
- MinorGC后,剩余存活对象大于Survivor区大小,但是小于老年代可用内存,直接进入老年代
- MinorGC后,剩余存活对象大于Survivor区大小,也大于老年代可用内存,进行FullGC
- FullGC之后,任然没有足够内存存放MinorGC的剩余对象,就会OOM
老年代的担保示意图:

1.4.5 案例演示:对象分配过程
01-大对象直接进入老年代
package com.hero.jvm.object;
/**
* 测试:大对象直接进入到老年代
* -Xmx60m -Xms60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:PretenureSizeThreshold
*/
public class YoungOldArea {
public static void main(String[] args) {
byte[] buffer = new byte[1024 * 1024 * 20]; // 20M
}
}
-
-XX:NewRatio=2 新生代与老年代比值
-
-XX:SurvivorRatio=8 新生代中,Eden与两个Survivor区域比值
-
-XX:+PrintGCDetails 打印详细GC日志
-
-XX:PretenureSizeThreshold 对象超过多大直接在老年代分配,默认值为0,不限制

02-对象内存分配的过程:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* -Xmx600m -Xms600m -XX:+PrintGCDetails
*/
public class HeapInstance {
public static void main(String[] args) {
List<Picture> list = new ArrayList<>();
while (true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(new Picture(new Random().nextInt(1024 * 1024)));
}
}
}
class Picture {
private byte[] pixels;
public Picture(int length) {
this.pixels = new byte[length];
}
}


1.4.6 案例演示:内存担保机制
案例准备
-
JVM参数: -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 - XX:+UseSerialGC
-
分配三个1MB的对象和一个5MB的对象
-
-Xmn10M新生代内存的最大值:包括Eden区和两个Survivor区的总和
代码如下:
/**
* 内存分配担保案例
*/
public class MemoryAllocationGuarantee {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
memoryAllocation();
}
public static void memoryAllocation() {
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[1 * _1MB]; // 1M
allocation2 = new byte[1 * _1MB]; // 1M
allocation3 = new byte[1 * _1MB]; // 1M
allocation4 = new byte[5 * _1MB]; // 5M
System.out.println("完毕");
}
}

最低0.47元/天 解锁文章
4161

被折叠的 条评论
为什么被折叠?



