本篇文章主要讲解Java(JVM)在运行期间,其运行时数据区域的作用、职责与划分。包括堆内存、栈内存——虚拟机栈、本地方法栈、方法区、常量池、程序计数器等概念。

1. JVM内存区块教程

1. JVM内存区块教程

https://zhuanlan.zhihu.com/p/44694290


1、程序计算器:

=================================

Java虚拟机是可以多线程并发执行的。对于一个CPU任意时刻,只能执行JVM中的一条线程。这就意味着,JVM要想实现多线程,则每条线程必须有独立的程序计数器用来标记线程执行的指令的位置(线程切换后恢复到正确的执行位置)。所以——程序计数器是线程私有的,私有意味着各条线程之间的计数器互不影响。

程序计数器(Program Counter Register),是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型中字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖于此计数器。JVM中的程序计数器也是在Java虚拟机规范中唯一一个没有规定任何OutOfMemoryError情况的区域。在任意时刻一个JVM线程只能执行一个方法的代码,方法可以是Java方法,或者是native方法。

此处还有2点需注意:

1.Java虚拟机中的程序计数器仅仅是虚拟机中的,存在于内存之上的“虚拟”计数器,而不是电脑中的实体程序计数器

2.JVM线程中执行的方法有2种类型:普通Java方法和由其他语言实现的native方法。如果当前执行的是普通Java方法,则程序计数器记录的是虚拟机字节码指令的地址。如果当前执行的是native方法,则计数器的值为空(Undefined)。

2Java方法栈(也称为 Java虚拟机栈(Java Virtual Machine Stacks))

=========================================================================================

Java方法栈和线程同时创建,用于存储栈帧。每个方法在执行时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直到执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

Java虚拟机规范中Java虚拟机栈内存的大小既可以被实现成固定大小,也可以根据计算动态拓展或收缩,当前大部分的JVM实现是支持动态拓展的。Java虚拟机栈可能发生的异常:

1.线程请求分配的栈容量>Java虚拟机最大栈容量,则JVM会抛出StackOverFlowError异常。(例子:方法递归调用时,Java方法栈不够用:https://blog.csdn.net/u011983531/article/details/79563162

2.如果Java虚拟机可动态拓展,则如果在拓展的过程中无法申请到足够的内存,就会抛出OutOfMemoryError异常。

3、本地方法栈

================================

4Java

====================================

前面所说的程序计数器、Java虚拟机栈、本地方法栈通常只占很小一部分的内存空间,对与大多数应用来说,Java堆(Java Heap)才是JVM管理的内存空间中最大的一块。此区域存在的唯一目的就是存放对象实例,几乎所有的对象实例都会在这被分配内存,而且Java堆是被所有线程共享的一块内存区域。

Java堆是Java中垃圾收集器管理的主要区域,因此也被称为GCGarbage Collected Heap.

关于垃圾堆的问题O不,GC堆的问题,我们以后会经常见到,著名的垃圾回收算法我们也会再之后的文章中详细探讨。

(根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存之中,只要逻辑上是连续的即可。且和栈一样,不同的JVM实现可以有不同的内存分配策略,Java堆的内存既可以设计成固定大小,也可以是可动态拓展的。)

5、方法区

==============================

方法区(Method Area),与Java堆一样是各个线程共享的内存区域。用于存储被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范将方法区描述为堆的一个逻辑部分,但是它却有个别名叫做Non-Heap(非堆),目的就是和Java堆区分开来。

Java虚拟机规范对方法区的限制十分宽松,除了和Java堆一样不需要连续的内存空间分配和可选择固定大小或可拓展内存以外,方法区也可以被垃圾回收器管理或不受其管理。

首先,要明确一个「概念」——方法区,是一个概念,是Java虚拟机规范中定义的概念,一个「非堆」的运行时数据区域,用于存放被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,运行时常量池也是存放于方法区中。逻辑上的「非堆」表示和Java堆独立,那物理上呢?Java虚拟机规范中定义了方法区这个概念,但是并没有规定此区域的是否需要垃圾收集。

Java7以前,HotSpot虚拟机中,方法区也被称为“永久代”,因为在物理上,方法区使用的是由JVM开辟的堆内存,由于和Java堆共享内存且内存空间由垃圾收集器统一分配和管理,自然的垃圾收集也拓展到了方法区上。此时,Java堆中分区为青年代Young Generation和老年代Old Generation,而方法区自然地被称为永久代Permanent Generation 。

(JVM虚拟机有不同的实现,比较主流的是sun公司的HotSpot虚拟机,在此才有“永久代的概念”,其他虚拟机不存在“永久代”这个概念)

Java8中,HotSpot虚拟机改变了原有方法区的物理实现,将原本由JVM管理内存的方法区的内存移到了虚拟机以外的计算机本地内存,并将其称为元空间(Metaspace)。这样一来,现在的方法区实际存储在于元空间,再也不用和Java堆共享内存了,“永久代”也就永久地被撤销了。

尽管永久代撤销了,方法区这个逻辑上的空间一直是存在的,所以在java8以后,方法区的垃圾回收在物理上就是对元空间的垃圾回收。由于元空间用的是计算机本地内存,所以理论上来说只要内存足够大,方法区就能有多大,实际上Metaspace的大小是可以通过参数设定的,如果Metaspace的空间占用达到了设定的最大值,那么就会触发GC来收集死亡对象和类的加载器。常用的G1和CMS垃圾收集器都能很好地回收Metaspace区。

5.1运行时常量池

运行时常量池(Runtime Constant Pool)是.class文件中每一个类或接口的常量池表(constant pool table)的运行时表示形式,属于方法区的一部分。每一个运行时常量池都在Java虚拟机的方法区中分配,在加载类和接口道虚拟机后,就创建对应的运行时常量池。常量池的作用是:

存放编译器生成的各种字面量和符号引用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建或运行时解析、翻译到具体的内存地址之中。

字面量(Literal),通俗理解就是Java中的常量,如文本字符串、声明为final的常量值等。

符号引用(Symbolic References)则是属于编译原理中的概念,包括了下面三类常量:

1.类和接口的全限定名

2.字段的名称和描述符

3.方法的名称和描述符

JVM基础 -- 基本类型

http://zhongmingmao.me/2018/12/15/jvm-basic-native-type/

public class Foo{

public static void main(String[] args) {

boolean flag = true;

if (flag) System.out.println("Hello, Java!");

if (flag == true) System.out.println("Hello, JVM!");

}

}

Java 的基本类型除了上面提到的 boolean 类型外,Java 的基本类型还包括整数类型 byte、short、char、int 和 long,以及浮点类型 float 和 double。

Java 的基本类型都有对应的值域和默认值。可以看到,byte、short、int、long、float 以及 double 的值域依次扩大,而且前面的值域被后面的值域所包含。因此,从前面的基本类型转换至后面的基本类型,无需强制转换。另外一点值得注意的是,尽管他们的默认值看起来不一样,但在内存中都是 0。

标签: Java, 虚拟机, 内存, 方法, 线程, JVM, 区块

相关文章推荐

添加新评论,含*的栏目为必填