Jvm - 详解一 运行时数据区

一.为什么说Java是一门跨平台语言

Java内置JVM (java虚拟机),而JVM也是一个软件,不同的平台有不同的版本。我们编写的Java源码,编译后会生成一种 .class 文件,称为字节码文件。Java虚拟机就是负责将字节码文件翻译成特定平台下的机器码然后运行。也就是说,只要在不同平台上安装对应的JVM,就可以运行字节码文件,运行我们编写的Java程序。

二.java程序从编译到执行的过程

先看一幅图

从图中可以看出,我们编写的Java文件,经由javac编译器,编译后文件变成class文件,由类加载器加载到JVM当中,执行引擎会完成字节码编译成二进制机器码并执行。

注:执行引擎是包含字节码解释器和JIT编译器的。

三.Jvm的大致运行过程

四.我们学Jvm主要是学得什么

1.学的是Java程序在运行时,程序数据在内存中的管理

2.JVM 会对我们的应用程序的数据,进行分类管理,我们要知道什么数据放到什么地方,以及什么时候会被 回收,什么时候会被腾挪

五.运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域

线程私有数据区

1.虚拟机栈(储放栈帧的)

1.存储当前线程运行方法所需的数据,指令、返回地址

2.栈是什么样的数据结构?先进后出(FILO)的数据结构。

3.虚拟机栈在JVM运行过程中存储当前线程运行方法所需的数据,指令、返回地址。

4.Java 虚拟机栈是基于线程的。哪怕你只有一个 main() 方法,也是以线程的方式运行的。在线程的生命周 期中,参与计算的数据会频繁地入栈和出栈,栈的生命周期是和线程一样的。

5.栈里的每条数据,就是栈帧。在每个 Java 方法被调用的时候,都会创建一个栈帧,并入栈。一旦完成相 应的调用,则出栈。所有的栈帧都出栈后,线程也就结束了。

6.每个栈帧,都包含四个区域:(局部变量表、操作数栈、动态连接、返回地址)

7.栈的大小缺省为1M,可用参数 –Xss调整大小,例如-Xss256k

栈帧

一个方法就是一个栈帧

局部变量表:    顾名思义就是局部变量的表,用于存放我们的局部变量的。首先它是一个32位的长度,主要存放我们 的Java的八大基础数据类型,一般32位就可以存放下,如果是64位的就使用高低位占用两个也可以存 放下,如果是局部的一些对象,比如我们的Object对象,我们只需要存放它的一个引用地址即可。

操作数栈 : 存放我们方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操 作的的元素可以是任意的java数据类型,所以我们知道一个方法刚刚开始的时候,这个方法的操作数 栈就是空的,操作数栈运行方法就是JVM一直运行入栈/出栈的操作

动态连接:Java语言特性多态(需要类运行时才能确定具体的方法)。

返回地址:正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表来确 定)

2.程序计数器

1.指向当前线程正在执行的字节码指令的地址。

2.较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响。 程序计数器是 一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线 程恢复等都依赖于计数器。 由于 Java 是多线程语言,当执行的线程数量超过 CPU 核数时,线程之间会 根据时间片轮询争夺 CPU 资源。如果一个线程的时间片用完了,或者是其它原因导致这个线程的 CPU 资 源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令。 程序计数 器也是JVM中唯一不会OOM(OutOfMemory)的内存区域。

3.本地方法栈

1.本地方法栈保存的是native方法的信息

2.当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法

3.虚拟机规范无强制规定,各版本虚拟机自由实现HotSpot直接把本地方法栈和虚拟机栈合二为一

线程共享数据区

1.方法区

用来存储类元信息、常量、静态变量、即时编译期编译后的代码

补充:方法区/永久代

1. 很多开发者都习惯将方法区称为“永久代”,其实这两者并不是等价的。

2. HotSpot 虚拟机使用永久代来实现方法区,但在其它虚拟机中,例如,Oracle 的 JRockit、IBM 的 J9 就 不存在永久代一说。因此,方法区只是 JVM 中规范的一部分,可以说,在 HotSpot 虚拟机中,设计人员 使用了永久代来实现了 JVM 规范的方法区。

3. 方法区主要是用来存放已被虚拟机加载的类相关信息,包括类信息、静态变量、常量、运行时常量池、字 符串常量池。

4. JVM 在执行某个类的时候,必须先加载。在加载类(加载、验证、准备、解析、初始化)的时候,JVM 会先加载 class 文件,而在 class 文件中除了有类的版本、字段、方法和接口等描述信息外,还有一项信 息是常量池 (Constant Pool Table),用于存放编译期间生成的各种字面量和符号引用。

5. 字面量包括字符串(String a=“b”)、基本类型的常量(final 修饰的变量),符号引用则包括类和方法 的全限定名(例如 String 这个类,它的全限定名就是 Java/lang/String)、字段的名称和描述符以及方 法的名称和描述符。

6. 而当类加载到内存中后,JVM 就会将 class 文件常量池中的内容存放到运行时的常量池中;在解析阶段, JVM 会把符号引用替换为直接引用(对象的索引值)。

7. 例如,类中的一个字符串常量在 class 文件中时,存放在 class 文件常量池中的;在 JVM 加载完类之后, JVM 会将这个字符串常量放到运行时常量池中,并在解析阶段,指定该字符串对象的索引值。运行时常量 池是全局共享的,多个类共用一个运行时常量池,class 文件中常量池多个相同的字符串在运行时常量池 只会存在一份。

8. 方法区与堆空间类似,也是一个共享内存区,所以方法区是线程共享的。假如两个线程都试图访问方法区 中的同一个类信息,而这个类还没有装入 JVM,那么此时就只允许一个线程去加载它,另一个线程必须等 待。在 HotSpot 虚拟机、Java7 版本中已经将永久代的静态变量和运行时常量池转移到了堆中,其余部 分则存储在 JVM 的非堆内存中,而 Java8 版本已经将方法区中实现的永久代去掉了,并用元空间(class metadata)代替了之前的永久代,并且元空间的存储位置是本地

9. 元空间大小参数:

jdk1.7及以前(初始和最大值):-XX:PermSize;-XX:MaxPermSize;

jdk1.8以后(初始和最大值):-XX:MetaspaceSize; -XX:MaxMetaspaceSize

jdk1.8以后大小就只受本机总内存的限制(如果不设置参数的话)

2.堆

用来存放对象实例(几乎所有)与数组 

java堆得大小参数设置:

-Xmx 堆区内存可被分配的最大上限

-Xms 堆区内存初始内存分配的大小

-Xmn:新生代的大小;

-XX:NewSize;新生代最小值;

-XX:MaxNewSize:新生代最大值;

例如- Xmx256m

JVM参数参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

补充:

1.堆是 JVM 上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的。我们常说的垃圾回收, 操作的对象就是堆。

2.堆空间一般是程序启动时,就申请了,但是并不一定会全部使用。

3.随着对象的频繁创建,堆空间占用的越来越多,就需要不定期的对不再使用的对象进行回收。这个在 Java 中,就叫作 GC(Garbage Collection)。

4.那一个对象创建的时候,到底是在堆上分配,还是在栈上分配呢?这和两个方面有关:对象的类型和在 Java 类中存在的位置。

5.Java 的对象可以分为基本数据类型和普通对象。

6.对于普通对象来说,JVM 会首先在堆上创建对象,然后在其他地方使用的其实是它的引用。比如,把这个 引用保存在虚拟机栈的局部变量表中。

7.对于基本数据类型来说(byte、short、int、long、float、double、char),有两种情况。当你在方法体 内声明了基本数据类型的对象,它就会在栈上直接分配。其他情况,都是在堆上分配。

堆与栈的区别

1.功能

以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、 float、double、boolean、char等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释 放; 而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆 内存中;

2.线程独占还是共享

栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以 理解成线程的私有内存。 堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。

3.空间大小

栈的内存要远远小于堆内存,栈的深度是有限制的,可能发生StackOverFlowError问题。

六.直接内存

1.不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。

2.如果使用了NIO,这块区域会被频繁使用,在Java堆内可以用directByteBuffer对象直接引用并操作。

3.这块内存不受Java堆大小限制,但受本机总内存的限制,可以通过MaxDirectMemorySize来设置(默认 与堆内存最大值一样),所以也会出现OOM异常。


版权声明:本文为m0_47914176原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。