java实例对象存储位置

java实例对象存储位置

在java内存模型中,运行时数据区是java程序得以运行至关重要的区域,主要的作用就是将字节码文件写入内存中,方便后续执行引擎的调用。而运行时数据区分为五个板块,程序计数器,java虚拟机栈,本地方法栈,java堆以及方法区。

运行时数据区

程序计数器是一块比较小的内存区域,主要是充当着字节码文件的信号指示器,线程的分支、循环、异常跳出、恢复运行都是依靠着程序计数器对其发号指令。在多线程运行中,实际上就是多个线程不断切换获取cpu执行任务,而每次切换都需要将自己线程的状态存储,就是存储在程序计数器中,因此每一个线程都有其自己私有的程序计数器,程序计数器也是唯一一块没有规定OutOfMemoryError情况的区域。

虚拟机栈也是每个线程私有的,是每个线程方法执行的内存模型,线程在方法执行中,会生成一个栈帧,栈帧里边存储着局部变量表,操作数栈,动态链接,方法出口。线程每一个方法的执行,就是栈帧在虚拟机栈中入栈出栈的过程。因为每个线程执行的方法不同,因此每一个虚拟机栈都是私有的,其生命周期与线程相同。

在栈中放入超过虚拟机要求的长度,就会发生StackOverflowError。如果在栈扩展时不能得到足够的内存则会抛出OutOfMemoryError。

本地方法栈与虚拟机栈类似,都是线程私有的,区别在于虚拟机栈是为执行字节码文件的线程服务的,而本地方法栈则是为调用native方法的线程服务的,本地方法栈同样会抛出StackOverflowError以及OutOfMemoryError。

java堆是运行时数据区中线程共享的内存区域,伴随着jvm开启而创建,其中存放着大部分实例对象。由于垃圾回收机制通常发生在堆中,因此堆又被叫做“GC堆”。由于目前垃圾回收大多采用分代回收机制,因此java堆又被分为新生代与老年代。而根据内存分配的不同又可以将堆空间分为不同线程的私有分配缓冲区。但不管怎么分配,堆中存储的始终是对象实例。不同的分配方式只是为了提高垃圾回收的效率以及内存分配的速度。

jvm规定堆中的内存在物理上可以是不连续的,但是在逻辑上是连续的,内存大小可以是固定的也可以是可扩展的,当实例对象未能成功创建而堆扩展无法拿到足够空间则会抛出OutOfMemoryError。

方法区也是线程共享的区域,其中存放的类信息,常量,静态变量以及即时编译器编译过后的代码数据。jvm规定方法区是一种类堆的结构,但是方法区还有一个别名是“Non-Heap”,就是为了与堆所区分。jvm对于方法区的规定较为轻松,方法区与堆一样可以是固定大小也可以是可扩展的,方法区可以不进行垃圾回收。对于方法区的回收就是常量的回收以及类型的卸载。


通过上边对于运行时数据区的简单描述,可以看出,堆是运行时数据区存放实例对象的区域,但是不是所有对象实例都存在于堆中呢?不是的。

逃逸分析

在《深入理解JVM虚拟机》中是这样写道的:

随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变的不那么“绝对”。

逃逸分析其实就是对于一个新对象的引用范围进行分析从而决定是否分配在堆上。

以下是两个例子分析:

/*
*不发生逃逸,没有被外部,在方法内创建在方法内销毁
*/
public void my_method(){
    Person person = new Person();
    //do something
}
//发生了逃逸,StringBuilder的对象实体通过返回值返回,这就有可能被外部使用到
public StringBuilder my_mythod2(){
    StringBuilder str = new StringBuilder();
    //do something
    return str;
}
//不会发生逃逸,我们关注的是在方法中创建的实体本身,也就是str本身是否会被外部的其他方法调用
//显然是不会被外部其他方法调用的
public String my_method3(){
    StringBuilder str = new StringBuilder();
    //do something
    return str.toString();
}

上边例子可以总结为

  • 当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
  • 当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。例如作为调用参数传递到其他地方中。

如果未发生逃逸,那么这个对象就可能是栈分配的备选,而不是堆分配。

锁消除

除了对于对象实例是否是进行堆分配时进行逃逸分析,在多线程中经过JIT分析过后发现当前锁对象只能被当前线程所持有而无法传递给其他线程竞争的话,那么就会取消这部分代码的同步,这样能够大大提高并发性和性能。

/**
*该部分的锁代码是无效的,因为要锁共享变量的,我们是重新向另一个方向分析
*/
public void my_method(){
    Person person = new Person();
    synchronized(person){
        System.out.println(peson);
    }
}

经过逃逸分析后优化为:

public void my_method(){
    Person person = new Person();
    System.out.println(person);
}

源自:fzzf的博客

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值