jvm中对象及引用
- 对象创建过程:每个对象必须是8字节整数大小(对象头+实例数据),如果大小不到8的倍数会有对象填充
- 类加载
- 检查加载:检查类的符号引用是否加载解析或者初始化过
- 分配内存:一般是堆内存,垃圾回收器一般都会带整理
- 指针碰撞:规整连续内存,分配完就将指针移动到新位置
- 空闲列表:虚拟机会维护一张内存的空闲列表,顺序寻找适合的内存空间就停止
- 解决并发安全
- 默认使用本地线程分配缓冲TLAB:会给每个线程分配一部分区域来用,一般为Eden区1%
- CAS机制加失败重试:读取当时的内存值为old,然后做一些准备数据之类的处理,最后要比较此时内存的值和old是否相同,相等就分配成功,如果不相等(其他线程抢先使用造成不相等)就重新操作,发现如果已经分配会继续用下一个内存
- 内存空间初始化:设置私有变量之类的初始值
- 设置对象
- 对象头:类型指针,自身运行时数据,如果是数组还应有记录数组长度
- 实例数据
- 对象填充(非必须):如果对象头加实例数据不是8字节整数就需要对象填充
- 对象初始化:执行构造方法初始化对象
- 对象定位访问:都有一个指向对象类型的指针,对象类型数据存在方法区
- 使用句柄:创建句柄池,对象引用和实例之间的关联,可以找到对象的引用,如果对象改变,只需要修改句柄池指向对象的指针,不需要修改引用。优点是稳定,缺点是性能低
- 直接指针(hotspot基本都使用):引用直接指向对象实例数据,对象没了就得重新指向对象
- 什么是垃圾
- 没有任何引用指向的一个对象,看是否是引用计数是1
- 互相引用
- 判断对象的存活
- 可达性分析(根可达):解决了循环引用的问题
- 虚拟机栈(栈帧中本地变量表)中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方发栈中JNI(一般是native方法)引用的对象
- jvm内部引用的对象(class对象、异常对象nullpoint、outofmemory,系统类加载起)
- 所有被同步锁持有的对象
- jvm内部的JMXBean、JVMTI中注册的回调、本地代码缓存灯
- jvm视线中的“临时性”对象,跨代引用的对象
- class回收条件同时满足
- 类实例都被回收了,堆中不存在类的实例
- 加载该类的classloader已经被回收了
- 该类的java.lang.Class对象没有在任何地方被引用同时无法在任何地方通过反射访问该类的方法
- 参数控制:+Xnoclassgc,参数设置就不能被回收
- Finalize:如果对象覆盖了Finalize方法,使用这个方法可以把对象重新连接,但是只能连接一次,并且方法优先级很低,有可能垃圾回收先回收
- 可达性分析(根可达):解决了循环引用的问题
- 对象各种引用
- 强引用:等号引用,不能被回收
- 软引用:主要用来用作缓存,图片视频之类的。SoftReference生成的对象,不管是否根可达,oom错误会对软引用进行回收
- 弱引用:只要垃圾回收就会回收
- 虚引用:随时会被回收,主要用来检测垃圾回收器
- 对象分配策略:几乎所有对象都在堆中间分配,也有在栈上分配的
- 是否栈上分配:主要是进行了逃逸分析,无法逃出方法
- 本地线程分配缓冲,对象优先在Eden分配
- 是否是大对象:如果Eden,From,To区都放不下,直接放入老年代,但是得参数设置才能生效,仅限于Serial和ParNew,优先于空间分配担保,不同的垃圾收集器机制不太一样
- 空间分配担保:首先进行新生代的MinorGC,然后检查老年代连续可用空间,如果小于新生代的使用总和,就会触发MajorGC或者FullGC回收老年代,然后在MinorGC回收新生代
- 长期存活的对象进入老年代:Eden区满了,如果对象根可达并且是强引用,就会垃圾回收到From区或者To区,在From和To区的对象每回收一次Eden回收年龄加1,并移动到From区或者To区,当达到15会进入老年代
- 动态对象年龄判断:相同类型的对象大于From区的一半就直接进入老年代不需要年龄判断
- 虚拟机的优化策略
- 逃逸分析:如果对象只在一个方法里使用,也没有其他方法使用,那么虚拟机会优化这个对象在栈中分配,一般是在运行中分析,这样会直接进栈出栈避免没必要垃圾分析
- 本地线程分配缓冲:会给每个线程分配堆中的一块区域,一般为Eden区1%