目录页:https://mp.youkuaiyun.com/postedit/95937156
1. 小声哔哔
作为一个程序员找不到对象很正常,但是我们写的代码要是找不到对象就出大问题,所以了解Jvm对象的相关知识作为我的第二部分开始学习,下面有记录的不对的地方请指正。
2.Jvm对象分配
国家不分配对象,但是Jvm分配对象。
我们在不使用spring等框架的时候,创建一个对象的最简单方式就是使用new,Jvm虚拟机在碰到new关键字分别做了以下几个步骤:检查类加载->分配内存->内存空间初始化->对象设置->对象初始化。
- 检查类加载:若需要new的对象的类还没有加载,则必须先进行类加载。
- 分配内存:对象所需的内存大小在类加载后就可以完全确认。我们都知道,对象的内存是在堆中划分出来的,对象的内存分配有两种方式:指针碰撞和空闲列表。
若java堆中的内存是绝对规整的,空闲内存在一边,被占用的内存在一边,指针指向内存空闲和被占用区域的分界点,分配内存时仅仅需要将指针向空闲区域移动一段与对象所需内存大小相同的距离即可,这种分配方式就是指针碰撞。
若堆中的内存是不规整的,那么虚拟机就必须维护一个列表,列表中记录哪些内存是可用的,在分配内存时只需找到一个足够大的内存区域分配并更新列表中记录即可,这种内存分配方式就是空闲列表。
选择哪种方式进行内存分配依赖于堆内存是否规整,而堆内存是否规整依赖于GC机制是否有压缩整理功能。
需要注意的是这两种内存分配的方式都会有并发安全的问题,解决这个问题有两种方式:1.Jvm虚拟机是采用CAS指令来保证并发安全。2.每个线程在Java堆中预先分配一小块内存,成为本地线程分配缓冲(TLAB),哪个线程要分配内存就在哪个线程的TLAB中进行分配。虚拟机是否使用TLAB通过-XX:+/-UseTLAB参数决定。
- 内存空间初始化:初始化参数默认值所占用的内存空间。
- 对象设置:这里的设置主要是设置这个对象是哪个类的实例,对象的hash码,对象的GC分代信息等。这些信息存放在对象头中。
- 对象初始化:调用构造方法进行对象初始化
3. 对象内存布局
HotSpot虚拟机中内存布局分配三个:对象头,实例数据和对齐填充。
对象头中包括两个信息:
- 运行时数据:哈希码(HashCode),锁状态标识,锁状态标识,GC分代信息等。
- 类型指针:对象指向它的类元数据指针,虚拟机通过该指针确认对象是哪个类的实例。
实例数据:程序代码中定义的各种类型的字段内容,包括父类中继承下来的也需要记录。
对齐填充:由于HotSpot自动内存管理系统要求对象地址必须是8字节的整数倍 ,对象头正好是8字节的倍数,因此当实例数据部分不是8的整数倍时就需要对齐填充来补全。
4. 对象访问方式
对象访问方式有两种:
- 句柄访问:Java中会分配一个内存地址来作为句柄池,访问对象时需要先到句柄池中找,然后找到对象的实例数据和类型数据
- 直接指针:访问对象时指针指向的直接就是对象的实例数据。很显然直接指针访问效率更高HotSpot使用的就是直接指针。
5. 堆内存分配策略
堆内存分为新生代和老年代,新生代又分为Eden空间,From Survivor空间和To Survivor空间。新生代中三个区域默认分配比例为8:1:1,可以通过参数-XX:SurvivorRatio=8来控制。
堆内存分配策略:
- 对象优先在Eden区分配
- 大对象直接进入老年代:Eden剩余空间不足,From区和To区也没有足够的空间,则对象直接进入老年代。
package com.jvm.coline.part2;
public class BigObjectDemo {
private static final int BYTE_1MB = 1024*1024;
public static void main (String[] arg0){
byte[] b1,b2,b3;
b1 = new byte[2*BYTE_1MB];
b2 = new byte[2*BYTE_1MB];
b3 = new byte[5*BYTE_1MB];
}
}
启动参数配置如下
-Xmx20m -Xms20m -Xmn10m -XX:+PrintGCDetails
输出结果:
可以看到新生代容纳不下的5M内存直接进入到了老年代。
也可以使用参数:-XX:PretenureSizeThreshold=3m来设置内存大于多少的直接进入老年代。
- 长期存活的对象进入老年代:GC年龄大于15的内存会被放置到老年代
- 动态对象年龄判定:from区或to区中内存占用已达到50%,则内存提前放置到老年代 。
- 空间分配担保:默认认为分配到老年代的内存空间是够用的,增加效率
6. 思维导图
参考资料:周志明大神-《深入理解Java虚拟机 JVM高级特性与最佳实践》