java内存分析

java内存分析

images/JAVA虚拟机运行时数据区(2).png

images/JAVA虚拟机运行时数据区(2).png

JAVA虚拟机运行时数据区(2).pngjava

程序计数器

  • 一块较小的内存空间,能够看做是当前线程所执行的字节码的行号指示器。java虚拟机多线程是经过线程轮流切换并分配处理器执行时间的方式来实现的。在任何一个肯定的时刻,一个处理器(对于多核来讲,就是一个核)都会执行一条线程中的指令。由于,为了线程切换后能恢复到正确的位置,每条现成都须要一个独立的程序计数器,各个线程之间计数器互相不影响,独立存储,这类内存区域为“线程私有”内存,若是正在执行的是Native方法,则技术器数值为空,此区域是java虚拟机中没有规定任何OutofMemoryError状况的区域web

虚拟机栈

  • 线程私有,生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型:每一个方法在执行的同时都会建立一个栈帧(Stack Frame)用于存储局部变量表,操做数栈,动态链接,发放出口等信息。每一个方法从调用直到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。算法

老师们总说的「堆内存」(Heap)和「栈内存」(Heap),其中的栈指的是上图中的「虚拟机栈」或「局部变量表」部分。
虚拟机栈是一个大的主体,包括局部变量表、操做数栈,动态链接,返回地址等数组

局部变量表

当前线程调用函数内的局部变量。
「变量槽」(slot)是局部变量表的最小单位,通常为32位大小,一个slot能够存放boolean,byte,char,short,int,float,reference和returnAddress8种类型,reference表示一个对象的实例引用,经过他能够得到对象在java堆中存放的起始位置和数据类型等信息,slot经过2个连续的空间存放long跟double类型,returnAddress类型是为字节码指令jsr、jsr_w和ret服务的,他指向了一条字节码指令的地址
变量槽的复用:
虚拟机经过索引定位的方式使用局部变量表。局部变量表存放的是方法参数和局部变量。当调用方法是非static 方法时,局部变量表中第0位索引的 Slot 默认是用于传递方法所属对象实例的引用,即 “this” 关键字指向的对象。分配完方法参数后,便会依次分配方法内部定义的局部变量。多线程

public class Test {
    public static void main(String[] args) {
        {
            byte[] temp = new byte[64 * 1024 * 2014];
        }
        System.gc();
    }
}
[GC (System.gc())  131353K->129368K(245248K), 0.0028307 secs]
[Full GC (System.gc())  129368K->129283K(245248K), 0.0053750 secs]

public class Test {
    public static void main(String[] args) {
        {
            byte[] temp = new byte[64 * 1024 * 2014];
        }
        int a=1;
        System.gc();
    }
}
[GC (System.gc())  131353K->129336K(245248K), 0.0049695 secs]
[Full GC (System.gc())  129336K->387K(245248K), 0.0109887 secs]

能够看到上面两断代码的gc效果「jvm启动参数:-verbose:gc」,当声明局部变量temp数组的时候,内存占用了一部分空间,可是第一段代码的gc并无回收没有引用的temp数组。第2段进行的正常的垃圾回收。这里就是变量槽的复用。主要是根据index来进行。只要被局部变量表中直接或者间接引用的对象都不会被回收.下面再看一个变量槽复用的代码。jvm

public static void main(String[] args) {
        {
            int a = 3;
            System.out.println(a);
        }
        int c = 3;
        System.gc();
    }

经过jclasslib分析。能够看到下面这图:函数

images/mer.png

images/mer.png

变量槽的index1复用过2次。第一次是a定义的时候,当局部变量失去引用的时候,把index1又给了c进行复用。

操做数栈

  • 和局部变量区同样,操做数栈也是被组织成一个以字长为单位的数组。可是和前者不一样的是,它不是经过索引来访问,而是经过标准的栈操做—压栈和出栈—来访问的。好比,若是某个指令把一个值压入到操做数栈中,稍后另外一个指令就能够弹出这个值来使用。ui

  • 虚拟机在操做数栈中存储数据的方式和在局部变量区中是同样的:如int、long、float、double、reference和returnType的存储。对于byte、short以及char类型的值在压入到操做数栈以前,也会被转换为int。this

  • 虚拟机把操做数栈做为它的工做区——大多数指令都要从这里弹出数据,执行运算,而后把结果压回操做数栈。好比,iadd指令就要从操做数栈中弹出两个整数,执行加法运算,其结果又压回到操做数栈中,看看下面的示例,它演示了虚拟机是如何把两个int类型的局部变量相加,再把结果保存到第三个局部变量的spa

begin  
    iload_0    // push the int in local variable 0 onto the stack  
    iload_1    // push the int in local variable 1 onto the stack  
    iadd       // pop two ints, add them, push result  
    istore_2   // pop int, store into local variable 2  
    end

动态链接

此处为空,没有太理解

方法返回地址

当方法返回时,可能进行3个操做:

  • 恢复上层方法的局部变量表和操做数栈

  • 把返回值压入调用者调用者栈帧的操做数栈

  • 调整 PC 计数器的值以指向方法调用指令后面的一条指令

附加信息

虚拟机规范并无规定具体虚拟机实现包含什么附加信息,这部分的内容彻底取决于具体实现。在实际开发中,通常会把动态链接,方法返回地址和附加信息所有归为一类,称为栈帧信息。

本地方法栈

本地方法栈与虚拟机栈做用类似,区别为虚拟机栈为虚拟机执行java方法服务,本地方法栈则为虚拟机使用到的native方法服务。

java堆是java虚拟机所管理内存中最大的一块。java堆是全部线程共享的一块内存区域,在虚拟机启动时建立,堆(Heap)的惟一目的就是存放对象实例,全部的对象实例以及数组都要在堆上分配,同时堆也是垃圾收集器管理的主要区域。从内存回收来看,如今的收集器都采用分代收集算法,因此java堆中还能够分为「新生代」和「老年代」,再细致能够分为eden空间,From Survivor空间,To Survivor空间等.根据java虚拟机规范的规定,java堆能够处于物理上不连续的内存空间中,只要逻辑上是连续的便可。当前主流的虚拟机都是按照可扩展来实现的(经过Xmx和Xms)。若是堆中没有内存完成实例分配而且堆也没法扩展的时候,就会出先OOM异常。

内存划分

enter description here

memery.png

堆内存

堆内存大小经过-Xms -Xmx来指定大小,堆内存分为新生代和老年代。经过-Xmx/-Xms来分配最大和最小堆内存。

新生代
  • 新生代和老年代默认的空间比例为1:2。能够经过-XX:NewRatio来配置,设置-XX:NewRation=3表示年轻代与老年代的比是1:3即年轻代占年轻代+老年代内存的4分之1.

  • 新生代中细分为eden和From Survivor和To Survivor三个区。默认eden:FromSurvivor:ToSurvivor=8:1:1。

  • 发生在新生代的是Minor GC,采用的是复制算法。

eden
  • 通常新建立的对象都会分配到eden区(对于一些较大的对象 ( 即须要分配一块较大的连续内存空间 ) 则是直接进入到老年代。)。当这些对象通过第一次Minor gc后,若是仍然存活的会被移动到survivor区。由于java中建立的对象基本是朝生夕死(80%),第一次Minor gc会回收不少的对象。

  1. MinorGC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。

  2. 当 JVM 没法为一个新的对象分配空间时会触发 Minor GC,好比当 Eden 区满了。因此分配率越高,越频繁执行 Minor GC。

  3. 内存池被填满的时候,其中的内容所有会被复制,指针会从0开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操做,取代了经典的标记、扫描、压缩、清理操做。因此 Eden 和 Survivor 区不存在内存碎片。写指针老是停留在所使用内存池的顶部。

  4. 执行 Minor GC 操做时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉

  5. 全部的 Minor GC 都会触发“stop-the-world”,中止应用程序的线程。对于大部分应用程序,停顿致使的延迟都是能够忽略不计的。其中的真相就 是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。若是正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长不少。

survivor

survivor分为From Survivor和To Survivor两个区。
这两个区总有一个是空的。在GC开始的时候,在eden中回收一些瞬时对象,剩下的存活对象会复制到To区域中(对象年龄+1)。而在From区中,存活的对象根据年龄来决定去向,年龄到达必定值的时候,对象会被移动到老年代中,没有达到必定值的时候,对象会移动到To区,而且年龄+1.通过这次GC。eden区和From区已经被清空。下次GC的时候,会把对象从To移动到From区中,就会变成eden和To区清空。每次GC进行重复上面的复制回收动做。使From或To区总有一个是清空的。当To 或 From区一个被填满以后,会将全部对象移动到老年代中。

  1. -XX:SurvivorRatio:设置eden和survivor的比值。-XX:SurvivorRatio=3表示eden:Survivor=3:2;eden占5分之三,From/To Survivor各占五分之一

  2. -XX:+PrintTenuringDistribution:显示每次Minor GC时Survivor区中各个年龄段的对象的大小

  3. -XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold:晋升到老年代的对象年龄的最小值和最大值。每一个对象在坚持过一次Minor GC以后,年龄就加0

老年代
  • 老年代发生的GC称为MajorGC,采用的是标记-清除算法。

  • 标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后须要为较大的对象分配内存空间时,若没法找到足够的连续的内存空间,就会提早触发一次 GC 的收集动做

  1. -XX:+PrintGCDetails:控制台显示 GC 相关的日志信息

  2. Full GC == Major GC指的是对老年代/永久代的stop the world的GC

  3. FullGC与MajorGC的区别{:target="_blank"}

方法区

方法区与java堆同样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。它还有一个别名叫Non-Heap(非堆)。对于HotSpot虚拟机。他还有一个别名,叫作永久代。在java8以前,能够经过-XX:MaxPermSize来设置方法区的上限。

  1. 在java8以前,若是没法分配内存会抛出OOM异常。能够经过上面的命令来增长上限值

  2. 在java8中,HotSpot已经移除了这个方法区。有了一个新的「元空间」来替代方法区。

元空间

快速入门

  • 它是本地堆内存中的一部分

  • 它能够经过-XX:MetaspaceSize和-XX:MaxMetaspaceSize来进行调整

  • 当到达XX:MetaspaceSize所指定的阈值后会开始进行清理该区域
    若是本地空间的内存用尽了会收到java.lang.OutOfMemoryError: 「Metadata space」或「Java heap space」的错误信息。
    和持久代相关的JVM参数-XX:PermSize及-XX:MaxPermSize将会被忽略掉,而且在启动的时候给出警告信息。

  • 充分利用了Java语言规范中的好处:类及相关的元数据的生命周期与类加载器的一致

内存分配模型

  • 绝大多数的类元数据的空间都从本地内存中分配

  • 用来描述类元数据的类也被删除了,分元数据分配了多个虚拟内存空间

  • 给每一个类加载器分配一个内存块的列表,只进行线性分配。块的大小取决于类加载器的类型, sun/反射/代理对应的类加载器的块会小一些。

  • 不会单独回收某个类,若是GC发现某个类加载器再也不存活了,会把相关的空间整个回收掉。这样减小了碎片,并节省GC扫描和压缩的时间。

调优

  • 使用-XX:MaxMetaspaceSize参数能够设置元空间的最大值,默认是没有上限的,也就是说你的系统内存上限是多少它就是多少。

  • 使用-XX:MetaspaceSize选项指定的是元空间的初始大小,若是没有指定的话,元空间会根据应用程序运行时的须要动态地调整大小。

  • 一旦类元数据的使用量达到了“MaxMetaspaceSize”指定的值,对于无用的类和类加载器,垃圾收集此时会触发。为了控制这种垃圾收集的频率和延迟,合适的监控和调整Metaspace很是有必要。过于频繁的Metaspace垃圾收集是类和类加载器发生内存泄露的征兆,同时也说明你的应用程序内存大小不合适,须要调整。

相关文章
相关标签/搜索