JVM 第三节 反射与内存模型

JVM第三节

反射

Oracal官网对反射的概括

委派模式

  • 本地实现 当调用次数比较少时
  • 动态实现 调用次数较多时
public static void hello(int num){
    System.out.println(" # "+num);
    new Exception(" ---- ").printStackTrace();
}	
public static void main(String[] args) throws Exception{
    Class<?> stu = Student.class;
    Method hello = stu.getMethod("hello",int.class);
    hello.invoke(null,0);
}

之所以采用,委派模式,为了在本地实现与动态实现之间进行转换.
存在一个阈值(15) 当大于阈值是就采用 动态实现.

public static void main(String[] args) throws Exception{
    Class<?> stu = Student.class;
    Method hello = stu.getMethod("hello",int.class);
    for (int i = 0; i < 20; i++) {
        hello.invoke(null,i);
    }
}

执行一次
在这里插入图片描述

执行多次
在这里插入图片描述

那为啥不直接使用 动态实现呢?在这里插入图片描述

因为,动态实现是通过字节码实现的.前期的准备时间长.
所以在调用次数比较少时,采用本地实现.
(有点像快速排序和插入排序,在10以内插入排序要比快速排序快)

反射的消耗

反射是很消耗内存的

  1. 先取得 Class
  2. 遍历所有的方法得要 Method
  3. 调用 invoke()

主要开销

  • 变长参数方法的Object数组
  • 基本类型的装箱拆箱
  • 方法内联

(方法内联指的是编译器在编译一个方法时,将某个方法调用的目标方法也纳入编译范围内,并用其返回值替代原方法调用这么个过程。)

Java内存对象的布局

当我们new 一个对象时,回去调用它的构造器.(如果没有显式声明,则会创建一个空参构造器)
而子类构造器又会去加载父类的构造器.

所以虽然父类中存在一些不能访问的字段(private),但仍然为它分配了内存.
这样做是很正常的,子类的方法调用父类的方法中有存在对该字段的访问.

每个对象都有一个对象头

对象头 = 标记字段 + 类型指针
在64位系统中,标记字段 , 类型指针都占 64bit 也就是需要 16B
像 int 它只占 4B ,如果用Integer 需要额外加上 16B

这里存在一个 优化

压缩指针

将 类型指针从 8B->4B ,从而将对象头 16B -> 12B
可以将 两个单元 看成 一个单元

例如 0 1 2 3 4 5

0 1 看成一个整体,如果要取 值的话 直接乘2 即可

内存对齐

内存模型

下面的代码中,将方法1,2 各执行一次
多线程中局部变量 (r1,r2) 可能存在多个结果

int a=0, b=0;

public void method1() {
  int r2 = a;
  b = 1;
}
public void method2() {
  int r1 = b;
  a = 2;
}

结果有 4 种

(1,0) (0,2) (0,0) (1,2)
其中 (1,2) 是很让我迷惑的,造成这种结果的原因

  • 即时编译器的重排序 (遵循 as-if-serial)
  • 处理器的乱序执行
  • 内存系统的重排序

as-if-serial 在单线程程序中,要保证重排序的结果要与顺序执行的一致,
即给人一种程序顺序执行的错觉

这里先说一下,即时编译器的重排序.
它是编译器对代码的优化. 犹记当初学习编译原理时,各种啥的啥的…忘记了

有点对编译器优化的印象.将一些代码进行重新排列,然后还会对压根没有使用到的
对象去除.

总之,最后代码的真正执行顺序是与 正常的顺序执行 不大相同的

当然,在单线程因为遵循 as-if-serial 规则,不会出现 (1,2)这种情况

而多线程,可能会存在数据竞争(data race),Java语言则归咎于没有处理好并发

Java内存模型与 happens-before

为了避免数据竞争造成的影响,Java5 提出了 happens-before
如果说操作 X happens-before Y ,
那么 X 对于 Y 来讲是可见的

但是,在程序中,如果 X 对于 Y 没有影响,那么 X 与 Y 的执行顺序是不一定的

内存屏障 阻止指令的重排序,导致缓存的刷新

锁,volatile,final

Java 中的锁,可以避免多线程中对共享变量数据不一致的问题.
在解锁时,会清除缓存,让当前线程修改的数据对其它线程可见.

JIN 存在逃逸分析,如果JIN 发现有锁没锁都一样,那么它就会取消锁

volatile 也算是一种轻量级锁,它可以保证 该对象的可见性,但是保证不了原子性.
同时,频繁地访问,会造成很多次缓存的清除,从而降低性能

逃逸分析的文章链接

final 字段,因为是不可变的.所以会有一个 写屏障,防止对其值的修改.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值