JVM对象的知识

虚拟机遇到一条 new 指令时,是如何创建对象呢?
 
可以用一个流程图来解答
类加载 就是把 class 加载到 JVM 的运行时数据区的过程,后面会细说。
 
(1)检查加载:检查这个指令的参数能否在常量池定位到一个符号引用,并且检查当前类是否已经被初始化,解析,初始化过。

(2)分配内存:分配内存有两种方式:

                           一:是指针碰撞,我们都知道对象都是首先在Eden区分配的,如果那个区的内存空间是连续的整齐的,就会用一个指针来当作已经被分配的内存和未被分配内存的临界点,当new一个对象的时候,就在指针的后面给对象分配内存,然后指针跟着移动到已经分配的对象的后面。
                     
 
                二:空闲列表
                如果内存空间不是整齐的,而是乱序的,就不能采用上面的分配方式了,这个时候会用到空闲列表了,就是记录一下已经用的内存和未用的内存,当new出一个新对象的时候,通过记录的数据把未使用的内存空间来给这个对象。
 
上面两种分配方式具体使用哪种,还要看垃圾回收期的类型,如果垃圾回收期用的是复制算法,或者是标记整理算法的话就会用到指针碰撞,如果是标记清除算法的话,就用空闲列表的方式分配内存空间。
 
设想一下如果碰见多个线程同时在new对象的时候,会不会造成并发的情况呢(就是A线程的对象和B线程的对象同时抢到一块内存空间),答案是很有可能会,如何解决这种问题呢,JVM为我们提供了两种来解决并发分配内存的方案.
             一:就是通过加锁的方式来保证原子性,利用CAS的自旋锁的机制来保证同一时间只能一个线程来得到内存空间,不过这种最大方案的最大缺点是太消耗CPU资源了,因为自旋锁就是比较和交换,失败一直重复尝试。
 
    二:就是线程分配缓冲区,简称TLAB
原理就是在Eden区会有一块很小的区域来创建一个TLAB区,这个区域专门来创建对象用,因为这块区域是线程私有的,所以不会分配对象内存空间的时候存在并发安全的问题,但是 底下存对象的内存空间还是给所有线程访问的,只是其它线程无法在这个区域分配而已。
 
(3)内存空间初始化
 简单一句话就是赋予变量初始值,比如int a就给a一个初始值0,Boolean a就给一个false
(4)设置
接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息( Java classes Java hotspot VM 内部表示为类 元数据)、对象的哈希码、对象的 GC 分代年龄等信息。这些信息存放在对象的对象头之中。
(5)对象初始化
和上面内存空间初始化不同,
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚刚开始,所有的字段都还为零值。
所以,一般来说,执行 new 指令之后会接着把对象按照程序员的意愿进行初始化 ( 构造方法 ) ,这样一个真正可用的对象才算完全产生出来。
 
 
 
 
对象的内存布局
HotSpot 虚拟机中,对象在内存中存储的布局可以分为 3 块区域:对象头( Header )、实例数据( Instance Data )和对齐填充( Padding )。
对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码( HashCode )、 GC 分代年龄、锁状态标志、线程持有的锁、偏向线程
ID 、偏向时间戳等。
对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
如果对象是一个 java 数组,那么在对象头中还有一块用于记录数组长度的数据。
第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于 HotSpot VM 的自动内存管理系统要求对对象的大小必须
8 字节的整数倍。当对象其他数据部分没有对齐时,就需要通过对齐填充来补全
 
 
对象的访问定位
建立对象是为了使用对象,我们的 Java 程序需要通过栈上的 reference 数据来操作堆上的具体对象。目前主流的访问方式有使用句柄和直接指针两种。
 
句柄:
如果使用句柄访问的话,那么 Java 堆中将会划分出一块内存来作为句柄池, reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类
型数据各自的具体地址信息。
使用句柄来访问的最大好处就是 reference 中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实
例数据指针,而 reference 本身不需要修改 .
直接指针:
如果使用直接指针访问, reference 中存储的直接就是对象地址。
这两种对象访问方式各有优势, 使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在 Java 中非常频
繁,因此这类开销积少成多后也是一项非常可观的执行成本。
Sun HotSpot 而言,它是使用直接指针访问方式进行对象访问的。
 
 
判断对象的存活
 
一:引用计数法(Python在用
在对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1 ,当引用失效时,计数器减 1.
Python 在用,但主流虚拟机没有使用,因为存在对象相互引用的情况,这个时候需要引入额外的机制来处理,这样做影响效率

当两个对象互相引用的时候, 那么他们永久不会失效,那么垃圾回收器也就不会回收了

这种的对象用这种算法就不会被回收

二:可达性分析算法(java在用)

来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“ GC Roots ”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为
引用链( Reference Chain ),当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。
作为 GC Roots 的对象包括下面几种
强引用 (比如Objec o=new Objec());
静态变量 static修饰的;
常量    final ;
本地方法栈 Native 方法
这四种是重点,剩下的不说了,记不住
 
 
引用的几种类型:
简单来讲就四种:强>软>弱>虚  和男人的生命周期很符合啊
强引用:Objec o=new Objec(),垃圾回收的时候宁愿发生OOM都不会回收。
软引用    SoftReference   系统将要发生内存溢出( OuyOfMemory )之前,这些对象就会被回收
弱引用   WeakReference   只要发生GC都会回收    实际运用( WeakHashMap ThreadLocal
虚引用   PhantomReference   只有在检查gc能否正常工作的时候用,平常用不到,了解即可

 

 

对象的分配策略

1 对象优先分配到Eden区

2 大对象直接进入老年代  设置-XX:PretenureSizeThreshold=4m,超过4m直接进入老年代

3当对象的年龄是15的时候直接进入老年代

4当Survivor区的对象的内存大小超过Survivor区一半的时候全部都进入老年代

5空间担保:对象放入老年代的时候,会判断一下放入的大小,如果能放下就放下,放不下就发生一次老年代的垃圾回收(),再放入回收的对象

 

虚拟机的优化技术

逃逸分析

分析对象动态作用域,当一个对象在方法中定义后,它可能被外部方法所引用。

如果确定一个对象不会逃逸出线程之外,那么让对象在栈上分配内存可以提高 JVM 的效率。

意思就是说本来是在堆上分配对象的,但是虚拟机通过逃逸分析的方法判断你这个对象不会出这个方法,也不会被其他方法引用,所以就把这个对象在栈上分配了。栈的生命周期很短,随着线程的消失而消失,这样就不会对这部分对象进行垃圾回收了,来达成优化的效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值