程序计数器
当前线程执行字节码行号指示器
每个线程都有一个计数器,是线程私有的。
栈
JAVA虚拟机栈
存储方法调用创建的栈帧,执行方法栈帧入栈,方法退出,出栈。线程私有
本地方法栈
JAVA虚拟机栈,区别是Native方法的栈。线程私有
HotSpot虚拟机不区分上面两个栈
这两个栈可能发生StackOverFlow(方法调用过深,栈帧占用内存超出栈大小)
与OOM(没有足够内存创建新栈(线程))
虚拟机 通过-Xss参数可以配置每条线程本地方法栈的大小-Xss128k
因此可以通过调小栈大小达到增加最大创建线程数的目的
栈包含许多栈帧,每个帧存储局部变量表,操作数栈,动态链接,返回地址等信息
JAVA堆
存放对象实例,GC的主要区域,分代(新生代,老年代),线程共享
GC算法:
①标记清除: a.标记要回收的对象 b.回收被标记的对象 (碎片问题,效率问题)
②复制算法(新生代使用): 0. 内存分大小相同的两块,每次只用一块 a.将存活对象移动到另外一块内存 b.清理已使用的内存 (内存少一半)
③标记整理(老年代使用): a.标记要回收的对象 b.存活对象向一端移动,清理端外的内存
堆分代收集
新生代(minor GC): 一个Eden区,两个Survival区from,to
。 Eden:Survivals = 8 : 1
老年代(full GC):新生代:老年代 = 1:2.
- 分配内存优先在
Eden区
分配,大对象直接进入老年代 - 当Eden区空间不足以分配内存,就会触发
minorGC
-
Eden区
活对象复制到to区
,from区
中仍存活且gc次数未达到上限的对象也复制到to区
- 分配担保:
minorGC
前检查老年代可用的空间是否足以放下新生代所有对象
虚拟机如果允许担保失败,检测老年代的剩余空间如果大于每次新生代晋升老年代对象大小的平均值,就再进行一次minorGC
。
如果不允许担保失败,或剩余空间小于平均晋升大小,就进行fullGC
GC安全点与安全区域
??./.todo
为什么分代?
- 优化GC性能,大多数情况都是进行minorGC,效率比较快。
为什么新生代复制而老年代标记整理?
- 新生代生命周期短(朝生夕死),每次GC一般只有少量对象存活,标记成本高,因此采用复制算法复制存活对象就可以完成GC,效率高。
- 老年代存活率高,而且存放对象很多都是大对象,使用复制算法内存要少一半,使用标记清除碎片多。
方法区
存储加载的类信息 ,常量,静态变量
Class 对象???、final、static
线程共享
运行时常量池 Ta在方法区内
Ta
存储运行期间加入的常量String::intern()
以及.class
文件中的常量池
直接内存
NIO
对象的创建
new object()
1、类加载、解析、初始化:虚拟机遇到new时先检查此指令的参数是否能在常量池 中找到类的符号引用,并检查符号引用代表的类是否被加载、解析、初始化,若没有则先进行类加载。
2、对象内存分配:虚拟机为新生对象分配内存,对象所需内存大小在类加载完成后便可完全确定。分配内存的任务等同于从堆中分出一块确定大小的内存。
分配方式分为指针碰撞 (空闲内存是整理好的,从空闲的内存割一块)
和空闲列表(内存是乱的,找到足够大的空闲的一块分配)
并发控制:多个对象同时从堆中分配内存因此需要同步
两种解决方案:
- 虚拟机用CAS配上失败重试保证原子操作;
- 把内存分配动作按线程划分在不同空间中进行,即每个线程预先分配一块线程本地缓冲区TLAB,各线程在各自TLAB为各自对象分配内存。
3、对象的初始化:对象头和对象实例数据的初始化 <init>
- 调用
new
操作符 - 调用
Class
或java.lang.reflect.Constructor
对象的newInstance()
方法 - 调用任何现有对象的
clone()
方法 - 通过
java.io.ObjectInputStream
类的getObject()
方法反序列化
定位对象
句柄访问
image.png内存移动(GC)的时候,本地变量表中reference不用变,只用改变句柄中的指针
但是多了一次指针定位
直接指针
image.png相反。少一次
image.png image.png image.png
网友评论