内存分区
线程共享:堆、方法区
线程私有内存:虚拟机栈(栈)、本地方法栈、程序计数器
程序计数器:因为对于每个线程,都会有一个内核来执行,因此需要单独的计数器来指向下一条语句位置,所以是线程独享。
虚拟机栈:描述java方法的执行的内存模型,每个方法执行时!会创建一个栈针,存储局部变量表、操作数表、动态链接、方法出口
局部变量表(编译期可知):存放基本数据类型、引用类型(引用地址)、returnAddress(返回地址),均占1个局部空间,long和double占两个局部空间(是局部空间),进入方法的时候,需要栈针空间完全确定,不会改变
本地方法栈:执行本地代码、native方法服务
Java堆:所有对象实例、数组分配地址,但是栈上分配、标量替换会导致出现不同解决,也被称作GC堆
方法区:线程共享,用于存储已被虚拟机加载的类信息,常量、静态变量、即时编译器编译后的代码等数据,Non-Heap,较少出现垃圾清理行为。
运行时常量池,将类预先置入Class文件中的常量池装载,但是不一定会只有这些,String方法的intern(),这个方法如果池已经包含与equals(Object)方法确定的相当于此String对象的字符串,则返回来自池的字符串。 否则,此String对象将添加到池中,并返回对此String对象的引用。也就是说这个方法可能会产生新的常量,也就是运行时才能知道的常量,也会放到这个常量池
创建对象
遇到new语句,先检查能否在常量池定位到这个类的符号引用,然后检查对应的类是否被加载,若没加载,必须经过:加载、连接、初始化过程,然后分配内存。
分配内存两种方式:
① 若内存堆规整(一边是使用过的内存,另一边是没使用过的内存),“指针碰撞模式”,每次分配内存只需要将指示边缘的指针向空闲内存方向移动分配大小的距离
② 不规整,采用空闲列表,记录所有空闲内存块,分配后更新空闲列表,一般若GC收集器没有Compact过程的采用这种,标记——清理算法
并发分配内存情况:A分配内存没来得及修改指针导致B再次分配
解决方案:
TLAB 将内存分配的动作按照线程来划分不同的空间,不同线程分配在各自的TLAB区域,直到TLAB区域的内存用尽,才会用共用内存,这时候加入同步锁定
或者采用CAS配上失败重试的方式保证更新的原子性(同步锁定)
分配内存后,将空间全部初始化为0(不一定在这个时候,如果使用TLAB可能在分配TLAB时就初始化为0),保证实例字段不赋初始值也能使用——基本类型的初始值
设置元信息,哈希码、GC分代年龄(判断进入老年代标志) 占据1~2个字节
访问定位:通过句柄或直接指向堆对象
通过句柄,GC后的修改方便(Sun HotSpot使用-)
指向堆对象:访问速度快、
通过减少内存来解决内存溢出
为线程的栈分配的内存越大,那么可建立的线程越少,因为总栈内存是固定的,因此如果多线程下出现内存溢出问题,很有可能是 线程栈的内存过大
而栈溢出就不一样