JVM笔记(一) Java内存区域
Java 内存区域
总概
java虚拟机在执行java程序的过程中,会把它管理的内存划分为几个不同的数据区域。每当运行一个java程序时,就会启动一个虚拟机。
具体的区域如图所示:
同时,方法区 与 堆 是由所有线程共享的数据区;而 虚拟机栈、本地方法栈、程序计数器 则是被线程隔离的区域。
一、程序计数器
什么是程序计数器?
概念:就是当前线程所执行的字节码的行号指示器。
- JVM的概念模型中,字节码解释器通过改变这个计数器的值来选取下一条字节码指令。
- JVM的多线程其实就是通过线程轮流切换并分配处理器执行时间的方式来实现的(在任何一个确定的时刻内,一个处理器都只会执行一条线程中的指令)。为了线程切换后能够恢复到正确的执行位置,每条线程都需要有独立的程序计数器,各线程计数器互不影响,独立存储。所以,程序计数器是线程私有的内存区域
- 如果线程执行一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是Native方法,则计数器的值为空。
- Java虚拟机规范中唯一一个没有规定任何OutOfMemoryError情况的区域。
二、Java虚拟机栈
- 线程私有,生命周期与线程相同。
- 虚拟机描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。(PS:我觉得可以将它看作是一个方法的快照,记录着方法的参数之类的信息,其实就是方法运行时的数据结构基础)
- 局部变量表,存放了各种基本数据类型、对象引用,和返回后所指向的字节码的地址。(PS:这个就是我们常说的“栈内存 Stack”)
- 在Java虚拟机规范中,对于该区域规定了两种异常状态:
- StackOverflowEError : 线程请求的深度大于虚拟机所允许的深度;
- OutOfMemoryError : 动态扩展时无法申请到足够的内存。
三、本地方法栈
- 本地方法栈为虚拟机使用到 Native 方法服务。
- 同 Java虚拟机栈 一样,会抛出 StackOverflowEError 和 OutOfMemoryError 异常。
四、Java堆(线程共享)
C语言是使用 malloc 从堆中来分配空间的。同样的,Java堆是用来存放对象实例的。
Java规范中的描述:所有的对象实例以及数组都要在堆上分配;但随着技术的发展,这种说法也不是那么“绝对”了。
- 唯一目的就是存放实例对象,几乎所有的对象实例都在这里分配内存。
- Java堆是垃圾收集器管理的主要区域。
- 可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,同时也是可扩展的。
- OutOfMemoryError : 如果在堆中没有内存完成实例分配,并且堆也无法再扩展。
五、方法区(线程共享)
- 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 方法区对于垃圾回收的效果比较难以令人满意,尤其是对于类型的卸载条件相当坎坷,但对于该区域进行垃圾回收是必要的。
- OutOfMemoryError : 如果方法区无法满足内存分配需求就会抛出这个异常。
常量池
常量池是方法区的一部分。这里存放着编译期生成的各种字面量和符号引用(其实就是八大基本类型的包装类型和String类型数据)。
运行时常量池一个重要的特征:具备动态性,解释就是Java语言并不要求常量一定只有编译期才产生,比如String类的intern()方法。
- String.intern()
String类的intern()方法是一个Native方法,底层调用C++的 StringTable::intern方法实现。
public static void main(String[] args) {
//String str = "FunriLy";
String str1 = new StringBuilder("Funri").append("Ly").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("java").toString();
System.out.println(str2.intern() == str2);
}
运行上面的代码,会得到一true一false;若去掉注释,则会得到两个false。
* 调用intern()后,JVM就会在当前类的常量池中查找是否存在与str等值的String,若存在则直接返回常量池中相应Strnig的引用;若不存在,则会在常量池中创建一个等值的String,然后返回这个String在常量池中的引用。
* 在 JDK 1.6 版本,**常量池被保存在方法区(PermGen)中**,而String类对象存在于堆区中,这就意味着多次intern()操作会使内存中存在许多重复的字符串,会造成性能损失。同时,在此区域其大小会受到限制。
* 在 JDK 1.7 版本,开始了"永久代"的移除(也就是前面提到过GC的要求):符号引用转移到了本地方法栈;字面量转移到了堆;类的静态变量转移到了堆。
* 在iJDK 1.8 版本,去掉了PermGen内存,所以永久代的参数 -XX:PermSize 和 -XX:MaxPermSize 也被移除了,转而出现了一个元空间(Metaspace)。【关于这一点,可以看一下这篇博文:http://blog.csdn.net/zhyhang/article/details/17246223 】
六、对象的创建
- 当虚拟机遇到一条 new 指令时,首先去常量池中检查是否能定位到一个类的符号引用,并检查类是否已经被加载、解析和初始化过。如果没有,就执行相应的加载操作。
- 接下来就是在堆中分配空间了。有两种方案:
- 第一种(指针碰撞法)
假设堆中内存绝对规整,那么只要在用过的内存和没用过的内存间放置一个指针即可,每次分配空间的时候只要把指针向空闲空间移动相应距离即可。 - 第二种(空闲列表)
假设内存空间并不规整,通过维护一个列表来记录堆内存的使用情况(PS:操作系统对于内存的管理就是这种模式)。
- 第一种(指针碰撞法)
- 但是我们也要考虑在并发情况下的线程安全性问题。比如,正在给对象A分配空间,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存,导致对象AB占用了相同的一块空间。
- 第一种,对分配内存空间的动作进行同步处理。
- 第二种,每个线程在堆中预先分配一小块内存,称为本地线程分配缓存(TLAB),每个线程只在自己的 TLAB 中分配内存。
- 最后,对象被成功分配内存空间,虚拟机会对对象进行必要的设置,对象的类,对象的哈希码等信息都存放在对象的对象头中,所以分配的内存大小绝不止属性的总和。
七、对象的内存布局
对于大部分的虚拟机,对象在堆中的存储布局可以分为3块区域:
- 对象头,
包括两部分信息,第一部分用于存储对象自身运行时数据,例如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳,官方称为”Mark
Word”。另外一部分是类型指针,即对象指向它的类元数据指针(即指向方法区类数据的指针)。 - 实例数据,存放着对象真正存储的有效信息,包括了父类继承和子类定义的信息。
- 对齐填充,占位符作用。
八、对象的访问定位
引用存放在虚拟机栈中,数据类型为reference,对象实例存放在堆中。
Java程序就是通过栈上的reference数据来操作堆上的具体对象。目前,主流的访问方式有使用句柄和直接指针两种。
使用句柄来访问对象
使用句柄的话,将会在Java堆中划分一块区域作为句柄池,引用中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
优势:在对象被移动时(比如垃圾回收)只会改变句柄中的实例数据指针,而引用本身不需要修改。
通过指针来访问对象
使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而引用中存储的直接就是对象地址。
优势:访问速度快,节省了一次指针定位(访问对象是非常频繁的操作)的时间开销。
参考资料
- 《收入了解Java虚拟机》(周志明 著)
- (intern()方法)http://www.jianshu.com/p/95f516cb75ef
JVM笔记(一) Java内存区域的更多相关文章
- 【转载】Java系列笔记(3) - Java 内存区域和GC机制
Java系列笔记(3) - Java 内存区域和GC机制 转载:原文地址http://www.cnblogs.com/zhguang/p/3257367.html 目录 Java垃圾回收概况 Java ...
- Java系列笔记(3) - Java 内存区域和GC机制
目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection, ...
- JVM探秘:Java内存区域
本系列笔记主要基于<深入理解Java虚拟机:JVM高级特性与最佳实践 第2版>,是这本书的读书笔记. 概述 Java 虚拟机为程序员分担了很多内存管理的工作,不再像 C/C++ 那样容易出 ...
- JVM参数配置 java内存区域
java内存区域 一些基本概念 http://www.importnew.com/18694.html https://www.cnblogs.com/wangyayun/p/6557851.html ...
- 【搞定Jvm面试】 Java 内存区域揭秘附常见面试题解析
本文已经收录自笔者开源的 JavaGuide: https://github.com/Snailclimb ([Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识)如果觉得不错 ...
- 深入理解JAVA虚拟机阅读笔记1——JAVA内存区域
一.Java内存区域 1.程序计数器 线程私有. 当前线程所执行的字节码的行号指示器.由于JAVA是多线程的,因此每个线程都独立的程序计数器. 异常:没有规定任何OutOfMemeryError情况的 ...
- 《深入java虚拟机》读书笔记之Java内存区域
前言 该读书笔记用于记录在学习<深入理解Java虚拟机--JVM高级特性与最佳实践>一书中的一些重要知识点,对其中的部分内容进行归纳,主要是方便之后进行复习. 运行时数据区域 Java虚拟 ...
- 【JVM.1】java内存区域与内存溢出
鲁迅曾说过:Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进来,墙里面的人想出去. 一.虚拟机内存分布 Java虚拟机在执行Java程序的过程中会把它所管理的内存 ...
- JVM 之:Java 内存区域与内存溢出
内存区域 Java 虚拟机在执行 Java 程序的过程中会把他所管理的内存划分为若干个不同的数据区域.Java 虚拟机规范将 JVM 所管理的内存分为以下几个运行时数据区:程序计数器.Java 虚拟机 ...
随机推荐
- mysql jdbc性能优化之mybatis/callablestatement调用存储过程mysql jdbc产生不必要的元数据查询(已解决,cpu负载减少20%)
INFO | jvm 1 | 2016/08/25 15:17:01 | 16-08-25 15:17:01 DEBUG pool-1-thread-371dao.ITaskDao.callProce ...
- Linq 对List的一些操作
代码: public class Person { public int ID { get; set; } public string Name { get; set; } public int Ag ...
- 加法变乘法|2015年蓝桥杯B组题解析第六题-fishers
加法变乘法 我们都知道:1+2+3+ ... + 49 = 1225 现在要求你把其中两个不相邻的加号变成乘号,使得结果为2015 比如: 1+2+3+...+1011+12+...+2728+29+ ...
- Spring的面向切面
Spring的面向切面 在应用开发中,有很多类似日志.安全和事务管理的功能.这些功能都有一个共同点,那就是很多个对象都需要这些功能.复用这些通用的功能的最简单的方法就是继承或者委托.但是当应用规模达到 ...
- IIS Logs
日志路径 %SystemDrive%\inetpub\logs\LogFiles https://stackify.com/where-are-iis-log-files-located/ Where ...
- C#学习笔记(十):函数和参数
函数 using System; using System.Collections.Generic; using System.Linq; using System.Text; using Syste ...
- basename、dirname、alias、date
basename 此命令用于打印目录或者文件的基本名称. basename和dirname命令通常用于shell脚本中的命令替换来指定和指定的输入文件名称有所差异的输出文件名称. basename ( ...
- spring cloud kubernetes之serviceaccount permisson报错
spring boot项目引用spring-cloud-starter-kubernetes <dependency> <groupId>org.springframework ...
- 关于PATH_INFO
nginx支持PATH_INFO? 想让nginx支持PATH_INFO,首先需要知道什么是pathinfo,为什么要用pathinfo? pathinfo不是nginx的功能,pathinfo是ph ...
- STL_算法_04_算术和生成算法
◆ 常用的算术和生成算法: 1.1.求和( accumulate 是求和的意思)(对指定范围内的元素求和,然后结果再加上一个由val指定的初始值.) T accumulate(iteratorBegi ...