深入理解Java虚拟机之Java内存区域随笔
1、java内存区域与内存溢出异常
Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域:1.程序计数器,2.栈(虚拟机栈和本地方法栈 ),3.堆,4.方法区(包含运行时常量池)。程序计数器和栈(虚拟机栈和本地方法栈 )为线程私有的,堆和方法区(包含运行时常量池)为线程共享的。
1.1程序计数器
程序计数器是一块儿较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码
指令,分支、循环、跳转异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
另外为了线程切换后能恢复到正确位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类区域为“线程私有”的内存。
程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
由以上可知程序计数器的两个作用:
1.字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的执行流程控制
2.多线程情况下,程序计数器记录当前线程的执行位置,线程切换时能够找到上次的执行位置
1.2Java虚拟机栈
与程序计数器一样,Java虚拟机栈也是线程私有的,生命周期和线程相同,描述的是方法执行的内存模型。
Java内存可以粗糙的区分为堆内存和栈内存,其中栈就是虚拟机栈,或者说是虚拟机栈中局部变量表部分。实际上,Java虚拟机栈是由一个个栈帧组成,每个栈帧中都拥有:局
部变量表、操作数栈、动态链接、方法出口信息。
局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个
指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)
Java虚拟机会出现两种异常:StackOverFlowError 和 OutOfMemoryError
StackOverFlowError:若Java虚拟机的栈内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,会抛出StackOverFlowError异常
OutOfMemoryError:若Java虚拟机的栈内存大小允许动态扩展,并且当线程请求栈时内存用完了,无法再动态扩展了,会抛出OutOfMemoryError异常
1.3本地方法栈
本地方法栈和虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行Java方法(字节码)服务,本地方法栈为虚拟机使用到的Native方法服务。HotSpot虚拟机则直
接将虚拟机栈和本地方法栈合二为一。本地方法栈也会抛出StackOverFlowError 和 OutOfMemoryError异常。
1.4堆
堆是Java虚拟机所管理内存中最大的一块儿 ,且堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例和
数组都在堆中分配内存。堆可以实现成固定大小,也可以是可扩展的,如果在堆中没有完成实例分配,并且堆也无法再扩展时,会抛出OutOfMemoryError异常。
Java堆是垃圾收集器管理的重要区域,因此也被称作GC堆。从垃圾回收角度,由于现在的垃圾收集器基本都采用分代收集算法,所以Java堆还可以分为新生代和老年代,再细
致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是为了更好的回收内存,或者更快的分配内存。
在JDK1.8中移除整个永久带,取而代之的是一个叫元空间的区域。永久带使用的是JVM的堆内存空间,而元空间使用的是物理内存,受到本机物理内存的限制。取而代之的目的
总结一下几点:
1.字符串存在永久带中,容易出现性能问题和内存泄露
2.类及方法的信息等比较难确定其大小,因此对于永久带的大小指定比较困难,太小容易出现永久带溢出,太大容易导致老年代溢出。
3.永久带会为GC带来不必要的复杂度,并且回收效率偏低。
1.5方法区
与Java堆一样,是各个线程共享的区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。HotSpot虚拟机中方法区被称为“永久带”,
其实两者并不等价,仅仅是HotSpot虚拟机把GC分代收集扩展至方法区,垃圾收集行为在这个区域是比较少见的,但并非数据进入方法区之后就“永久”存在了,方法区的内存回
收主要是针对常量池的回收和对类型的卸载。当方法区无法满足内存分配需求时,将会抛出OutOfMemoryError异常。
1.6运行时常量池
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等信息,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)。既然作为方法
区一部分,自然受到方法区内存的限制。当常量池无法再申请到内存时会抛出OutOfMemoryError异常。
JDK1.7之后的版本JVM已经将运行时常量池从方法区中移了出来,在堆中开辟了一块区域存放运行时常量池。
1.7直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁的使用,当配置虚拟机参数时,如果忽略掉直接内存,使得
各个内存区域的总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常。
JDK1.4中加入的NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O形势,它可以直接使用Native函数库直接分配堆外内存,然后通过
存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。避免了在Java堆和Native堆之间来回复制数据,在一些场景中显著提高了性能。
2、补充内容
2.1String对象的两种创建方式
两种创建方式如下
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1==s2);//false,因为s1是在常量池创建的对象,s2是在堆内存中创建对象
那么 String s2 = new String("abc");创建了几个对象?两个。
解释:现有字符串"abc"放入常量池,然后new了一份字符串"abc"放入Java堆中,所以是常量池一份"abc"对象和堆中一份"abc"对象(字符串常量"abc"在编译期就已经确定
放入常量池,而Java堆上的"abc"是在运行期初始化阶段才确定)。s2是String对象的引用,根据Java虚拟机栈的知识可以知道,这个引用(s2)存在于Java虚拟机栈中,
并且s2指向Java堆中的对象"abc"。
2.2String类型的常量池的使用方法
1、直接使用双引号声明出来的String对象会直接存储在常量池中。
2、如果不是用双引号声明的String对象,可以使用String对象提供的 intern 方法。String.intern() 是一个Native方法,它的作用是:如果运行时常量池中已经包含了一个
等于此String对象内容的字符串,则返回常量池中改字符串的引用;如果没有,则在常量池中创建与此String内容相同的字符串,并返回常量池中此字符串的引用。
代码如下
String s1 = new String("abc");
String s2 = s1.intern();
String s3 = "abc";
System.out.println(s2);//abc,s1调用intern方法,会在常量池中创建"abc",且s2指向它
System.out.println(s1==s2);//false,s1指向堆内存中的"abc",s2指向常量池中的"abc"
System.out.println(s3==s2);//true,s2和s3都指向常量池中的"abc"
2.3字符串拼接
代码如下
String s1 = "ab";
String s2 = "cd";
String s3 = "ab" + "cd";//常量池中的对象
String s4 = s1 + s2;//堆中创建对象,相当于String s4 = new String("abcd");
String s5 = "abcd"; //常量池中的对象
System.out.println(s3==s4);//false s3指向常量池中的对象,s4指向堆中的对象
System.out.println(s3==s5);//true 都指向常量池中的对象
System.out.println(s4==s5);//false
所以尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用StringBuffer(线程安全)或者StringBuilder(线程不安全)
2.4八种基本类型的包装类和常量池
Byte、Short、Integer、Long、Charactor、Boolean这六种包装类都实现了常量池技术,前五种包装类默认创建了数值 [-128,127]的相应类型的数据缓存,但是超出此范围
仍然会去创建新的对象。两种浮点类型的包装类Float、Double并没有实现常量池技术。
Integer类型比较代码如下
Integer i1 = 40;
Integer i2 = 40;
System.out.println(i1==i2);//true
Integer i3 = 300;
Integer i4 = 300;
System.out.println(i3==i4);//false i3和i4会各自在堆上创建对象
Double i5 = 1.6;
Double i6 = 1.6;
System.out.println(i5==i6);//false 浮点类型没有常量池,都会创建对象
更丰富的例子如下
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
System.out.println(i1==i2);//true 常量池
System.out.println(i1==i2+i3);//true 常量池
System.out.println(i1==i4);//false i1常量池,i4堆
System.out.println(i4==i5);//false 两个堆对象
System.out.println(i4==i5+i6);//true
System.out.println(40==i5+i6);//true
i4==i5+i6为true,因为运算符 + 不适合Integer对象,首先i5和i6会进行自动拆箱操作,进行数值相加,即i4==40,然后Integer对象 i4无法与数值40进行比较,所以i4进行
自动拆箱转为int值40,最后这条语句转为 40==40 进行数值的比较。40==i5+i6同理。
深入理解Java虚拟机之Java内存区域随笔的更多相关文章
- Java虚拟机运行时内存区域简析
figure:first-child { margin-top: -20px; } #write ol, #write ul { position: relative; } img { max-wid ...
- java虚拟机和java内存区域概述
什么是虚拟机,什么是Java虚拟机 虚拟机 定义:模拟某种计算机体系结构,执行特定指令集的软件 系统虚拟机(Virtual Box.VMware),进程虚拟机 进程虚拟机 jvm.Adobe Flas ...
- 深入理解java虚拟机之java内存区域
java虚拟机在执行java程序的时候会把它所管理的内存分为多个不同的区域,每个区域都有不同的作用,以及由各自的生命周期,有些随着虚拟机进行的启动而存在,有些区域则依赖于用户线程的启动或结束而建立或销 ...
- 深入理解Java虚拟机之Java内存区域与内存溢出异常
Java内存区域与内存溢出异常 运行时数据区域 程序计数器 用于记录从内存执行的下一条指令的地址,线程私有的一小块内存,也是唯一不会报出OOM异常的区域 Java虚拟机栈 Java虚拟机栈(Java ...
- 深入理解Java虚拟机(1)--Java内存区域
运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用 ...
- java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3)
概述 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又 ...
- 深入理解Java虚拟机(自动内存管理机制)
文章首发于公众号:BaronTalk 书籍真的是常读常新,古人说「书读百遍其义自见」还是很有道理的.周志明老师的这本<深入理解 Java 虚拟机>我细读了不下三遍,每一次阅读都有新的收获, ...
- 深入理解Java虚拟机之JVM内存布局篇
内存布局**** JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JVM的稳定高效运行.不同的JVM对于内存的划分方式和管理机制存在部分差异.结合JVM虚拟机规范,一起来 ...
- 深入理解java虚拟机【Java内存结构】
Java虚拟机规范规定的java虚拟机内存其实就是java虚拟机运行时数据区,其架构如下: 其中方法区和堆是由所有线程共享的数据区. Java虚拟机栈,本地方法栈和程序计数器是线程隔离的数据区. (1 ...
随机推荐
- TOJ-5395 大于中值的边界元素
描述 给定一个二维数组,求二维数组的边界元素中,大于二维数组“中值”的元素个数.这里的“中值”定义为一个元素序列中: (1)当元素个数为奇数时,即为中间大的元素: (2)当元素个数为偶数时,为中间大的 ...
- js解密
import base64 src_code = 'Ly93eDIuc2luYWltZy5jbi9tdzYwMC8wMDc2QlNTNWx5MWcxaWlpOHNybThqMzB1MDE5NWRyMS ...
- 剑指Offer 18. 二叉树的镜像 (二叉树)
题目描述 操作给定的二叉树,将其变换为源二叉树的镜像. 输入描述: 二叉树的镜像定义:源二叉树 8 / \ 6 10 / \ / \ 5 7 9 11 镜像二叉树 8 / \ 10 6 / \ / \ ...
- 神州数码OSPF Stub(末梢区域)和Totally Stub(完全末梢区域)的配置
实验要求:了解末梢区域及完全末梢区域的配置 拓扑如下 R1 enable 进入特权模式 config 进入全局模式 hostname R1 修改名称 interface l0 进入端口 ip addr ...
- javax/servlet/jsp/jstl/core/Config
javax/servlet/jsp/jstl/core/Config springmvc出现的问题. 尝试了各种jar,问题依旧. DispatcherServlet配置如下. <bean id ...
- Reasoning With Neural Tensor Networks For Knowledge Base Completion-paper
https://www.socher.org/index.php/Main/ReasoningWithNeuralTensorNetworksForKnowledgeBaseCompletion 年份 ...
- 软件测试_Linux
# Linux## 基础知识### 操作系统* 作为中间人,连接软件和硬件### Linux * 特点 * 免费+安全### 查看日志,定位bug,修改文件,搭建环境## 安装### 装虚拟机 vmw ...
- 1.Python
一.Python基础:1.第一句python文件后缀名:文件后缀名是.py2.两种执行方式:(1)把文件地址交给python解释器,python解释器去找到这个文件读到内存执行(2)进入解释器:解释器 ...
- 常见模块(一) time/datetime
1 time模块 1)时间三种格式的转化 2)time模块的相关方法 time.time() 打印当前时间的时间戳 单位是秒 距离1970年1月1日到当前的时间差 time.sleep(n) ...
- PHP面试题学习
PHP 开发工程师笔试试卷 姓名 :__________ 第一部分为必答题,第二.三部分任选其一回答 一. PHP 开发部分 1.合并两个数组有几种方式,试比较它们的异同. 2.请写一个函数来检查用户 ...