zoukankan      html  css  js  c++  java
  • JVM内存结构

     JavaSE,Java平台标准版,为Java EE和Java ME提供了基础。

     JDK:Java开发工具包,JDK是JRE的超集,包含JRE中的所有内容,以及开发程序所需的编译器和调试程序等工具。

     JRE:Java SE运行时环境 ,提供库、Java虚拟机和其他组件来运行用Java编程语言编写的程序。主要类库包括:程序部署发布、用户界面工具类、继承库、其他基础库,语言和工具基础库

     JVM:java虚拟机,负责JavaSE平台的硬件和操作系统无关性、编译执行代码(字节码)和平台安全性。

    JVM 内存结构

     

    简介

    程序计数器

    一块很小的内存空间,它是线程私有的,可以认作为当前线程的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,所以为线程私有。此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

    虚拟机栈

    虚拟机栈描述的是Java执行方法的内存模型。每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。

    Java虚拟机栈可能出现两种类型的异常:

    1. 线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。
    2. 虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。

    本地方法栈

     本地方法栈是与虚拟机栈发挥的作用十分相似,区别是虚拟机栈执行的是Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务,可能底层调用的c或者c++,我们打开jdk安装目录可以看到也有很多用c编写的文件,可能就是native方法所调用的c代码。

    方法区(永久区),

    用于存储类信息,常量,静态变量static关键字修饰的等信息,当class文件被加载的时候就会被初始化,线程共享

    Java虚拟机管理内存中最大的一块,线程共享区域。所有对象实例和数组都在堆上分配内存空间。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。

    例:

      

    因为有static关键字,所以这个对象是存放在方法区,去掉static则存放在堆。

     栈

    函数中定义的一些基本类型的变量对象的引用变量都在函数的栈内存中分配。 当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。

    每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。


    方法区

    这块区域对应的是Permanent Generation(持久代),一般的,方法区上执行的垃圾收集是很少的,因此方法区又被称为持久代的原因之一。

    当方法区使用的内存超过它允许的大小时,就会抛出OutOfMemory:PermGen Space异常。

    方法区主要是存储类的元数据的,如虚拟机加载的类信息、编译后的代码等。JDK8之前方法区的实现是被称为一种“永久代”的区域,这部分区域使用JVM内存,但是JDK8的时候便移除了“永久代(Per Gen)”,转而使用“元空间(MetaSpace)”的实现,而且很大的不同就是元空间不在共用JVM内存,而是使用的系统内存 ,好处是不占用虚拟机内存,可以从系统内存分配空间

    相关参数 

    jdk1.7及以前:-XX:PermSize;-XX:MaxPermSize;

    jdk1.8以后:-XX:MetaspaceSize; -XX:MaxMetaspaceSize

    jdk1.8以后大小就只受本机总内存的限制

    如:-XX:MaxMetaspaceSize=3M

    分为新生代,老年代。

    --新生代

    新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中。

    新生代的目标就是尽可能快速的收集掉那些生命周期短的对象。新生代分为三个区。一个Eden区,两个Survivor区(一般而言)。

    大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活将被复制到另外一个Survivor区,当这个Survivor区也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。

    年轻代:创建的新对象会被放入年轻代的eden空间,而年轻代gc采用复制算法,复制算法会把内存分为两个区域(即两个survivor空间:from和to)。当进行一次minor gc时(既年轻代的gc),minor gc是串行的,eden空间如果没有被gc root引用的会被回收,而依然存活的会被移动到from空间中,如果from空间在minor gc时对象依旧可以存活,就会对该对象年龄+1,当年龄达到一定数值时会直接放入老年代,没有达到年龄的存活对象会被复制到to中。这时from和eden空间已经被清空,虚拟机会交换from和to的空间,空的from变成to,to的变成from,保证了to是空的,minor gc会不断重复这样的工作,直到to彻底被填满,这时会将对象移动到老年代。)

    新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例。

    --老年代

    在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到老年代中。因此,可以认为老年代中存放的都是一些生命周期较长的对象。

    老年代:老年代空间的对象是经过minor gc反复锤炼出来的。老年代使用并行的gc回收期,标记-清除算法,并且产生的是full gc(major gc)。老年代gc虽然是并行的,但full gc会同时对年轻代进行gc,所以大量的full gc会严重耗费jvm的性能,甚至卡死应用。另外可以大对象会直接分配到老年代,避免了在minor gc对两个survivor空间的复制耗时。

    什么情况下触发垃圾回收

    由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC Full GC

            Scavenge GC

            一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对新生代的Eden区进行,不会影响到老年代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden区能尽快空闲出来。

             Full GC(Major GC)

             对整个堆进行整理,包括Young、Tenured 和 Perm。Full GC 因为需要对整个堆进行回收,所以比 Scavenge GC 要慢,因此应该尽可能减少 Full GC 的次数。在对JVM调优的过程中,很大一部分工作就是对于 Full GC 的调节。

    有如下原因可能导致Full GC:
             . 新生代(Tenured)被写满

             . 持久代(Perm)被写满

             . System.gc()被显式调用

             . 上一次GC之后Heap的各域分配策略动态变化

              https://blog.csdn.net/qq_35625303/article/details/79374964

    对象的内存布局

    在HotSpot虚拟机中。对象在内存中存储的布局分为 对象头、实例数据、对齐填充。

    对象头

    hotspot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据(如 哈希码、GC分代年龄、锁状态标志、线程持有的锁,偏向线程ID、偏向时间戳等)。

    在32位系统下,对象头8字节,64位则是16个字节【未开启压缩指针,开启后12字节】。

    markword很像网络协议报文头,划分为多个区间,并且会根据对象的状态复用自己的存储空间。

    为什么这么做:省空间,对象需要存储的数据很多,32bit/64bit是不够的,它被设计成非固定的数据结构以便在极小的空间存储更多的信息,

    假设当前为32个字节,在对象未被锁定情况下。25bit为存储对象的哈希码、4bit用于存储分代年龄,2bit用于存储锁标志位,1bit固定为0。

    另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

    实例数据

    存放对象程序中各种类型的字段类型,不管是从父类中继承下来的还是在子类中定义的。分配策略:相同宽度的字段总是放在一起,比如double和long

    对齐填充

    这部分没有特殊的含义,仅仅起到占位符的作用满足JVM要求。

    由于HotSpot规定对象的大小必须是8的整数倍,对象头刚好是整数倍,如果实例数据不是的话,就需要占位符对齐填充。

    对象的访问定位

    java程序需要通过栈上的reference数据来操作堆上的具体对象。目前主流的访问方式有使用句柄直接指针两种。

    • 使用句柄,java堆中会划出一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息。

    使用句柄来访问的好处就是reference中存储的是稳定的句柄地址,在对象被移动时指挥改变句柄中的实例数据指针,而reference本身不需要修改。

    • 使用直接指针,那么java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址。

    使用直接指针的好处是,速度更快,它节省了一次指针定位的时间开销,对象的访问在java中非常频繁,因此此类开销积少成多后也是一项非常可观的执行成本。

     

     

    运行时数据区中哪些区域是线程共享的?哪些是独享的?

    在JVM运行时内存区域中,PC寄存器、虚拟机栈和本地方法栈是线程独享的。

    而Java堆、方法区是线程共享的。但是值得注意的是,Java堆其实还未每一个线程单独分配了一块TLAB空间,这部分空间在分配时是线程独享的,在使用时是线程共享的。(TLAB介绍

    待续。。。

  • 相关阅读:
    Redis安装
    mysql 存储过程与存储函数
    mysql 常用函数
    cpu-z笔记本加条子
    centos上网络服务起不来network.service failed
    centos/redhat命令行上传下载文件
    docker删除已经停止的容器
    centos/redhat/ubuntu不同之处
    部署lamp动态网站(图解)
    写交互式脚本时,遇到到报错:not a regular file
  • 原文地址:https://www.cnblogs.com/dingpeng9055/p/10390560.html
Copyright © 2011-2022 走看看