总览
-
以是否为
线程独享
作为区分度
-
引入本地内存一览
程序计数器
程序计数器也叫PC
计数器,用于记录程序当前所运行到的指令行。在多线程中,CPU会对就绪状态的线程进行随机调度,此时就需要程序计数器来记录不同线程所执行到的位置。
程序计数器是唯一
不会发生OOM(OutOfMemory)的地方。
虚拟机栈
其实虚拟机栈和本地方法栈都是栈结构
。在方法调用中就容易看出这个结构:
public void one(){
System.out.println("one start");
two();
System.out.println("one finish");
}
public void two(){
System.out.println("two start");
three();
System.out.println("two finish");
}
public void three(){
System.out.println("three start");
System.out.println("three finish");
}
不难看出,在方法调用中先调用的方法最后结束;这个结构就跟栈一样。
虚拟机栈主要是用于普通方法。每个方法在执行的时候都会创建一个栈帧,其包含了局部变量表
、操作数栈
、动态链接
、返回出口
。
可以反编译全字节码出得相关信息:
-
先通过
javac -g XXX.java
编译成字节码包含所有调试信息
-
通过
javap -v XX.class
命令,打印出反编译后的字节码文件
-
最终得到反编译后的结果(下图只附上了add方法)
下面是《深入理解虚拟机》关于局部变量表的一段话:
局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、 float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始 地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型
说明局部变量不仅存储八大基本类型,还存放引用类型(即对象实例的引用)。
并且,在栈中会发生栈溢出
以及OOM
。
先来说栈溢出。当发生疯狂递归调用并且没有出口的时候就会爆栈;在平时刷题或者编程写递归的时候就要主要达到某个条件就结束递归这个basecase。
其次是内存溢出。对象的创建主要是在堆空间
,但是在栈中也会发生内存溢出。首先是每个栈的内存大小可以控制,但是总的大小无法控制
,如果开千万个线程是有可能会爆的;其次就是申请不到栈空间,因为内存已满造成的内存溢出错误。
本地方法栈
本地方法栈就是执行本地方法,即native方法
。该方法就是已经被封装好了,由虚拟机底层c++代码进行调用。
可以通过一些方法来查看相关native方法对应C++/C中的底层源码。
堆
堆空间又称为GC堆
。GC即垃圾收集,因为在所有的对象都在堆中创建生成,分配内存(抛开栈上逃逸)。并且在JDK1.7以后,常量池从方法区迁移到了堆中。
说到这里就要类比C++/C了,在Java中虚拟机有垃圾回收机制,即使创建的对象不做释放处理垃圾回收也会根据特定的规则
判断是否是垃圾需要回收。
而C++/C中谁创建的(molloc),谁负责释放。
关于堆的分区:
- 大体分成
新生代
和老年代
- 新生代又分成
Eden
、From
、To
三个区 - 分区的目的是为了
更好的进行垃圾回收
关于虚拟机的简单调参:
N表示具体数值
- Xmx:Nm 堆空间
最大
的容量 - Xms:Nm 堆空间
初始
容量 - Xmn:Nm
新生代
最大空间
OOM演示:
这块要保证创建的对象都要用=
赋值(强引用),保证对象不会被垃圾回收器回收。
-
设置最大空间
-
使用容器保证对象不被回收
-
强引用保证对象不被回收
方法区
主要存放静态变量
,相关类信息
、常量
、常量池(1.7及以前)
在1.7及以前叫永久代
,跟堆处一块;1.8以后叫元空间
,扔到直接内存去。
因为在之前,使用HotSpot虚拟机。堆跟方法区使用同样的垃圾回收器,在堆中随时可能产生垃圾;但是在方法区的可能要很久才会产生垃圾。
相当于叫人不停的去打扫比较干净的区域
,浪费了性能。所以在1.8后就放到直接内存去,采用不同的垃圾回收器
进行回收,带来了一定的效率。
直接内存
在引用NIO
加入了缓冲区以后,避免操作系统不断的进行读写拷贝,实现零拷贝操作。直接在宿主机内存区域中划出一块直接内存。该区域不属于虚拟机规范,即不算运行时数据区,但是因为被频繁使用
,所以才划出这一块。
在java堆内可以用directByteBuffer
对象直接引用并操作
虽然不属于虚拟机规范,但是也是可控制的,并且也会发生OOM的错误。
参数设置
在默认的情况下,他的大小跟-Xmx
大小一样。即堆的最大容量就是直接内存的大小。
- -XX:MaxDirectMemorySize=Nm
- 限制最大直接内存大小100m
可以通过ByteBuffer.allocateDirect(128*1024*1204)
来分配直接内存,可以做直接内存溢出的错误示例:
小结
关于运行时数据区
线程共享:堆,方法区,直接内存
线程独有:虚拟机栈,本地方法栈,程序计数器
线程独有的空间会随着线程的结束而消亡
,而线程共享的生命周期跟随整个应用
。
如果需要查看每个区存储内容(例如方法区放类信息,静态变量,常量),可以设置虚拟机参数打印垃圾回收细节(命令-XX:+PrintGCDetails
)。根据控制变量法对比即可得出相关结论。