JVM--内存结构

JVM的内存结构主要分为两个区域
私有区域
程序计数器、虚拟机栈、本地方法栈
公共区域
堆、方法区、直接内存(堆外内存)
程序计数器是唯一不会发生内存溢出和栈溢出的区域
java程序执行过程
通过ECJ编译器,将java文件编译生成Class字节码文件

通过类加载器加载字节码文件,生成Class对象,并将字节码中的符号引用转化为内存的直接引用,配合字节码执行引擎执,JIT编译器和解释器,来完成程序的执行

直接内存
直接内存也被称为堆外内存。使用堆外内存可以带来哪些好处呢?
操作系统对JVM进程的所占用内存的最大值是有限制的,直接内存可以突破这种限制,充分利用系统内存。如果系统部署了多个JVM,如果JVM之间需要通信,则可以开辟直接内存,将共享数据放入直接内存中,这样就不需要在JVM内存之间进行复制,可提高系统的性能。还可以用于系统调用的数据存储,避免了从JVM内存再次复制到系统内存中,比如NIO。
元空间
在JVM1.8中,对JVM的内存结构有了一些调整,删除了方法区,将原来方法区中的数据都放入了堆中, 因此可以发现JVM1.8默认增大了堆的大小,同时开辟了元空间,元空间使用的是直接内存, 将类的元数据放入了元空间中,使用元空间的原因和好处有以下几点:
1.  在GC不停顿的情况下,就可以释放元空间, 减少了元数据的扫描
2.  目前受限于方法区的功能,如果未来改进提供了可能性
3.  元数据的使用情况,可以进行监控
4.  永久代启动时大小固定,不方便调优,并增加了垃圾收集的复杂度
5.  类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
CAS
Compare And Set, 比较并赋值,CPU的原子指令。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
遇到的问题:
1.  ABA问题
CAS需要在操作的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果在执行的过程中,值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了,虽然对执行结果没有影响,但是在安全性上存在问题。ABA问题的解决思路是追加版本号。
2.  性能问题
CAS在性能上会有两大性能问题:
a.  自旋CAS
所谓自旋,就是不断的循环,直到触发某种条件停止循环。自旋CAS,即失败重试,直至成功为止,否则自旋。
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令, 使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存访问顺序冲突,而引起CPU流水线被清空,从而提高CPU的执行效率。
b.  多处理器
多处理器的环境中,由于CAS所引起的性能问题,后续讲到内存模型和多线程的实现时,咱们再详细讨论。
pause指令
频繁的检测会让流水线上充满了读操作。另外一个线程往流水线上丢入一个锁变量写操作的时候,必须对流水线进行重排,因为CPU必须保证所有读操作读到正确的值。流水线重排十分耗时,影响lock()的性能。
为了解决这个问题,intel发明了pause指令。这个指令的本质功能:让加锁失败时cpu睡眠30个CPU时钟,从而使得读操作的频率低很多。流水线重排的代价也会小很多。
内存访问顺序冲突,当处理器在指令重排序执行的流水线上去内存load某个内存地址的值(此处是lock)的时候,发现这个值正在被store,而且store本身就在load之前,对于处理器来说,这就是一个hazard,流水流不起来。
在本文中,具体是指当一个获得锁的工作线程W从临界区退出,在调用unlock释放锁的时候,有若干个等待线程S都在自旋检测锁是否可用,此时W线程会产生一个store指令,若干个S线程会产生很多load指令,在store之后的load指令要等待store在流水线上执行完毕才能执行,由于处理器是乱序执行,在没有store指令之前,处理器对多个没有依赖的load是可以随机乱序执行的,当有了store指令之后,需要reorder重新排序执行,此时会严重影响处理器性能,按照intel的说法,会带来25倍的性能损失。Pause指令的作用就是减少并行load的数量,从而减少reorder时所耗时间。
指令重排序
指令重排序,是调整指令执行的顺序,但不破坏其程序的逻辑和执行结果!
举个简单的例子:
public int test() {
    int i = 0;
    int j = 0;
    int k = 0;
    // ... 此处可能有1000行代码,但都与 i 无关;
   i++;
}
我们看上面这个简单的代码,如果CPU顺序执行,首先按顺序将i, j, k, 从内存加载到缓存中和CPU的寄存器当中进行各种运算和执行, 然后执行1000行代码也是按顺序的, 那么在这个过程中,缓存可能是不够用的,在这个过程中,i 没有再被使用,因此系统可能会将 i 从缓存中移除, 等执行完1000行代码后, 这时系统发现又需要使用 i ,那么需要再次将 i 从内存加载到缓存。
为了解决这个问题, CPU会提前执行i++, 导致了指令的重排,提高了缓存的命中,加快了系统的性能。另一个方面是为了使指令流水更加顺畅也会对指令进行重排序。
指令流水
为提高处理器执行指令的效率,把一条指令的操作分成多个细小的步骤,每个步骤由专门的电路完成。
一条指令要执行要经过3个阶段:取指阶段、解析指令阶段、执行阶段;每个阶段都要花费一个时钟周期,如果没有采用流水线技术,那么这条指令执行需要3个时钟周期;如果采用了指令流水线技术,那么当这条指令完成取指后进入解析指令的同时,下一条指令就可以进行取指了,这样就提高的指令的执行效率。
文章:http://www.wannengye.com/pages/Mwh1g9FU


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值