深入JVM学习心得
前言
相信很多人和我一样长期使用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学习心得的更多相关文章
- JVM学习心得
出处:http://blog.csdn.net/qq_16143915/article/details/51195438 一.JAVA内存管理与GC机制 Java在JVM所虚拟出的内存环境中执行,ja ...
- JVM学习心得—JVM内存模型(个人整理,请勿转载)
一.运行时数据区域 线程私有的:程序计数器+虚拟机栈+本地方法栈 线程共享的:堆+方法区(运行时常量池)+直接内存(非运行时数据区的一部分) *JDK1.8后将方法区废除,新增元空间. 1.1 程序计 ...
- effective java 学习心得
目的 记录一下最主要学习心得,不然凭我这种辣鸡记忆力分分钟就忘记白看了... 用静态工厂方法代替构造器的最主要好处 1.不必每次都创建新的对象 Boolean.valueOf Long.valueOf ...
- 我的MYSQL学习心得(一) 简单语法
我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...
- 我的MYSQL学习心得(二) 数据类型宽度
我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...
- 我的MYSQL学习心得(三) 查看字段长度
我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...
- 我的MYSQL学习心得(四) 数据类型
我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(五) 运 ...
- 我的MYSQL学习心得(五) 运算符
我的MYSQL学习心得(五) 运算符 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据 ...
- 我的MYSQL学习心得(六) 函数
我的MYSQL学习心得(六) 函数 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类 ...
随机推荐
- vim 配置半透明
转载两个博客 链接一 链接二
- ArcGIS下图层范围不正确的两种处理方式
ArcGIS下图层范围不正确,偶尔能碰上这种情况,主要表现为“缩放至图层”时,其显示范围与该图层内所有要素的外包围盒范围不一致.针对这个问题,有两种解决办法. 方法一:导出数据.新创建含有要素的Sha ...
- SVG系列教程:SVG简介与嵌入HTML页面的方式
地址:http://www.w3cplus.com/html5/svg-introduction-and-embedded-html-page.html 随着技术向前的推进,SVG相关的讨论也越渐频繁 ...
- [ASP.NET]Net Framework环境问题的一种修复方案
一.情况介绍 造价软件基于.net framework 4.0开发,要成功运行需要在目标电脑上安装4.0版本以上的framework.一般情况下xp是没有的,win7系列自带3.5,都需要手动安装4. ...
- JAVA 从头开始<三>
一.数据类型转换 取反:1变0,0变1 强转 Insteger.toBinaryString(-7); 下面这样写会出错,要用l来接收 为什么byte b 可以接收int类型(而不是10b),大数据类 ...
- SQL Server基础优化
1.先过滤简单且能筛选大部分数据出去的条件: 2.只查询有用的数据 不返回自己不需要的列,尽量不要使用select *: 不要返回自己不需要的行,尽量使用where条件来过滤自己需要的内容: 考虑使用 ...
- Could not load file or assembly '$SharePoint.Project.AssemblyFullName$'
The fix is simple, do the following: 1. Open your project file in NotePad 2. Find the PropertyGrou ...
- 【自动化专题】借助firefox插件定位web元素小技巧
浏览器:firefox 插件:firebug.firepath.firefinder 安装插件---已安装的请跳过 1.安装firefox浏览器 2.打开firefox-附加组件(找不到入口的问度娘) ...
- Day 33 Socket编程.
套接字 (socket)处使用 基于TCP 协议的套接字 TCP 是基于链接的 ,服务器端和客户端启动没有顺序. 服务器端设置: import socket sk =socket.socket() # ...
- Bootstrap框架(一)
day57 参考:https://www.cnblogs.com/liwenzhou/p/8214637.html 下载:http://www.bootcss.com/ 选择用于生产环境的 Boo ...