JVM内存模型与操作系统内存模型
本地方法栈
与虚拟机栈发挥的作用是相似的,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。在虚拟机规范中对本地方法栈中使用、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机 比如(SUn HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryErr异常
调用JNI程序(安卓需要操作硬件),Java没有办法直接操作硬件。只能用C/C++汇编调用需要加载动态链接库(Sytem.loading())手动加载jar包
// 点亮屏幕
public static native void light();
这些动态链接库下面是一些操作系统内核中的驱动。在做嵌入式的驱动硬件中,之前是嵌入Linux内核,现在是嵌入Android系统。
Java通过JNI调用C/C++动态链接库需要的栈,随着socket的发展,JNI技术已经用得非常非常少了
堆
对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得表示那么"绝对"了。Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做"GC堆"(Garbage Collected Heap,幸好国内没有翻译成"垃圾堆"),从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分:新生代和老年代:再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB).不过无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。
根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
堆区min:1/64 物理内存 max:1/4物理内存
为什么老年代空间>新生代空间? 因为老年代要存储的东西比新生代多
- 1.GC大于15的对象(跟对象头有关,4个bit)
- 2.空间担保机制
- 3.动态年龄判断机制:新生代没有足够的空间存放,
为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象地年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小地综合大于Survivor空间地一般,年龄大于或等于该年龄地对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。 - 4.大对象
所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。大对象对虚拟机的内存分配来说就是一个坏消息(比遇到一个大对象更加坏的消息就是遇到一群"朝生夕死"的"短命大对象",写程序的时候应当避免),经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来"安置"它们。虚拟机提供了一个参数,大于这个参数的对象直接在老年代分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制
内存模型中最核心的区域,也是JVM调优重点关注的区域
对象的创建
DCL中单例对象的创建为什么要加volatile?
如果在ns级别的超高并发是需要加volatile关键字的,这个关键字会禁止指令重排,因为CPU是会乱序执行这些指令的。如下指令
接下来我们看下一个对象的创建流程
0 new #2
1.堆区申请了内存(不完全对象)构造方法还未执行
2.内存地址压入栈
3 dup duplicate
1.赋值栈顶元素
为什么要复制?
因为接下来调用init非静态方法,但是this指针还是空的,所以需要把栈顶元素弹出去,给this指针赋值,然后再把元素压入栈
2.再次压入栈
4 invokespecial #3 方法
this指针
this = null
执行方法分为两步:
1.构建环境会涉及到创建栈帧、传参、保存现场。给this指针赋值,记录方法的执行之前的位置
2.执行
7 astore_1
1.pop出元素:对象的指针
2.赋值给index=1的位置的变量(局部变量)
JVM内存模型的设计与GC收集器有很大关系
G1之前是新生代 + 老年代
之后划分2048个块,每个块大小为2M,基于Region模型
新生代中在一轮GC之后,90~95%的对象都会被回收,采用复制的话,避免了内存整理,操作比较简单。
分代+复制算法:为了保证内存使用的高效性
标记-清楚算法: 会有碎片化问题
标记-整理算法:整理算法 内存合并算法 耗费CPU,