月初的时候个人网站到期了, 不想再折腾重新建站了, 以后还是来第三方博客写文章吧, 可以省去很多问题. 之前写的文章也不是很多, 备份懒得做了, 从头开始吧. 博文仅仅是用来记录和学习总结, 如有错误之处请帮忙指正!
今天想说说 JVM 内存结构的问题, 说到 JVM 大家肯定首先想到的是栈和堆. 的确, 这两块说是 JVM 内存结构最重要的部分也不为过. 先来简单介绍一下吧,
内存结构按照私有和共享划分方式如下:
线程私有: 栈区, 本地方法栈, 程序计数器
线程共享: 堆区, 方法区
其他不说了, 重点说说栈和堆
栈:
栈的特点就是快. 每个线程对应一个栈, 每个栈含有 1 个或多个栈帧, 栈帧用来存放方法的局部变量表, 操作数栈, 动态链接, returnAddress 等信息, 每运行一个方法就会创建一个栈帧, 从方法运行到结束对应着栈帧在栈区里面入栈和出栈的过程.
局部变量表包含两类数据结构, 一是八大基本类型, byte short int long float double char boolean; 第二类是 reference 类型, 占用空间是 4byte, 不管是对象是大是小, 操控它仅需要用一个 4byte 的变量即可, 而且可以按需销毁, 这足以体现栈堆分离的好处.
栈空间是可以重复利用的, 遇到左括号入栈, 遇到右括号出栈, 我们当系统进行递归调用时, 系统会连续多次执行入栈操作, 直到最深处才执行出栈操作, 这就可能导致栈空间不足, StackOverFlowError 异常, 因此遇到该异常一般不是对象太大导致的, 多是因为不正确递归或栈空间不足以容纳导致. 在栈区还可能会遇到 OOM 异常, 当线程过多时或分配空间过小时, 如果无法申请到足够内存时, 便会报 OOM 异常(栈区 OOM 异常的原因暂不确定也为遇见过, 只是周志明 JVM 书中提到栈区 OOM 异常, 上网也未查到).JVM 调优: 通过 - Xss 来控制栈空间大小.
堆:
堆是 JVM 内存中最大的一块, 是用来为对象和数组元素分配空间的地方, 而对象的引用变量和数组引用都是在栈中存放的."几乎所有的对象都在这里创建", 那么什么情况下对象不在堆中创建呢? 随着 JIT 编译器的发展, 在编译期间, 如果 JIT 经逃逸分析后发现对象没有逃逸出方法, 那么该对象坑会在栈上分配内存而不是堆, 但是也不绝对. 测试: 为 JVM 分配足够空间, 关闭逃逸分析, 创建一百万个 User 对象, jmap 命令发现堆中创建了一百万个对象, 开启逃逸分析, 发现堆中的对象变成了八万个, 也就是说 JIT 编译器确实会将对象分配在栈里, 但并不绝对.
使用逃逸分析, 编译器可以针对分析结果做以下优化:
1, 同步省略(锁消除优化): 如果一个对象被发现只能被一个线程访问, 那么可以不考虑该对象的同步;
2, 栈上分配对象; 如果一个对象在子程序中被分配, 要是指向该对象的指针永远不会逃逸, 对象可能会在栈中被分配, 而不是在堆中;
3, 分离对象或标量替换: 有的对象可能不需要连续的内存结构, 可以将该对象的部分或全部存储到 CPU 的寄存器中.
逃逸分析:-XX:+/-DoEscapeAnalysis
关于堆的分代和垃圾收集下次再讲.
顺带讲一下, 参数传递的时候, Java 是传值还是传引用? 这个问题相信每个程序员都会思考过, 也肯定遇到过由此引发的问题.
要说明这个问题, 首先要说明两点: 一是不要和 C 语言类比, Java 没有指针的概念; 二是程序运行永远都是在栈中进行, 因而参数传递时涉及到的只会是基本数据类型和引用类型, 理论上来说不会涉及到对象本身. 而 Java 中没有指针的概念, 因此 Java 可以说是传值调用, 当参数是引用类型时传递的是引用变量的值; 但是当进入被调用方法时, 被传递的这个引用的值, 被程序解释 (或者查找) 到堆中的对象, 这个时候才对应到真正的对象, 这个时候如果对其进行修改, 修改的并不是引用本身, 而是对象的属性, 所以对对象的修改会保持. 程序参数传递时, 被传递的值本身都是不能进行修改的, 但是, 如果这个值是一个引用, 则可以修改这个引用背后的东西.
来源: http://www.bubuko.com/infodetail-3122140.html