JVM-运行时数据区

摘要

  • 自从java面世以来,声势浩大,提出“Write Once,Run Anywhere";Java相比于其余C/C++语言的优点:在JVM内存管理之下,再也不须要为每个new操做去手动分配内存和free/delete的内存释放;不容易出现内存泄漏和内存溢出等问题。
  • 本节主要讲解Java运行时数据区:线程共享数据区:方法区、堆 线程隔离数据去:虚拟机栈、本地方法栈、程序计数器

思惟导图

image.png

内容

一、运行时数据区包含哪几部分?

image.png
java虚拟机运行时数据区主要包含如下几个模块?java

  • 线程共享数据区:方法区、堆
  • 线程隔离数据区:虚拟机栈、本地⽅法栈、堆、程序计数器

程序计数器:用来记录字节指令的行号;咱们将.java文件编译成.class文件后,交由JVM去执行的时候,程序一行一行执行就是交给程序计数器去作的
Java虚拟机栈:好比咱们写一个方法,JVM执行这个方法的时候,相似于建立了一个栈针;入栈到出栈就是这个方法调用的整个过程;对应的就是一个方法一个栈。
本地方法栈:就是JVM虚拟机执行一些本地方法库;咱们在进行一个CAS操做的时候:经过unsafe的compareAndSwapInt调到本地方法库里面的native方法。那么这些native方法就是在本地方法栈里面运行的。
方法区:存放类信息,类变量,静态变量。
堆:几乎全部数组跟对象的建立都是在堆里面。c++

二、程序计数器

是什么?

程序计数器(Program Counter Register)是一块较小的内存空间,是当前线程所执行的字节码行号指示器程序员

为何?

  1. 字节码解释器工做时候经过改变程序计数器的值来选取下一条要执行的字节码指令;线程的各个基础功能都须要依赖这个计数器来完成。
  2. java虚拟机多线程是经过线程轮流切换并分配处理器执行的时间实现,在任意一个肯定的时刻,一个处理器都只会执行一条线程指令,所以为了线程切换后能恢复到正确的执行位置。每一个线程都须要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储。咱们称这类内存区域为“线程私有”的内存。

特色?

内存区域中惟⼀⼀个在Java虚拟机规范中没有规定任OutOfMemoryError 状况的区域。由于程序计数器自己不须要咱们程序员去操做,因此不会出现OOM。数据库

实战演练

咱们建立一个Person类;拥有属性age,提供getter/setter方法。数组

public class Person {
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    private int age;
}

咱们使用javac进行编译源代码为字节码,而后使用javap查看字节码,以下:
image.png数据结构

image.png

咱们经过javap -l能够看到字节码的内容;咱们看到里面有两个方法:setAge;getAge;而后咱们看到getAge方法在第5行。setAge在8,9行。如今若是咱们须要执行Person里面的getAge方法;咱们说到,这个时候咱们可能多线程来执行这个方法;因此这块运行时数据库是一块独立的内存;咱们知道程序计数器是一块线程隔离的数据库,因此每块线程有本身独立的程序计数器。这块内存是在咱们的线程里面单独隔离开来的,不一样的线程维护了本身不一样的程序计数器。多线程

三、java虚拟机栈

上一节咱们讲解了程序计数器;程序计数器是线程私有的一块小内存,线程私有的内存除了程序计数器以外还有两块:java虚拟机栈,本地方法栈。方法区跟堆相对的就是线程共享的内存。jvm

是什么?

Java方法执行的一块内存区域;随着线程方法执行时候压栈出栈,此内存区域也会销毁,他是跟线程的生命周期相同的。this

为何?

方法的执行是按照栈数据结构;每一个方法在执行的同时都会建立一个栈帧(Stack Frame)用于存放局部变量表、操做数栈、动态连接、⽅法出⼝等信息。每⼀个⽅法从调⽤直⾄执⾏完成的过程,就对应着⼀个栈帧在虚拟机栈中⼊栈到出栈的过程。spa

特色?

  1. 局部变量表存放了编译期可知(指代咱们这些基础数据类型他所对应的数据的大小,大小是可知道的)的各类基本数据类型(boolean、byte、char、short、int、 float、long、double)以及对象引⽤(reference 类型)
  2. 若是线程请求的栈深度⼤于虚拟机所容许的深度,将抛出 StackOverflowError异常。

实战

public class StackDemo {
    public static void a(){
        System.out.println("method a executed");
    }
    public static void b(){
        //a();
        b();
        System.out.println("method b executed");
    }
    public static void main(String[] args){
       b();
        System.out.println("method main executed");
    }
}

咱们修改b()的调用为本身。上面进行了递归调用:b()方法执行不断入栈操做,而没有出栈,致使所分配的栈内存不够,从而出现栈内存溢出。栈长度超过制定长度大小。结果以下:

image.png

四、本地⽅法栈

java运行时数据区里面的java虚拟机栈的讲解;JVM运行时数据区里面java虚拟机栈、本地方法栈、程序计数器这3快的内存是线程私有的。他的生命周期跟线程的生命周期是同样的。java虚拟机栈跟本地方法栈是很类似的。他们的区别无非就是:java虚拟机栈他执行的是java方法,他会被编译成字节码。本地方法栈执行的是native方法。那么什么是native方法?native是一个修饰符。native方法比较特殊,他不容许有方法体。咱们native方法执行的时候,他会调用CAS(CAS是cpu的原子指令)。

是什么?

native方法执行的一块内存区域。

为何?

咱们java程序须要调用native方法从而调用cpu指令。知足CAS原子性操做

特色?

  1. 与java虚拟机栈同样也会抛出Stackoverflow.
  2. java虚拟机栈跟本地方法栈差很少,Hotshot将Java虚拟机栈和本地⽅法栈合⼆为⼀;咱们上面讲解的java运行时数据区是jvm标准,而Hotshot只是jvm虚拟机的实现。

image.png
咱们经过java -version能够查看使用的JVM类型。

实战

本地方法实战,在咱们juc里面会有一些原子类:Atomic相关类,好比:以AtomicInteger为例子。
image.png
咱们能够看到上面demo;原子类进行CAS操做时候会调用底层native方法。
image.png
native方法就是调用java语言以外的语言,好比c语言/c++语言;咱们调用其余语言的话,咱们将这个方法用关键字:native来修饰。java里面原子类都是基于native方法调用cpu指令的。

五、java堆

上一节咱们讲解了java运行时数据区里面本地方法栈的讲解;咱们讲解了java虚拟机栈、本地方法栈、程序计数器的讲解,这三块是java运行时候数据区里面的线程独占区内存。那么除了线程独占区内存以外。咱们图示左边是线程共享区:方法区跟堆。咱们先讲解java运行时数据区里面的线程共享区:java堆。

是什么?

Java中⽤来存放对象实例的最大一块内存区域,【⼏乎全部的对象实例都在这⾥分配内
存】

为何?

  1. 内存区域的惟⼀⽬的就是存放对象实例。
  2. Java 堆(Java Heap)是 Java 虚拟机所管理的内存中最⼤的⼀块;Java 堆是被全部线程共享的⼀块内存区域。

特色?

  1. Java 堆是垃圾收集器管理的主要区域,所以不少时候也被称作“GC 堆”(Garbage Cash)。
  2. 经过:-Xmx -Xms。(最大堆内存、最小堆内存)。
  3. Java堆能够分红新⽣代和⽼年代 新⽣代可分为To Survivor、From Survivor、Eden。

实战

咱们运行一个SpringBoot项目;经过ps或者jps查看其对应的pid。
image.png

而后经过指令:jmap -heap 13027;
image.png

Heap Configuration:
//对应jvm启动参数-XX:MinHeapFreeRatio设置JVM堆最小空闲比率(default 40)
   MinHeapFreeRatio         = 0
//对应jvm启动参数 -XX:MaxHeapFreeRatio设置JVM堆最大空闲比率(default 70)
   MaxHeapFreeRatio         = 100
//对应jvm启动参数-XX:MaxHeapSize=设置JVM堆的最大大小
   MaxHeapSize              = 16848519168 (16068.0MB)
//对应jvm启动参数-XX:NewSize=设置JVM堆的‘新生代’的默认大小
   NewSize                  = 351272960 (335.0MB)
//对应jvm启动参数-XX:MaxNewSize=设置JVM堆的‘新生代’的最大大小
 MaxNewSize               = 5616173056 (5356.0MB)
//对应jvm启动参数-XX:OldSize=<value>:设置JVM堆的‘老生代’的大小
   OldSize                  = 703594496 (671.0MB)
 //对应jvm启动参数-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
   NewRatio                 = 2
//对应jvm启动参数-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage://堆内存分步
PS Young Generation
Eden Space://Eden区内存分布
   capacity = 117964800 (112.5MB)  //Eden区总容量
   used     = 27181312 (25.922119140625MB) //Eden区已使用
   free     = 90783488 (86.577880859375MB)  //Eden区剩余容量
   23.041883680555557% used //Eden区使用比率 
From Space: //其中一个Survivor区的内存分布
   capacity = 1572864 (1.5MB)
   used     = 950272 (0.90625MB)
   free     = 622592 (0.59375MB)
   60.416666666666664% used
To Space://另外一个Survivor区的内存分布 
   capacity = 1572864 (1.5MB)
   used     = 0 (0.0MB)
   free     = 1572864 (1.5MB)
   0.0% used
PS Old Generation //当前的Old区内存分布
   capacity = 1331167232 (1269.5MB)
   used     = 115252040 (109.91291046142578MB)
   free     = 1215915192 (1159.5870895385742MB)
   8.657968527879147% used

61594 interned Strings occupying 6891128 bytes.

六、⽅法区

上一节咱们讲解了java运行时数据区块里面线程私有的:java虚拟机栈、本地方法栈、程序计数器;以及线程间共享的数据区:java堆。这一节咱们讲解线程共享的数据区方法区:

是什么?

线程共享的用于存储已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据的内存区域。

为何?

为了存放长久存在的极少被垃圾回收的常量。Hotspot使⽤永久代来实现⽅法区 JRockit、IBM J9VM Java堆⼀样为了管理这部份内存。

特色?

  1. 并⾮数据进⼊了⽅法区就如永久代的名字⼀样“永久”存在了。这区域的内存回收⽬标主要是针对常量池的回收和对数据类型的卸载。---因此咱们把它叫作部分永久。
  2. ⽅法区也会抛出OutofMemoryError,当它⽆法满⾜内存分配需求时(当咱们的方法区没法知足内存分配时候),虽然咱们的方法区的内存没有堆内存那么大,可是当咱们的方法去里面的类信息比较庞大时候就会出现OOM。

七、常量池

是什么?

运⾏时常量池是⽅法区的⼀部分,Class⽂件除了有类的版本、字段、⽅法、接⼝等描述信息外,还有⼀项信息是常量池,⽤于存放编译器⽣成的各类字⾯量和符号引⽤,这部份内容将在类被加载后进⼊⽅法区的运⾏时常量池中存放。

什么是类信息:类版本号、⽅法、接⼝。
咱们打开class文件时候:咱们能够看到开头是cafe babe(由于咱们的java的图标是一杯咖啡),里面就是一些类版本号、⽅法、接⼝信息。

为何?

用户存放共有的常量数据。

特色?

运⾏时常量池是⽅法区的⼀部分,受到⽅法区内存的限制,当常量池再申请到内存时会抛出OutOfMemoryError异常。

实例

image.png
咱们经过代码发现:a跟b是相等的,若是说a跟b是存在在堆内存,那么将会建立不一样的对象,开辟不一样的空间,那么这个时候a跟b确定是不相等的。因此咱们知道a跟b是存放在方法区。而且经过源码发现String类型的都是常量,常量是存放在方法区的。

image.png

image.png

image.png

相关文章
相关标签/搜索