一:对象创建流程
- 类加载检查:需要加载累的时机
- 内存分配
从内存中划分出特定大小的内存
a:如何划分内存
1:指针碰撞(bump the pointer):用过的放在一边,空闲的放在一边,分界点作为指示器,分配内存 时,移动指针
2:空闲列表(free list):空闲,已用内存交错,空闲内存放在一个列表中
b: 解决并发的方法
1:CAS(compare and swap )后续并发在补充
2:本地线程分配缓冲(Thead Local Allocation Buffer,TLAB)
每个线程在Java堆中预先分配一块内存
-XX:+/-UseTLAB 是否开启 ,默认开启
-XX:TLABSize 指定TLAB大小
3:初始化
内存空间初始化为零值,不包括对象头
设置了TLAB,会在TLAB时设置
保证对象不赋值就可以直接使用
4:设置对象头
对象在内存的存储布局可分三块:
一:对象头 header: 存储对象自身的运行时数据
哈希码,GC分带年龄,锁状态,线程持有的锁,偏向线程ID,偏向时间戳
类型指针:指向类元数据
二:实例数据 Instance Data
三:对齐填充 Padding
5:执行
属性复制,程序员赋值,执行构造方法
tip;对象大小和指针压缩
引入依赖查看对象大小:
org.openjdk.jol jol‐core 40.9
import org.openjdk.jol.info.ClassLayout;
/**
* 计算对象大小
*/
public class JOLSample {
public static void main(String[] args) {
ClassLayout layout = ClassLayout.parseInstance(new Object());
System.out.println(layout.toPrintable());
System.out.println();
ClassLayout layout1 = ClassLayout.parseInstance(new int[]{});
System.out.println(layout1.toPrintable());
System.out.println();
ClassLayout layout2 = ClassLayout.parseInstance(new A());
System.out.println(layout2.toPrintable());
}
// ‐XX:+UseCompressedOops 默认开启的压缩所有指针
// ‐XX:+UseCompressedClassPointers 默认开启的压缩对象头里的类型指针Klass Pointer
// Oops : Ordinary Object Pointers
public static class A {
//8B mark word
//4B Klass Pointer 如果关闭压缩‐XX:‐UseCompressedClassPointers或‐XX:‐UseCompressedOops,则占用8B
int id; //4B
String name; //4B 如果关闭压缩‐XX:‐UseCompressedOops,则占用8B
byte b; //1B
Object o; //4B 如果关闭压缩‐XX:‐UseCompressedOops,则占用8B
}
}
指针压缩
1:jdk1.6 update14,64位操作系统支持指针压缩
2:jvm 配置:UseCompressedOops,
3:启用指针压缩:-XX:+UseCompressedOops ,默认开启
压缩的原因:
1::64位平台使用32位Hotspot,内存会多1.5倍,数据copy移动,带宽较大,GC压力也大
2:减少64位平台的内存消耗
3:jvm 32位地址最大支持4G,,指针压缩,编码解码可以最大支持32G
4: 小于4G 时,不需开启,直接去除高位使用地位
5:堆内存大于32时,压缩指针会失效,会强制使用64位,寻址,会出现1的问题,堆内存最好不超过32G
二:对象内存分配
流程图
一:栈上分配
逃逸分析:对象不会被外部访问,才有机会栈上分配
有没有可能被其他方法引用,
或者传递到其他方法中
开启逃逸分析:-XX:+DoEscapeAnalysis JDK7以后默认开启
标量替换:对可以栈上分配的对象,进行拆分,使用其变量替换对象本身,jvm并不再创建该对象
标量:不可在分解的量,如基本数据类型
聚合量:可以分解的量。对象
public User test1() { //逃逸
User user = new User();
user.setId(1);
user.setName("zhuge");
//TODO 保存到数据库
return user;
}
public void test2() { //未逃逸
User user = new User();
user.setId(1);
user.setName("zhuge");
//TODO 保存到数据库
}
二:EDEN 分配
Minor GC/Young GC:新生代GC的动作** eden满时触发,频繁,
minorGC时发现如果占S区比较大,也会提前移动到老年代(优化S区大小,或整个ENED大小)
Major GC/Full GC:老年代,新生代,方法区GC,速度慢很多
Eden与Survivor区默认8:1:1
-XX:+UseAdaptiveSizePolicy(默认开启) :自适应条件e/s区比例
三:大对象直接进入老年代
-XX:PretenureSizeThreshold 设置大大对象的大小
仅在Serial 和ParNew收集器下有效
-XX:MaxTenuringThreshold 进入老年的年龄阈值
经历一次minorGC +1
默认15,其中CMS默认6
对象动态年龄判断
minor GC 是发现往 S1区复制的对象大于 S1的50%,也会直接移动到老年代
-XX:TargetSurvivorRatio,可以指定整个比例
老年代空间分配担保机制
-XX:-HandlePromotionFailure
三:对象内存回收
引用计数法
简单,高效,不使用,循环引用的问题
可达性分析算法
从GC Roots 搜索到的都是非垃圾对象
GC Roots: 线程栈的本地变量、静态变量、本地方法栈的变量
常见引用类型(面试用,平时用不到)
强:
普通变量的引用public static User user = new User();
不会回收,会抛OOM
弱:
GC后发现释放不出新空间存放对象时会回收掉public static SoftReference<User> user = new SoftReference<User>(new User());
软:
GC会直接回收掉public static WeakReference<User> user = new WeakReference<User>(new User());
虚:
几乎不用,忽略吧
finalize()方法
- 第一次标记并进行一次筛选
筛选的条件就是 finalize方法如果没覆盖此方法,会直接被回收 - 第二次标记
必须与GC root关联上,不然也会被回收, - 一个对象的finalize()方法只会被执行一次
判断一个类是无用的类
- 所有的实例都被回收
- 加载类的ClassLoader也会回收(三种加载器几乎不会被回收)
- 对应的java.lang.Class 对象也没有任何的引用,反射也访问不到了