前言

相信很多人和我一样长期使用java编程,却很少关注过JVM底层实现,这很大程度上是因为JVM设计的很精巧,因此平时项目也很少遇到涉及JVM的问题。但是一方面出于对java底层技术的好奇,另一方面某些高并发,要对特定场景优化或者是排错的问题也迫切需要对JVM实现的了解,于是楼主这两天仔细拜读了《inside JVM》这本关于JVM的经典著作,对JVM的一些实现细节有了较为清楚的认识,将一些学习的体会和收获记录下来与各位有相同困扰的朋友分享。

本文将从JVM的几大核心技术切入:JVM内存管理、class文件格式、类装载、垃圾收集、多线程并发。需要注意的是因为Java是一个平台无关的技术,JVM在不同平台上必须有不同的实现,因此当年的Sun发布了一个JVM specification(Java 虚拟机规范)。任何团体或个人实现的JVM都必须遵照该规范才能正确的运行java程序。因此本文讨论的很多技术可能在不同的虚拟机上实现会有所不同,本文只是讨论一些通用的技术以及虚拟机规范定义的一些要求。

JVM内存管理

在Java虚拟机规范中,将JVM虚拟机的内存分成了如下图中运行时数据区几大区域

这几个区域分别是方法区,堆,Java栈,PC寄存器,本地方法栈。接下来我们就来详细认识下这些内存区域的作用。

首先要说的是堆,堆中存放的是所有在java程序运行过程中创建的对象,因为在java里,数组是以对象的形式存在,因此数组也是存放在堆中的。堆占据了JVM的大部分内存。因此也是Java的GC,垃圾收集器主要工作的目标区域。

接下来要说的是方法区,方法区里存储了所有类装载进来后和这个类相关的所有运行时需要的信息(如类的静态变量,常量,类的全局名称,方法信息等)。我们在后面介绍class文件的章节里会详细介绍class文件加载进来之后是如何将这些信息对应写入方法区的数据结构中的。

和前面介绍的两个区域是所有线程共享的不同,Java栈和本地方法栈以及PC寄存器都是线程独占的,也就是说每个线程都有一个java栈和PC寄存器或者本地方法栈(如果用到了本地方法的话)。

说到这里需要介绍一下本地方法,我们知道java是跨平台的,但是我们比如在需要读文件的时候,不用去关心将来是在哪个平台运行,只要调用FileInputStream把文件读入就可以了,不用调用底层操作系统的API函数,这是因为不同平台Java的API把所有这些与平台相关的操作都封装了起来提供了一个统一的Java编程接口。而Java的API正是通过调用一些本地方法(这些方法很多时候是一些编译后的可执行的C程序)来实现了这些功能。同时虽然Java实现了大部分平台都有的一些功能(如IO,多线程等),但是有些平台的一些功能是该平台特有的,提供Java虚拟机的厂商为了提供这些功能往往就以动态链接库的形式提供一些本地方法的调用来完善JVM在该平台的功能。至于如何去调用以及如何与本地方法通信(获取返回值等)就是具体JVM实现需要去做的事情。

说了这么多本地方法的内容,现在回到Java栈的部分,每个线程都有一个自己独立的Java栈,每次线程执行到一个新的方法时就在栈里面压入一个栈帧。帧里包含了方法里的局部变量,操作数栈以及帧数据区。这三种区域中局部变量很好理解,就是在方法作用范围内的变量,包括基本变量和对象的引用。理解操作数栈要先对JVM执行java程序的过程有所了解,JVM在装载进class文件后可能采用解释执行、即时编译执行、混合执行这三种方式来执行class文件中的JVM指令集。JVM指令集是一个4字节的指令集,就像汇编语言做相加操作需要先将两个数存入寄存器一样,JVM指令做数据相关的操作也要先将数据压入java栈里面的操作数栈才能进行。比如方法里将i变量和j变量相加赋值给z,JVM先将i压入操作数栈,再将j压入操作数栈,最后将结果写回局部变量表或者是对象的字段。至于帧数据区,是为了在方法执行过程中访问方法区的数据以及返回方法结果而用的。某个方法执行结束完之后如果是正常返回则会将返回结果压入上一个方法的操作数栈中,如果是异常退出且没有catch该异常则会运行到上一个方法继续抛出该异常。

本地方法和Java方法一样,只是Java栈是执行Java方法的线程申请的内存,而本地方法是执行本地方法而申请的内存。下面这张图显示了两者的关系。

最后程序计数器是为每个线程记录当前执行的字节码位置而设立的,线程切换时需要记录下当前执行到哪一步了以便该线程重新获取CPU执行时能继续正确执行。

顺便说一句,在java里面对象是通过引用来操作的,栈里面存储的引用,而堆里存储的对象。不同的JVM实现在引用的具体实现上可能有所不同,两种比较流行的方式分别是通过对象句柄引用和通过直接指针引用。JVM的GC也是通过引用来确定哪些对象可以回收。下图分别表示了两种引用的实现:

对比这两种引用实现,句柄池的方法在GC需要移动对象(消除内存碎片以存放大对象)时,只需要将句柄池中每个对象的指针地址修改即可。但是引用访问对象需要经过两个地址查找,降低了效率。直接指向对象的方式在需要移动对象时要将每个引用的地址都做修改,这相对直接修改句柄池来说要昂贵的多,但是因为一次寻址提高了效率。

细心的读者可能注意到不管采用什么方式,每个引用都有一个指向方法区里该类数据的指针。这是因为在java里面不像C++可以直接对内存对象做类型转换,Java类型转换前一定要做类型检查以保证这次转换是安全的以避免可能因此带来的程序崩溃。因此每个引用都有一个指向类型数据的指针。

本文花了很大篇幅介绍java栈的内容,是因为作者认为在这几个区域中,Java栈是最难理解的部分,希望读者能耐心读完,有什么问题也欢迎留言交流,最后为了加深对堆和栈存储哪些数据的理解,作者写了两个分别产生OutOfMemoryError和StackOverflowError的函数以帮助理解,oom函数在数组对象s中不停的添加数据,最后堆内存无法满足新的添加需求JVM就退出同时报出了OutOfMemoryError, stack()方法中有一个s的双精度局部变量,同时不停的递归调用自己,Java栈中就不停的压入新的方法栈,最后JVM退出并报出了StackOverflowError

Java代码:

package Experiment;

import java.util.ArrayList;

public class TestJVM {
public static void main(String[] args)
{
stackof();
//oom();
}
private static void oom()
{
ArrayList<Integer> s=new ArrayList<>();
while(true)
{
s.add(1);
}
}
private static void stackof()
{
double s;
stackof();
}
}

运行结果:

原文地址:http://www.cnblogs.com/developerY/p/3330811.html 转载请注明出处

深入JVM学习心得的更多相关文章

  1. JVM学习心得

    出处:http://blog.csdn.net/qq_16143915/article/details/51195438 一.JAVA内存管理与GC机制 Java在JVM所虚拟出的内存环境中执行,ja ...

  2. JVM学习心得—JVM内存模型(个人整理,请勿转载)

    一.运行时数据区域 线程私有的:程序计数器+虚拟机栈+本地方法栈 线程共享的:堆+方法区(运行时常量池)+直接内存(非运行时数据区的一部分) *JDK1.8后将方法区废除,新增元空间. 1.1 程序计 ...

  3. effective java 学习心得

    目的 记录一下最主要学习心得,不然凭我这种辣鸡记忆力分分钟就忘记白看了... 用静态工厂方法代替构造器的最主要好处 1.不必每次都创建新的对象 Boolean.valueOf Long.valueOf ...

  4. 我的MYSQL学习心得(一) 简单语法

    我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...

  5. 我的MYSQL学习心得(二) 数据类型宽度

    我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...

  6. 我的MYSQL学习心得(三) 查看字段长度

    我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...

  7. 我的MYSQL学习心得(四) 数据类型

    我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(五) 运 ...

  8. 我的MYSQL学习心得(五) 运算符

    我的MYSQL学习心得(五) 运算符 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据 ...

  9. 我的MYSQL学习心得(六) 函数

    我的MYSQL学习心得(六) 函数 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类 ...

随机推荐

  1. hdu 5024 最长的L型

    http://acm.hdu.edu.cn/showproblem.php?pid=5024 找到一个最长的L型,L可以是斜着的 简单的模拟 #include <cstdio> #incl ...

  2. Excel 帮助无法正常工作的解决方法

    Excel 中出现错误:帮助无法正常工作,但您仍可以转到 Office.com,以获得最新和最好的文章.视频和培训课程. 英文消息:Help isn't working, but you can st ...

  3. C#实现FTP文件的上传、下载功能、新建目录以及文件的删除

    本来这篇博文应该在上周就完成的,可无奈,最近工作比较忙,没有时间写,所以推迟到了今天.可悲的是,今天也没有太多的时间,所以决定给大家贴出源码,不做详细的分析说明,如果有不懂的,可以给我留言,我们共同讨 ...

  4. asp.net MVC把Areas区域绑定成二级域名

    先分析需求 在MVC项目中,我们如果有两个Areas.比如Test和DEMO.我们的访问地址应该是 http://localhost:8098/test http://localhost:8098/d ...

  5. 接口interface和抽象类型abstract

    一.接口 接口不能被实例化 接口只能包含方法声明 接口的成员包括方法.属性.索引器.事件 接口中不能包含常量.字段(域).构造函数.析构函数.静态成员 接口中的所有成员默认为public,因此接口中不 ...

  6. JavaScript学习知识点归纳

    JavaScript学习包括几大方面: 1.基础语法 2.JavaScript核心对象 3.DOM操作 4.BOM操作 5.正则表达式 6.AJAX 7.面向对象编程 以下依次为各版块相关内容==&g ...

  7. JAVA 定时器时间格式

    格式: [秒] [分] [小时] [日] [月] [周] [年] 通配符说明: \*:表示所有值.例如:在分的字段上设置"\*",表示每一分钟都会触发. ?:表示不指定值.使用的场 ...

  8. [Swift实际操作]七、常见概念-(3)尺寸CGSize的使用详解

    本文将为你演示CGSize的使用 首先导入需要使用到的两个框架 import UIKit import QuartzCore 定义一个尺寸对象,尺寸对象包含宽度和和高度两个参数.从右侧的结果可以看出, ...

  9. Python中的运算符与表达式

    你所编写的大多数语句(逻辑行)都包含了表达式(Expressions).一个表达式的简单例子便是 2+3.表达式可以拆分成运算符(Operators)与操作数(Operands).运算符(Operat ...

  10. POJ 1036

    #include<iostream> #include<algorithm> #define MAXN 205 using namespace std; struct node ...