Java虚拟机是JVM类语言的根基,其中动态内存管理和垃圾收集技术是JVM中最重要的特性。本节主要讲述其中的内存管理相关概念。

一 Java虚拟机的基本结构

Java虚拟机结构.jpg

如图所示为Java虚拟机的基本结构,每个模块介绍如下:

类加载子系统

类加载子系统负责从文件或网络中加载Class字节码信息,然后存放于方法区。

方法区

方法区是各个线程共享的内存区域,用于存储虚拟机加载的类变量、常量、静态变量以及即时编译后的代码等数据。

Java堆

在虚拟机启动的时候建立,是Java程序最主要的内存工作区域,几乎所有的对象实例和数组都在Java堆上分配。和方法区一样,是各个线程共享的内存区域。可通过-Xmx和Xms虚拟机参数控制Java堆大小。

垃圾回收系统

垃圾回收是Java虚拟机的重要组成部分,其中的垃圾回收器可以对Java堆、方法区和直接内存进行回收。同时Java堆是回收器的工作重点。

直接内存

在Java的NIO库中,允许Java程序使用直接内存,它是Java堆外直接向系统申请的内存区域。通常情况下该区域的内存访问速度优于Java堆。

Java栈

Java栈是线程私有的,它的生命周期和线程相同。它在线程创建的时候被创建。Java栈中保存帧信息,每个方法创建的时候都会创建一个栈帧,用于存储局部变量、方法参数、操作数栈、方法出口灯信息,和方法的调用返回密切相关。

本地方法栈

和Java栈类似,但其中最大的不同是Java栈用于方法调用,而本地方法栈用于本地方法调用。

PC寄存器

该区域也是每个线层私有的空间。Java虚拟会为每个Java线程创建PC寄存器。当当前执行的方法不是本地方法时,PC寄存器就会指向当前正在被执行的指令。若当前执行的方式是本地方法,则PC寄存器的值为undefined。

执行引擎

执行引擎是虚拟机最核心的组件之一,它负责执行虚拟机的字节码。

二 Java堆

Java堆是和Java应用程序关系最为密切的内存空间。Java堆内存通过垃圾回收机制,垃圾对象会被自动清理,而不需要显示的释放。Java堆分为新生代和老年代。其中新生代存放新生对象或年龄不大的对象,而老年代则存放老年对象。新生代和老年代结构如下图所示:

Java堆结构.png

在大多数情况下,对象首先在Eden区分配,在一次新生代回收后,若对象还存活着则进入S0或S1,在这之后,每经一次新生代回收,若对象还存活着则它的年龄会加1,达到一定年龄后该对象就被认为是老年对象,从而进入老年代。当然这里只是其中一种方式进入老年代,后续文章会有详细叙述。

三 Java栈

Java栈是一块线程私有内存空间。Java栈用于传递每次函数调用的数据。它是一块后进先出的数据结构,在其中保存的主要内容是栈帧。每一次函数调用都会有对应的栈帧压如Java栈,同时函数结束时栈帧被弹出。

Java栈帧.jpg

上图可用下面的代码表示:

public void function1() {

public void function2();

}

public void function2() {

public void function3();

}

public void function3() {

....

}

...

在一个栈帧中至少包含局部变量表、操作数栈和帧数据区几个部分。

但是Java栈空间也不能无限使用下去,它受-Xss参数限制,该参数也决定了函数调用的最大深度。

示例:

public class TestStackDeep {

private static int count = 0;

public static void recursion() {

count++;

recursion();

}

public static void main(String[] args) {

try {

recursion();

} catch (Throwable e) {

System.out.println("deep of calling = " + count);

e.printStackTrace();

}

}

}

上面的代码计算最大栈深度,设置虚拟机参数-Xss256K,其结果为:

deep of calling = 2374

当设置-Xss512K时,结果为:

deep of calling = 9245

栈溢出则会抛出java.lang.StackOverflowError异常。

1)局部变量表

局部变量表用于保存函数的参数以及局部变量,它同样也随函数的调用而生灭,函数变量表可通过jclasslib工具查看,在Idea中,jclasslib可作为插件方式安装。

示例代码:

public class TestStack {

public void test1() {

int m, n, i, j, k;

System.out.println("hello world");

}

public void test2(int param1, int param2) {

long m, n, i, j, k;

System.out.println("hello world");

}

public static void main(String[] args) {

TestStack testStack = new TestStack();

}

}

通过查看jclasslib可看到以下内容:

testStack

在jclasslib中,可看到当前类中的静态池,接口字段以及方法等信息。查看方法的Code部分可看到局部变量表统计信息:

局部变量表信息

从上图看出,test2()最大局部变量表占用大小为13字,因为test2()参数为两个int,加上this字段以及5个long变量正好是13字(int占用一个字,this占用一个字,long占用两个字)。查看局部变量表信息可通过LocalVariableTable:

局部变量表

上图中分别对应了局部变量的作用域范围,所在槽位(index),变量名(name)以及数据类型(Descriptor)

栈帧中局部变量表的槽位是可以复用的,如果一个局部变量过了其作用域,那么在其后申明的新局部变量就有可能复用过期局部变量的槽位,从而达到节省资源的目的。

2)操作数栈

操作数栈主要用于保存计算过程中间结果。也作为计算过程中变量的临时存储空间。

3)帧数据区

4)栈上分配

栈上分配是Java虚拟机提供的一项优化技术,基本思想是对于线程私有对象(即不会被其它线程访问到的对象实例),可以将它们分配在栈上,而不必从堆中分配,这样的好处是该对象在函数调用完毕后可以自行销毁而不必接入垃圾回收器,从而提高系统的整体性能。栈上分配的一个基础技术是逃逸分析,逃逸分析的目的是判断对象作用域是否逃逸出函数体。

在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法——分析在程序的哪些地方可以访问到指针。它涉及到指针分析和形状分析。

当一个变量(或对象)在子程序中被分配时,一个指向变量的指针可能逃逸到其它执行线程中,或是返回到调用者子程序。如果使用尾递归优化(通常在函数编程语言中是需要的),对象也可以看作逃逸到被调用的子程序中。如果一种语言支持第一类型的延续性在Scheme和Standard ML of New Jersey中同样如此),部分调用栈也可能发生逃逸。

编译器可以使用逃逸分析的结果作为优化的基础:

将堆分配转化为栈分配。如果某个对象在子程序中被分配,并且指向该对象的指针永远不会逃逸,该对象就可以在分配在栈上,而不是在堆上。在有垃圾收集的语言中,这种优化可以降低垃圾收集器运行的频率。

同步消除。如果发现某个对象只能从一个线程可访问,那么在这个对象上的操作可以不需要同步。

分离对象或标量替换。如果某个对象的访问方式不要求该对象是一个连续的内存结构,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。

例如下面的代码便是一个逃逸对象:

private static Bean bean;

public static void alloc() {

bean = new Bean();

bean.setParam(23);

....

}

其中bean字段可能被其它线程访问到,故属于逃逸对象。

下面的代码显示了非逃逸对象:

public static void alloc() {

bean = new Bean();

bean.setParam(23);

....

}

启用逃逸分析需要设置-server执行程序,JVM参数如下:

-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations

-Xmx256m -Mms256m 分别制定了堆最大空间和堆最小空间为10M;

-XX:+DoEscapeAnalysis 启用逃逸分析;

-XX:+PrintGC 打印GC信息;

-XX:-UseTLAB 关闭TLAB;

-XX:+EliminateAllocations 开启标量替换,允许将对象打散分配到栈上。

以上参数都是默认启用的。

示例:

public class OnStackTest {

public static class User {

public int id = 0;

public String name = "";

}

public static void alloc() {

User user = new User();

user.id = 10;

user.name = "vincent";

}

public static void main(String[] args) throws InterruptedException {

long start = System.currentTimeMillis();

for (int i = 0; i < 1000000000; i++) {

alloc();

}

System.out.println(System.currentTimeMillis() - start);

}

}

上面的代码进行了1000000000次调用,但是产生的GC日志很少:

[GC (Allocation Failure) 2047K->536K(9728K), 0.0008531 secs]

7

但如果关闭了逃逸分析,则会产生大量的GC日志。例如将-XX:+DoEscapeAnalysis 替换成-XX:-DoEscapeAnalysis

四 方法区

方法区也是所有线程共享的内存区域,用于保存系统类信息,例如字段,方法常量池等。该区域大小决定了系统可以保存多少类。但若定义了太多类同样也会导致方法区溢出。

在JDK6和JDK7中,方法区可链接为永久区,通过参数-XX:PermSize和-XX:MaxPermSize指定。但在JDK8中,永久区已经被移除,替代为元数据区,可使用-XX:MaxMetaspaceSize参数指定,若不指定该参数,默认情况下虚拟机会耗尽所有可用系统内存,在VisualVM中可观察永久区:

永久区

元数据区溢出虚拟机会抛出java.lang.OutOfMemoryError: Metaspace异常。

参考

《实战Java虚拟机: JVM故障与性能优化》

《深入理解Java虚拟机:JVM高级特性与最佳实践》

java虚拟机有哪几部分组成,Java虚拟机基本结构相关推荐

  1. 怎么把虚拟机清空内存_深入理解java虚拟机1——内存管理机制与回收机制

    文中涉及JVM底层知识大多来自<深入理解Java虚拟机>第2版,内容枯燥乏味,如果看,认真看.跟着撸一遍也可以受益良多. 1.JVM:是运行在操作系统之上的,它与硬件没有直接的交互. 运行 ...

  2. 深入Java虚拟机读书笔记第五章Java虚拟机

    Java虚拟机 Java虚拟机之所以被称之为是虚拟的,就是因为它仅仅是由一个规范来定义的抽象计算机.因此,要运行某个Java程序,首先需要一个符合该规范的具体实现. Java虚拟机的生命周期 一个运行 ...

  3. 深入理解Java虚拟机知乎_深入理解Java虚拟机(类文件结构)

    深入理解Java虚拟机(类文件结构) 欢迎关注微信公众号:BaronTalk,获取更多精彩好文! 之前在阅读 ASM 文档时,对于已编译类的结构.方法描述符.访问标志.ACC_PUBLIC.ACC_P ...

  4. java虚拟机的内存模型_JVM(Java虚拟机)内存模型(转载/整理)

    Java虚拟机包括一套字节码指令集.一组寄存器.一个栈.一个垃圾回收堆和一个存储方法域.JVM是为Java字节码定义的一种独立于具体平台的规格描述,是Java平台独立性的基础. 对于字节码指令集不感兴 ...

  5. java虚拟机内存监控_深入理解JVM虚拟机9:JVM监控工具与诊断实践

    本文转自: https://juejin.im/post/59e6c1f26fb9a0451c397a8c 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到 ...

  6. java虚拟机编译文件,理解Java虚拟机(1)之一个.java文件编译成.class文件发生了什么...

    理解Java虚拟机(1)之一个.java文件编译成.class文件发生了什么 最近在看<深入理解Java虚拟机>弄明白了很多java的底层知识,决定分几部分总结下,从.java文件编译,到 ...

  7. 为啥JAVA虚拟机不开发系统_理解Java虚拟机体系结构

    1 概述 众所周知,Java支持平台无关性.安全性和网络移动性.而Java平台由Java虚拟机和Java核心类所构成,它为纯Java程序提供了统一的编程接口,而不管下层操作系统是什么.正是得益于Jav ...

  8. 怎么运行java虚拟机_Java代码如何运行在Java虚拟机中

    我们都知道要运行Java代码就必须要有JRE,也就是Java运行时环境,JRE中包含了Java程序的必需组件,包括Java虚拟机以及Java核心类库,然而运行C++代码则不需要额外的运行时环境,只需要 ...

  9. 《深入理解java虚拟机》第1章 走近Java

    1.4 Java虚拟机发展史 上一节我们从整个Java技术的角度观察了Java 技术的发展,许多Java程序员都会潜意识地把它与Sun公司的HotSpot虚拟机等同看待,也许还有一些程序员会注意到BE ...

  10. java虚拟机教程图解_深入拆解JAVA虚拟机学习教程

    搞JAVA的深入下去java虚拟机是必须掌握的知识,最近发现个不错的视频教程,学习了几篇讲得非常不错,推荐给大家.废话不多说,大看直接看目录吧. PS:主讲人是Oracle 高级研究员,计算机博士 郑 ...

最新文章

  1. 如何利用ArcGis把经纬度转成shp数据
  2. 【Matlab】找到矩阵中每个连通域的最小值
  3. JZOJ 5609. 【NOI2018模拟3.28】Tree BZOJ 4919: [Lydsy1706月赛]大根堆
  4. java重新初始化吗_Java中为何已经重新赋值的变量在输出后会初始化?
  5. 10.10 traceroute:追踪数据传输路由状况
  6. POJ - 2342 Anniversary party(树形dp入门)
  7. Python 两大环境管理神器:pyenv 和 virtualenv
  8. [bug解决] cannot import name ‘_validate_lengths‘ from ‘numpy.lib.arraypad‘
  9. Out of resources when opening file './xxx.MYD' (Errcode: 24)
  10. MATLAB画图线性,颜色和数据点
  11. k线图中的三条线没了怎么办?
  12. java 坦克大战 教程_马士兵老师/坦克大战/java基础/网络编程 (9.1G)视频教程下载...
  13. BMI160低功耗学习
  14. ACR122U Android端应用开发入道指南
  15. 微信小程序下拉刷新功能--onPullDownRefresh
  16. Unity导入Goolgle.Protobuf.dll报错
  17. zhu hao shi de shi
  18. 微信兔子,比较下来算是比较好用的工具
  19. 使用.htaccess 开启gzip 缓存文件 网页 提高速度 和 .htaccess文件用法集锦
  20. 2022年交通工具公开拍卖市场研究报告

热门文章

  1. idea将maven所有依赖包导出
  2. 乐优商城(05)--商品管理
  3. 李宏毅机器学习课程作业-HW1
  4. Excel筛选之高级筛选篇
  5. 分布式强化学习之D4PG
  6. 大话转岗PHP开发小结
  7. 基于c51单片机的毕业设计——智能温度控制
  8. html音乐博客代码,【转载】HTML博客音乐播放器代码大全
  9. 舌尖上的广西 | 口馋者争相追求的中国美食梦!!!型男索女尝过吗?
  10. JSP+MySQL房屋租赁管理系统