java内存分为堆(heap),栈(stack),方法区(method Area),程序计数器(Program counter Register)
程序计数器:占用很小的内存,线程私有区域。唯一没有内存溢出异常(OutOfMemoryError)的区域。每个线程都有一个程序计数器用来记录程序的执行状态,可以看作是字节码的行号指示器。多线程是通过轮流切换并分配处理器执行时间的方式来实现的。一个确定的时刻一个处理器只有一个线程在运行,每个线程通过程序计数器来恢复之前执行的位置。
栈:分为JVM栈和本地方法栈
java虚拟机栈:线程私有区域。其生命周期与线程相同。
每个方法在执行的同时都会创建一个栈帧用于存储局部变量表(编译器可知的各种基本类型、reference)、操作数栈、动态链接、方法出口等信息,每个方法从调用到完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表的内存空间在编译期完成分配,运行期间不会改变其大小
线程请求的栈深度大于虚拟机所允许的深度 抛出StackOverflowError。还有内存不够用OutOfMemoryError。可以通过递归一个数字的++来造成
本地方法栈:跟java虚拟机栈差不多
java堆:线程共享的区域,所有对象的实例都在这里分配内存。是java垃圾收集器管理的主要区域。
方法区:线程共享区域,存储已经被虚拟机加载的类的信息(类名、访问修饰符、字段描述、方法描述),常量、静态变量、即时编译编译后的代码数据。方法区的内存回收比较苛刻(永久带),一般是对常量池的回收和对类型的卸载。
运行时常量池:存储编译期字面常量符号引用,这部分内容在类加载后进入方法区的运行时常量池。如文本字符串,声明为final的常量值等
Java中八种基本类型的包装类的大部分都实现了常量池技术,它们是Byte、Short、Integer、Long、Character、Boolean,另外两种浮点数类型的包装类(Float、Double)则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值在-128到127时才可使用对象池。
直接内存:nio的知识,通过 ByteBuffer.allocateDirect()创建,在堆外分配内存,
对象的创建new object():虚拟机遇到一个new的指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用所代表的类是否已被加载、解析和初始化过。如果没有必须执行相应的类加载过程。对象所需内存在类加载完成后便可完全确定,
hotsopt虚拟机中,对象在内存分为3个部分对象头、实例数据、对齐填充
对象头:哈希码、GC分代年龄,锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。还可能存储查找对象元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
引用:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)
- 强引用:不会被回收
- 软引用:在系统将要发生内存溢出异常之前,把这些对象列进回收范围之中进行二次回收
- 弱引用:被弱引用关联的对象只能存活到下次垃圾收集发生之前
- 虚引用:....
GC回收:
通过GC Roots对象作为起点,从这些节点向下搜索,搜索所走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连,则证明此对象不可用
java语言中,可作为GC Roots 的对象包括以下几种
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI引用的对象
一个对象死亡要至少经过两次标记过程,第一次标记并进行筛选,筛选条件此对象是否有必要执行finalize()方法,如果有必要执行,对象放到F-Queue的队列中,并由虚拟机低优先级线程进行去执行。但并不承诺会等待它运行结束。可以在finalize()方法中将this赋给某个变量(最好不要介么做)。第二次标记时它将被移出即将回收的集合。
内存回收:要回收的内存分为新生代和老年代,
新生代分为3个部分8:1:1 每次回收90%,将还存活的对象复制到另外10%,如果不够用将通过分配担保机制分配到老年代, 然后清空那90%。(复制算法)
老年代对象存活率较高,所以采用标记-整理算法,让存活的对象向一端移动,然后清理边界以外的内存。
每次GC都会对新生代存活下的对象年龄加一如果熬过一个晋升老年代的阀值,就将其放到老年代中,阀值不是绝对标准