java虚拟机运行时数据区主要包含如下几个模块?java
程序计数器:用来记录字节指令的行号;咱们将.java文件编译成.class文件后,交由JVM去执行的时候,程序一行一行执行就是交给程序计数器去作的
Java虚拟机栈:好比咱们写一个方法,JVM执行这个方法的时候,相似于建立了一个栈针;入栈到出栈就是这个方法调用的整个过程;对应的就是一个方法一个栈。
本地方法栈:就是JVM虚拟机执行一些本地方法库;咱们在进行一个CAS操做的时候:经过unsafe的compareAndSwapInt调到本地方法库里面的native方法。那么这些native方法就是在本地方法栈里面运行的。
方法区:存放类信息,类变量,静态变量。
堆:几乎全部数组跟对象的建立都是在堆里面。c++
程序计数器(Program Counter Register)是一块较小的内存空间,是当前线程所执行的字节码的行号指示器。程序员
内存区域中惟⼀⼀个在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查看字节码,以下: 数据结构
咱们经过javap -l能够看到字节码的内容;咱们看到里面有两个方法:setAge;getAge;而后咱们看到getAge方法在第5行。setAge在8,9行。如今若是咱们须要执行Person里面的getAge方法;咱们说到,这个时候咱们可能多线程来执行这个方法;因此这块运行时数据库是一块独立的内存;咱们知道程序计数器是一块线程隔离的数据库,因此每块线程有本身独立的程序计数器。这块内存是在咱们的线程里面单独隔离开来的,不一样的线程维护了本身不一样的程序计数器。多线程
上一节咱们讲解了程序计数器;程序计数器是线程私有的一块小内存,线程私有的内存除了程序计数器以外还有两块:java虚拟机栈,本地方法栈。方法区跟堆相对的就是线程共享的内存。jvm
Java方法执行的一块内存区域;随着线程方法执行时候压栈出栈,此内存区域也会销毁,他是跟线程的生命周期相同的。this
方法的执行是按照栈数据结构;每一个方法在执行的同时都会建立一个栈帧(Stack Frame)用于存放局部变量表、操做数栈、动态连接、⽅法出⼝等信息。每⼀个⽅法从调⽤直⾄执⾏完成的过程,就对应着⼀个栈帧在虚拟机栈中⼊栈到出栈的过程。spa
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()方法执行不断入栈操做,而没有出栈,致使所分配的栈内存不够,从而出现栈内存溢出。栈长度超过制定长度大小。结果以下:
java运行时数据区里面的java虚拟机栈的讲解;JVM运行时数据区里面java虚拟机栈、本地方法栈、程序计数器这3快的内存是线程私有的。他的生命周期跟线程的生命周期是同样的。java虚拟机栈跟本地方法栈是很类似的。他们的区别无非就是:java虚拟机栈他执行的是java方法,他会被编译成字节码。本地方法栈执行的是native方法。那么什么是native方法?native是一个修饰符。native方法比较特殊,他不容许有方法体。咱们native方法执行的时候,他会调用CAS(CAS是cpu的原子指令)。
native方法执行的一块内存区域。
咱们java程序须要调用native方法从而调用cpu指令。知足CAS原子性操做
咱们经过java -version能够查看使用的JVM类型。
本地方法实战,在咱们juc里面会有一些原子类:Atomic相关类,好比:以AtomicInteger为例子。
咱们能够看到上面demo;原子类进行CAS操做时候会调用底层native方法。
native方法就是调用java语言以外的语言,好比c语言/c++语言;咱们调用其余语言的话,咱们将这个方法用关键字:native来修饰。java里面原子类都是基于native方法调用cpu指令的。
上一节咱们讲解了java运行时数据区里面本地方法栈的讲解;咱们讲解了java虚拟机栈、本地方法栈、程序计数器的讲解,这三块是java运行时候数据区里面的线程独占区内存。那么除了线程独占区内存以外。咱们图示左边是线程共享区:方法区跟堆。咱们先讲解java运行时数据区里面的线程共享区:java堆。
Java中⽤来存放对象实例的最大一块内存区域,【⼏乎全部的对象实例都在这⾥分配内
存】
咱们运行一个SpringBoot项目;经过ps或者jps查看其对应的pid。
而后经过指令:jmap -heap 13027;
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堆⼀样为了管理这部份内存。
运⾏时常量池是⽅法区的⼀部分,Class⽂件除了有类的版本、字段、⽅法、接⼝等描述信息外,还有⼀项信息是常量池,⽤于存放编译器⽣成的各类字⾯量和符号引⽤,这部份内容将在类被加载后进⼊⽅法区的运⾏时常量池中存放。
什么是类信息:类版本号、⽅法、接⼝。
咱们打开class文件时候:咱们能够看到开头是cafe babe(由于咱们的java的图标是一杯咖啡),里面就是一些类版本号、⽅法、接⼝信息。
用户存放共有的常量数据。
运⾏时常量池是⽅法区的⼀部分,受到⽅法区内存的限制,当常量池再申请到内存时会抛出OutOfMemoryError异常。
咱们经过代码发现:a跟b是相等的,若是说a跟b是存在在堆内存,那么将会建立不一样的对象,开辟不一样的空间,那么这个时候a跟b确定是不相等的。因此咱们知道a跟b是存放在方法区。而且经过源码发现String类型的都是常量,常量是存放在方法区的。