一:对象创建流程

image.png

  1. 类加载检查:需要加载累的时机
  2. 内存分配
    从内存中划分出特定大小的内存

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
image.png
5:执行 方法
属性复制,程序员赋值,执行构造方法

tip;对象大小和指针压缩
引入依赖查看对象大小:

org.openjdk.jol jol‐core4 0.9
  1. import org.openjdk.jol.info.ClassLayout;
  2. /**
  3. * 计算对象大小
  4. */
  5. public class JOLSample {
  6. public static void main(String[] args) {
  7. ClassLayout layout = ClassLayout.parseInstance(new Object());
  8. System.out.println(layout.toPrintable());
  9. System.out.println();
  10. ClassLayout layout1 = ClassLayout.parseInstance(new int[]{});
  11. System.out.println(layout1.toPrintable());
  12. System.out.println();
  13. ClassLayout layout2 = ClassLayout.parseInstance(new A());
  14. System.out.println(layout2.toPrintable());
  15. }
  16. // ‐XX:+UseCompressedOops 默认开启的压缩所有指针
  17. // ‐XX:+UseCompressedClassPointers 默认开启的压缩对象头里的类型指针Klass Pointer
  18. // Oops : Ordinary Object Pointers
  19. public static class A {
  20. //8B mark word
  21. //4B Klass Pointer 如果关闭压缩‐XX:‐UseCompressedClassPointers或‐XX:‐UseCompressedOops,则占用8B
  22. int id; //4B
  23. String name; //4B 如果关闭压缩‐XX:‐UseCompressedOops,则占用8B
  24. byte b; //1B
  25. Object o; //4B 如果关闭压缩‐XX:‐UseCompressedOops,则占用8B
  26. }
  27. }

指针压缩
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

二:对象内存分配

流程图

image.png

一:栈上分配

逃逸分析:对象不会被外部访问,才有机会栈上分配
有没有可能被其他方法引用,
或者传递到其他方法中
开启逃逸分析:-XX:+DoEscapeAnalysis JDK7以后默认开启
标量替换:对可以栈上分配的对象,进行拆分,使用其变量替换对象本身,jvm并不再创建该对象
标量:不可在分解的量,如基本数据类型
聚合量:可以分解的量。对象

  1. public User test1() { //逃逸
  2. User user = new User();
  3. user.setId(1);
  4. user.setName("zhuge");
  5. //TODO 保存到数据库
  6. return user;
  7. }
  8. public void test2() { //未逃逸
  9. User user = new User();
  10. user.setId(1);
  11. user.setName("zhuge");
  12. //TODO 保存到数据库
  13. }

二: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
image.png

三:对象内存回收

引用计数法

简单,高效,不使用,循环引用的问题

可达性分析算法

GC Roots 搜索到的都是非垃圾对象
GC Roots: 线程栈的本地变量、静态变量、本地方法栈的变量
image.png

常见引用类型(面试用,平时用不到)

强:
普通变量的引用
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()方法

  1. 第一次标记并进行一次筛选
    筛选的条件就是 finalize方法如果没覆盖此方法,会直接被回收
  2. 第二次标记
    必须与GC root关联上,不然也会被回收,
  3. 一个对象的finalize()方法只会被执行一次

判断一个类是无用的类

  1. 所有的实例都被回收
  2. 加载类的ClassLoader也会回收(三种加载器几乎不会被回收)
  3. 对应的java.lang.Class 对象也没有任何的引用,反射也访问不到了