程序计数器:它是较小的一块内存区域,分支、循环、跳转、异常处理和线程恢复等基础功能都依赖计数器。
Java方法栈:栈内存,线程创建时创建,它的生命周期随线程的生命周期,线程结束时栈内存随之释放。局部变量表存放编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double),局部变量表所需的内存空间在编译期间完成分配,在方法运行前,局部变量表所需的内存空间是固定的。
本地方法栈:一个本地方法(Native Method)就是一个java调用非java代码的接口,为java虚拟机提供本地方法的服务。
堆:是被所有线程共享的内存区域。在JVM启动时创建,专门用来保存对象的实例。保存对象实例也是保存对象的属性值,并不保存对象的方法。对象实例在堆分配好后,需在栈中保存一个4字节的Heap内存地址,它用来定位该对象在Heap中的位置。
内存分为两块:permantspace(持久代,永久代)和heapspace。
永久代(permantspace)是在方法区,方法区逻辑上属于堆的一部分,为了与堆区分,又被称为“非堆”。
永久代中主要存放用于存放静态类型数据,如java class,method等。
虚拟机栈(栈中的本地变量表)中引用的对象;方法区中的类静态属性引用的对象、常量引用的对象;本地方法栈JNI(Java Native Interface)中引用的对象可以作为GC Roots的对象。JVM是通过根搜索算法判定对象是否存活的。从GC Roots对象开始向下搜索。若一个对象到GC Roots没有任何引用链相连(不可达)时,证明该对象是不可用的。若两次标记后,对象在finalize()方法中没有重新与引用链上的任何一个对象建立连接,则会被垃圾回收。(注finalize()方法只被系统自动调用一次)
永久代(持久代)内存溢出原因: 比如,1、一些第三方的框架,如spring、hibernate等通过字节码生成技术(CGLib)来增强功能,可能需要更大的方法区来存储动态生成的class类 2、服务器热部署之后,原来的class没有被卸载掉 3、应用程序大,涉及的类多,而所分配的持久代较小也会出现这样的问题
永久代的垃圾回收是以下两类:
1、废弃常量:若字符串“abc”进入了常量池,但当前系统没有任何String对象引用“abc”常量,则当发生内存回收(有必要的情况下)时,常量就会被回收。
2、无用的类:
- 该类的所有实例已被回收,Java堆不存在该类实例
- 加载该类的classloader已被回收
- 加载该类的class对象,没有任何地方引用,且也不能通过反射访问
heapspace分为年轻代和老年代。
年轻带的垃圾回收叫Young GC,老年代的垃圾回收叫Full GC。
在年轻代中经历了N次(可配置)垃圾回收后仍然存活的对象,就会被复制到老年代。(因此一般老年代可以被认为是存放生命周期较长的对象)
垃圾回收算法:
- 标记-清除算法(Mark-Swap):标记清除后会产生大量的碎片,以后分配内存较大的对象可能会找不到足够大的连续内存空间,从而导致下一次GC
- 复制算法:由于新生代的对象生命周期较短,老年代的对象存活率较高,把内存空间分成1块Eden空间和2块Survivor空间。垃圾回收时,把Eden和Survivor空间中活着的对象一次性复制到另一块Survivor空间中,再清理Eden和Survivor的空间。HotSpot默认Eden:Survivor=8:1.
- 标记-整理算法:和Mark-Swap一样,但最后复制后的存活对象向一端移动。
- 分代算法:新生代用复制算法,老年代用标记清理算法活着标记整理算法。