Java代码是运行在Java虚拟机(JVM)上的,Java虚拟机通过解
释执行(解释器)或编译执行(编译器)来完成。Java内存模
型分为5个部分: 方法区(Method Area),Java堆 (Heap),Java栈(VM Stack),本地方法栈(Native Method Stack),程序计数器(PC 寄存器)
线程私有的,与线程在同一时间创建。
管理JAVA方法执行的 内存模型。每个方法执行时都会创建一个桢栈来存储方法的 的变量表(基本数据类型)、操作数栈、动态链接方法、返 回值、返回地址等信息。栈的大小决定了方法调用的可达深 度(递归多少层次,或嵌套调用多少层其他方法,-Xss参数可 以设置虚拟机栈大小)。栈的大小可以是固定的,或者是动 态扩展的。如果请求的栈深度大于最大可用深度,则抛出 StackOverflowError;如果栈是可动态扩展的,但没有内存空 间支持扩展,则抛出OutOfMemory错误
线程共享的,存放所有对象实例和数组。垃圾回收的主要区 域。可以分为新生代(Eden)、老年代(tenured)、永久代 (PermGen,Java8废弃,替换为Metaspace:本地内存)。新生 代用于存放刚创建的对象以及年轻的对象,如果对象一直没 有被回收,生存得足够长,老年对象就会被移入永久区。 新 生代又可进一步细分为eden、survivorSpace0(s0,from space)、 survivorSpace1(s1,to space)。刚创建的对象都放入eden,s0和s1 都至都至少经过一次GC并幸存。如果幸存对象经过一定时间仍存 在,则进入老年代(tenured)。 如果在堆中没有内存完成实例 分配,且堆也无法再扩展时,将会抛出OutOfMemoryError 错 误
线程共享的,用于存放被虚拟机加载的类的元数据信息:如 常量、静态变量、即使编译器编译后的代码。也成为永久 代。如果hotspot虚拟机确定一个类的定义信息不会被使用, 也会将其回收。回收的基本条件至少有:所有该类的实例被 回收,而且装载该类的ClassLoader被回收 根据Java 虚拟机规 范的规定, 当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError。
JDK1.8之后方法区中的常量存储被转移到堆空间。
多线程时,当线程数超过CPU数量或CPU内核数量,线程之间 就要根据时间片轮询抢夺CPU时间资源。因此每个线程有要有 一个独立的程序计数器,记录下一条要运行的指令。线程私 有的内存区域。如果执行的是JAVA方法,计数器记录正在执 行的java字节码地址,如果执行的是native方法,则计数器为 空
本地方法栈和JVM栈非常相似,它们之间的区别不过是jvm栈是为了执行java方法的服务,而本地方法栈式为了jvm使用到对本地方法服务(由c/c++编写)。HotSpot虚拟机中直接把本地方法栈和JVM栈合并为一了。
在C/C++中,存在指针的概念,指针(向内存申请空间)创建之 后,在使用完时需要手动回收,因此经常会出现回收不当, 导致内存溢出;但是,java中不存在指针的概念(实际是引用 类型),因为JVM中有GC(Garbage Collection:垃圾收集器)机 制,能够在内存不够时自动运行清理可回收空间,不再需要 程序手动对内存清理。
从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片(碎片化),碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题
这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。
缺点:效率慢。并且移动他的内存空间会对用户产生速度的
影响。
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它 的核心思想是根据对象存活的生命周期将内存划分为若干个 不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点 是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可 以根据不同代的特点采取最适合的收集算法。 目前大部分垃圾收集器对于新生代都采取Copying算法, 因为新生代中每次垃圾回收都要回收大部分对象,也就是说 需要复制的操作次数较少,但是实际中并不是按照1:1的比 例来划分新生代的空间的,一般来说是将新生代划分为一块 较大的Eden空间和两块较小的Survivor空间,每次使用Eden空 间和其中的一块Survivor空间,当进行回收时,将Eden和 Survivor中还存活的对象复制到另一块
而由于老年代的特点是每次回收都只回收少量对象,一 般使用的是Mark-Compact算法。 注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永 久代的回收主要回收两部分内容:废弃常量和无用的类。 (Java8中PermGen替换成Metaspace)
java8之前该区域位于方法区,Java8之后替换到堆区
类加载机制指的是将java源文件产生的 *.class 文件中的二进 制数据装载到内存(JVM)的过程,将其放在运行时数据区的 方法区内,然后在堆区创建一个java.lang.Class对象,用来封装 类在方法区内的数据结构。类的加载的最终产品是位于堆区 中的Class对象,Class对象封装了类在方法区内的数据结构, 并且向Java程序员提供了访问方法区内的数据结构的接口;JVM类加载机制分为五个部分:加载,验证,准备,解析,初 始化。以下是一个java类的生命周期:
通常重点关注:
加载
连接