深入理解java虚拟机(一)-----java内存区域以及内存溢出异常
概述
Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。
一、运行时数据区域
在java程序执行的过程中,jvm会把它所管理的内存区域划分为不同的数据区域,因为在计算机中,操作系统是老大,操作系统分配一定的内存给到jvm,jvm就是一个小家庭,拿到了土地了,比如这块呢做房子,那块种菜园,后面一块做池塘。。这样每块地都互不干扰,也都有自己的用途。如图:
红框区域代表是所有线程共享的区域,绿色区域则非线程共享,每个线程都自己管理这些数据区域。
1、堆
大部分的java项目中,堆内存都是最大的,因为堆的唯一目的就是存放对象实例,包含所有的对象以及数组都存放在这里。但是随着JIT编译器的发展,这一点也不是那么绝对了,这点以后再介绍,当前可以认为所有对象都存放在堆内存中。
另外堆中内存还可以细分:Eden、From Survivor、To Survivor、Old Generation,前三者属于新生代,最后一个则是老年代。
2、栈
在java中,栈分为虚拟机栈以及本地方法栈。是java程序方法执行时的内存模型,当执行到一个方法时,栈中就会创建一个栈帧,如图所示:
本地方法栈与虚拟机栈的作用非常类似,只是虚拟机栈执行的是java方法,本地方法栈执行的是native方法。
3、程序计数器
是一块较小的内存区域,可以看做是当前线程所执行的字节码的行号指示器。字节码解释器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
4、方法区
用于存储已被虚拟机加载的类信息、常量(放入运行时常量池)、静态变量、即时编译器编译后的代码等数据。说到运行时常量池,string很有发言权,当声明String s = "虚拟机";首先自然是创建一个string对象放在堆中,另外会把"虚拟机"字符串放入到
运行时常量池,可以理解成缓存,不明白的可以看走进JDK(二)------String
另外值得一提的就是直接内存,这块内存不归jvm管理,但是在nio中使用到,可以使用native函数库直接分配堆外内存,然后通过一个存储在堆中的DirectByteBuffer对象作为这块内存的引用进行操作。拿nio来说,这种操作能够大大提高性能,因为避免了在java堆和native堆中来回复制数据。
二、对象创建过程
当我们new xxx()时,可曾想过背后到底发生了什么? 看图:
三、对象内存布局
对象在内存中存储的布局主要分为三块:对象头、实例数据和对齐补充。
对象头:
1、用于存储对象自身运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
2、另一部分是类型指针,即对象指向它的类元数据的指针,迅疾通过这个指针来确定这个对象时哪个类的实例。
实例数据:对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。父类继承下来的字段,也会在子类中存储。
对齐补充:仅仅起着占位符的作用。主要是因为jvm要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍,所以当实例数据部分没有对齐时,就需要通过对齐填充来补全。
四、对象的访问定位
对象创建好了之后,如何使用对象呢?通过栈上的reference数据来操作堆上的具体对象,主流的访问方式有句柄和直接指针两种:
1、句柄
优点:当对象移动的时候(垃圾回收的时候移动很普遍),这样值需要改变句柄中的指针,但是栈中的指针不需要变化,因为栈中存储的是句柄的地址
缺点:需要进行二次定位,寻找两次指针,开销相对于更大一些
2、直接指针
优点:速度快,不需要和句柄一样指针定位的开销
对于sun hotspot虚拟机来说,使用的是第二种方式。
五、OutOfMemoryError与StackOverflowError
1、OutOfMemoryError:
各内存区域中,栈、堆、方法区、直接内存都有可能抛出此异常。
栈:当虚拟机栈可以动态扩展时,无法申请到足够的内存时,就会抛出此异常。
while(true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while(true) { }
}
});
thread.start();
}
堆:当堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出此异常。
List list = new LinkedList();
while(true) {
list.add(student);
}
方法区:当方法区无法满足内存分配需求时,将抛出此异常。
2、StackOverflowError:
线程请求的栈深度大于虚拟机所允许的深度,将会抛出此异常。
这么说估计很多人都不明白,来个示例:
。。。main() {
method()
}
//method()不停的调用自己,此时就会出现StackOverflowError
void method() {
method()
}
深入理解java虚拟机(一)-----java内存区域以及内存溢出异常的更多相关文章
- 深入理解java虚拟机系列(一):java内存区域与内存溢出异常
文章主要是阅读<深入理解java虚拟机:JVM高级特性与最佳实践>第二章:Java内存区域与内存溢出异常 的一些笔记以及概括. 好了開始.假设有什么错误或者遗漏,欢迎指出. 一.概述 先上 ...
- 深入理解Java虚拟机之Java内存区域与内存溢出异常
Java内存区域与内存溢出异常 运行时数据区域 程序计数器 用于记录从内存执行的下一条指令的地址,线程私有的一小块内存,也是唯一不会报出OOM异常的区域 Java虚拟机栈 Java虚拟机栈(Java ...
- 深入理解Java虚拟机之图解Java内存区域与内存溢出异常
Java内存区域与内存溢出异常 运行时数据区域 程序计数器 用于记录从内存执行的下一条指令的地址,线程私有的一小块内存,也是唯一不会报出OOM异常的区域 Java虚拟机栈 Java虚拟机栈(Java ...
- 深入理解Java虚拟机之Java内存区域随笔
1.java内存区域与内存溢出异常 Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域:1.程序计数器,2.栈(虚拟机栈和本地方法栈 ),3.堆,4.方法区(包含 ...
- 《深入理解java虚拟机》第二章 Java内存区域与内存溢出异常
第二章 Java内存区域与内存溢出异常 2.2 运行时数据区域
- 深入理解java虚拟机-第二章:java内存区域与内存泄露异常
2.1概述: java将内存的管理(主要是回收工作),交由jvm管理,确实很省事,但是一点jvm因内存出现问题,排查起来将会很困难,为了能够成为独当一面的大牛呢,自然要了解vm是怎么去使用内存的. 2 ...
- 深入理解Java虚拟机(1)--Java内存区域
运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用 ...
- 深入理解java虚拟机---->java内存区域与内存溢出异常
2. java内存区域于内存溢出异常 2.1 概述: 对于C/C++而言,内存管理具有最高的权利,既拥有每一个对象的“所有权”,又担负着每一个对象生命开始到结束的维护责任. 对于java而言,则把内存 ...
- 《深入理解Java虚拟机》之(一、内存区域)
一.java的体系构成: Java的技术体系主要由支撑java程序运行的虚拟机.提供各种开发领域接口支持的java api.java编程语言及许多第三方java框架(如Spring .Struts等) ...
- 深入了解Java虚拟机(1)java内存区域与内存溢出异常
java内存区域与内存溢出异常 一.运行时数据区域 1.程序计数器:线程私有,用于存储当前所执行的指令位置 2.Java虚拟机栈:线程私有,描叙Java方法执行模型:执行方法时都会创建一个栈帧,存储局 ...
随机推荐
- 【EFM32】EFM32芯片解锁、加锁操作
[解锁] 使用J-Flash,新建Project,选择对应的EFM32芯片型号. 选择Target >> Connect >> Unsecure chip ,便进行解锁了,解锁 ...
- Jacobian矩阵、Hessian矩阵和Newton's method
在寻找极大极小值的过程中,有一个经典的算法叫做Newton's method,在学习Newton's method的过程中,会引入两个矩阵,使得理解的难度增大,下面就对这个问题进行描述. 1, Jac ...
- 2017-11-11 Sa Oct 消参
2017-11-11 Sa Oct 消参 Prior versions: 2017-11-04 Sa Oct 消参 2017-11-10 Fr Oct 消参 2017-11-04 Sa $ P(-3, ...
- vue provide和inject 父组件和子孙通信
父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量.不论子组件有多深,只要调用了inject那么就可以注入provider中的数据.而不是局限于只能从当前父组件的prop ...
- .netframe初识
转发自:https://blog.csdn.net/bingshan5haoao/article/details/32966581 https://www.cnblogs.com/liuxx/p/35 ...
- 常用vi命令
i 在当前光标处插入字符,并进入编辑模式 o 在当前光标插入下一行 x 从当前光标处向后删除一个字符. dd 删除当前光标处所在行 :q! 强制退出不保存 :q 退出(文本有改动则警告) :w 保存 ...
- 高性能mysql 事务笔记
事务的四大特性原子性.一致性.隔离性.持久性, 事务隔离的四大隔离级别: READ UNCOMMITTED(未提交读), 在 read uncommitted级别,事务中的修改,及时没有提交,对其他事 ...
- ceph mimc版本ceph-deploy安装与配置
系统环境centos7.6 内核5.0.9 YUM源配置 [root@k8s-sys-10-82-4-200 ceph-cluster]# cat /etc/yum.repos.d/ceph.repo ...
- Appium Capabilities 详解(Android适配/IOS后续再补充)
Appium 关键字 关键字 描述 实例 automationName 你想使用的自动化测试引擎 Appium (默认) 或 Selendroid platformName 你要测试的手机操作系统 i ...
- python中的集合
在python中,普通集合是可变数据类型 通过以下案例说明: >>> s = {1, 2, 3, 4} >>> id(s) 2108634636808 >&g ...