初步了解JVM第二篇
在一篇《初步了解JVM第一篇》中,我们已经了解了:
- 类加载器:负责加载*.class文件,将字节码内容加载到内存中。其中类加载器的类型有如下:
- 启动类加载器(Bootstrap)
- 扩展类加载器(Extension)
- 应用程序类加载器(AppClassLoader)
- 用户自定义加载器(User-Defined)
- 执行引擎:负责解释命令,提交给操作系统执行。
- 本地接口:目的是为了融合不同的编程语言提供给Java所用,但是企业中已经很少会用到了。
- 本地方法栈:将本地接口的方法在本地方法栈中登记,在执行引擎执行的时候加载本地方法库
- PC寄存器:是线程私有的,记录方法的执行顺序,用以完成分支、循环、跳转、异常处理、线程恢复等基础功能。
那在这一篇中我们来聊一聊方法区、栈和堆。
5.方法区
在JVM的架构图中,Java栈、本地方法栈、程序计数器都是线程私有的。而方法区跟堆一样,是一个内存共享的区域,他的主要作用就是存储每一个类的结构信息,例如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容。
再简单来说方法区就是一个类的模板,在上一篇我们已经说了ClassLoader将class文件加载完成之后会把类的字节码内容放到方法区中,就像把Car.class文件通过类加载器加载后,会把car这个类的结构信息存放在方法区中。当你要实例化的时候再通过这个模板去new出你想要的car1,car2,car2,而你创建出来这些类对象是存放在堆(heap)中的。
图一是方法区中存放的内容
图一
方法区的实现:
方法区只是一个定义、一个规范。在不同的虚拟机里头实现是不一样的。这里我们主要介绍的是JDK7和JDK8的实现方式
- JDK7:永久代(PermGen space)
- JDK8:元空间(Metaspace)
永久代
在JDK7中方法区的实现方式叫永久代,但是它存储的部分数据是存放在JVM的一块地方的,这会造成一个问题:
当类加载太多了,可能会导致内存栈溢出:java.lang.OutOfMemoryError: PermGen,这样一来就不够灵活,为了提高灵活性(这只是其中一个原因)就有了元空间
元空间:
在JDK8中,JVM的开发者就把永久代移除了,移至元空间中。其实作用是差不多的,只是元空间不再使用JVM的内存了,而是直接使用本地堆内存(native heap),说白了就是直接使用系统的内存,这样就几乎不会发生内存溢出的情况,提高了灵活性。
所以为什么在网上会看到关于方法区很多不同的说法就是因为方法区的实现方式在不同的JVM中是不同,最典型的就是永久代和元空间。
以上我们总结出:
- 方法区:类似一个模板,存储一个类的结构信息。
- 实现方法:
- 永久代:使用JVM的内存。
- 元空间:使用系统内存。
以上就是方法区的介绍,在介绍堆的时候还会提及。
6.Stack栈
栈是一个线程私有的,主要用来管理Java程序的运行。是在线程创建的时候创建的,它的生命周期跟随这线程的结束而结束,当线程结束了栈的内存也就释放了,对于栈来说,不会存在垃圾回收问题,因为只要线程一结束该栈就结束了。
栈中主要存储的内容:
- 8种基本数据类型
- 对象的引用变量
- 实例方法
栈就类似一个子弹夹,它的特点就是“后进先出,先进后出”,在Java中需要实现很多方法,而这些方法就是一个一个被压进栈中的,然后再依次调用。在平常中,我们所说的Java中的方法在栈其实有一个专有名词叫栈帧,栈帧主要存放三类数据:
- 本地变量(Local Variables):输入参数和输出参数以及方法内的变量。
- 栈操作(Operand Stack):记录出栈、入栈的操作。
- 栈帧数据(Frame Data):包括类文件、方法等等。
栈运行原理:
Java中的方法存放在栈中,但是这些方法到底是怎么执行的呢?
接下来我们就用一个例子来说明一下:
- package testJVM;
- public class TestStack {
- public static void method_one(){
- System.out.println("This is the method_one");
- }
- public static void method_two(){
- System.out.println("This is the method_two");
- }
- public static void main(String[] args) {
- System.out.println("This is the main method");
- //调用方法一
- method_one();
- //调用方法二
- method_two();
- //输出程序结束
- System.out.println("The program is finish");
- }
- }
以上的运行结果为:
这样的输出结果,相信已经在大家的预料之中,但是这些方法在栈中是怎么运行的呢?废话不说,上图二
图二
我们都知道main方法是一切程序的入口,所以程序一执行碰到的是main方法,main方法就第一个入栈了,所以他们的执行过程是这样的:
- 程序执行碰到第一个方法是main方法,main方法入栈。
- main方法遇到的方法是method_one,将其入栈。
- 再遇到的下一个方法是method_two,将其放入栈。
所以就形成了图二,当他运行的时候:
- 弹出method_two方法,在我们图三中的箭头就是PC寄存器的作用,所以在执行method_two,我们需要调用method_one方法。
- 弹出method_one,下一步,我们看到图二有指针指向main方法。
- 弹出main方法,全部出栈。
这样就形成了类似一条执行链,依次执行了main方法。
总结栈运行原理:
栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧 F1,并被压入到栈中, A方法又调用了 B方法,于是产生栈帧 F2 也被压入栈, B方法又调用了 C方法,于是产生栈帧 F3 也被压入栈, 执行完毕后,先弹出F3栈帧,再弹出F2栈帧,再弹出F1栈帧…… 遵循“先进后出”和“后进先出”原则。每个方法执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。栈的大小和具体JVM的实现有关,通常在256K~756K之间,与等于1Mb左右。
栈溢出
讲完了栈的内容,现在我们来看一个大家在实际开发中会碰到的一个错误,请看下列代码:
- package testJVM;
- public class TestStack {
- public static void method_one(){
- //递归调用
- method_one();
- }
- public static void main(String[] args) {
- method_one();
- }
- }
上述是一个递归调用的例子,现在来执行一下,看看会出现一个什么结果:
相信大家多多少少都会遇到过上述的错误,栈溢出。原因如下:
由于我们的方法method_one一直在递归调用自己,而且并没有停止的条件。所以method_one这个方法就会被一直压入栈中,JVM中的内存又是有限的,上述我们也提到了Java中的栈是随着线程的生命周期结束而结束的,不会存在垃圾回收机制,内存得不到释放而方法又不断的进栈,最终内存不够造成栈溢出的现象。图三
图三
以上就是本人对栈的理解,最后来到了重头戏堆(heap),那就下篇再进行介绍吧,哈哈哈。
在下篇将会介绍:
- 堆(heap)
- GC垃圾回收机制
初步了解JVM第二篇的更多相关文章
- 初步了解JVM第一篇
大家都知道,Java中JVM的重要性,学习了JVM你对Java的运行机制.编译过程和如何对Java程序进行调优相信都会有一个很好的认知. 废话不多说,直接带大家来初步认识一下JVM. 什么是JVM? ...
- JVM 第二篇:垃圾收集器以及算法
本文内容过于硬核,建议有 Java 相关经验人士阅读. 0. 引言 一说到 JVM ,大多数人第一个想到的可能就是 GC ,今天我们就来聊一聊和 GC 关系最大的垃圾收集器以及垃圾收集算法,希望能通过 ...
- 【JVM第二篇--类加载机制】类加载器与双亲委派模型
写在前面的话:本文是在观看尚硅谷JVM教程后,整理的学习笔记.其观看地址如下:尚硅谷2020最新版宋红康JVM教程 一.什么是类加载器 在类加载过程中,加载阶段有一个动作是"通过一个类的全限 ...
- JVM第二篇 类加载子系统
1.内存结构概述 简图 详细 2.类加载器与类加载的过程 类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识[CA FE BA BY ...
- kotlin学习三:初步认识kotlin(第二篇)
上一章熟悉了kotlin基本的变量和函数声明,并明白了如何调用函数.本章再来看一些其他有用的东西 包括: 1. kotlin代码组织结构 2. when语法 3. 循环迭代语法 4. try表达式 1 ...
- 初步了解JVM第三篇(堆和GC回收算法)
在<初步了解JVM第一篇>和<初步了解JVM第二篇>中,分别介绍了: 类加载器:负责加载*.class文件,将字节码内容加载到内存中.其中类加载器的类型有如下:执行引擎:负责解 ...
- Python开发【第二篇】:初识Python
Python开发[第二篇]:初识Python Python简介 Python前世今生 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏 ...
- PHP 性能分析第二篇: Xhgui In-Depth
[前言]这是国外知名博主 Davey Shafik 撰写的 PHP 应用性能分析系列的第二篇,第一篇介绍 Xhprof/Xhgui,第三篇则关注于性能调优实践. 在第一篇中,我们初步介绍了 xhpro ...
- ElasticSearch入门 第二篇:集群配置
这是ElasticSearch 2.4 版本系列的第二篇: ElasticSearch入门 第一篇:Windows下安装ElasticSearch ElasticSearch入门 第二篇:集群配置 E ...
随机推荐
- scss--函数 (Functions)--unitless
(Sass::Script::Value::Bool) unitless($number) Returns whether a number has units. Examples: unitless ...
- vue学习笔记(八)组件校验&通信
前言 在上一章博客的内容中vue学习笔记(七)组件我们初步的认识了组件,并学会了如何定义局部组件和全局组件,上一篇内容仅仅只是对组件一个简单的入门,并没有深入的了解组件当中的其它机制,本篇博客将会带大 ...
- C博客作业05--2019-指针
0.展示PTA总分 1.本章学习总结 1.1 学习内容总结 1.2 本章学习体会 2.PTA实验作业 2.16 -7 输出月份英文名 2.1.1 伪代码 char* getmonth(int n) { ...
- 【浅析】|白话布隆过滤器BloomFilter
通过本文将了解到以下内容: 查找问题的一般思路 布隆过滤器的基本原理 布隆过滤器的典型应用 布隆过滤器的工程实现 场景说明: 本文阐述的场景均为普通单机服务器.并非分布式大数据平台,因为在大数据平台下 ...
- jenkins + maven + nexus + [ svn 或 GitLab 或 GitHub ]
目录 介绍 DevOps平台四大模块 针对DevOps开源项目 Jenkins 介绍 Maven 介绍 maven的核心概念介绍 SVN介绍 Nexus介绍 Maven私服的 个特性: 流程图 环境搭 ...
- java this,super简单理解
*****this****** 表示对当前对象的引用. 作用:1.区分实例变量和局部变量(this.name----->实例变量name) 2.将当前对象当做参数传递给其它对象和方法.利用thi ...
- python中68个内置函数的总结
内置函数 内置函数就是python给你提供的, 拿来直接用的函数, 比如print., input等. 截止到python版本3.6.2 python一共提供了68个内置函数. #68个内置函数 # ...
- 补习系列(20)-大话 WebSocket 与 "尬聊"的实现
目录 一.聊聊 WebSocket 二.Stomp 是个什么鬼 三.SpringBoot 整合 WebSocket A. 引入依赖 B. WebSocket 配置 C. 控制器 D. 前端实现 四.参 ...
- 0-N-0计数的优化写法
采用取余%的写法: int i = 0; while( 1 ) { printf( "%d\n", i ); i = ( i + 1 ) % ( N + 1 );}
- elementUI最新版的el-select使用filterable无效无法匹配正确搜索结果的Bug解决办法
Bug描述: 今天做开发时遇到一个elementUI存在的bug. 当el-select使用filterable功能搜索时,如果你恰巧使用的是微软拼音输入法,那么你有可能会遇到搜索结果和输入的值不匹配 ...