方法区与Java堆同样,是各个线程共享的区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译(JIT)后的代码等数据。对于JDK1.8以前的HotSpot虚拟机而言,不少人常常将方法区称为咱们上图中所描述的永久代,实际上二者并不等价,由于这仅仅是HotSpot的设计团队选择利用永久代来实现方法区而言。同时对于其余虚拟机好比IBM J9中是不存在永久代的概念的。 其实,移除永久代的工做从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没彻底移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。而在JDK1.8以后永久代概念也已经再也不存在取而代之的是元空间metaspace。java
常量池实际上是方法区中的一部分,由于这里比较重要,因此咱们拿出来单独看一下。注意咱们这里所说的运行时的常量池并仅仅是指Class文件中的常量池,由于JVM可能会进行即时编译进行优化,在运行时将部分常量载入到常量池中。面试
JVM中的程序计数器和计算机组成原理中提到的程序计数器PC概念相似,是线程私有的,用来记录当前执行的字节码位置。仍是稍微解释一下吧,CPU的占有时间是以分片的形式分配给给每一个不一样线程的,从操做系统的角度来说,在不一样线程之间切换的时候就是依赖程序计数器来记录上一次线程所执行到具体的代码的行数,在JVM中就是字节码。算法
与程序计数器同样,Java虚拟机栈也是线程私有的,用通俗的话将它就是咱们经常据说到堆栈中的那个“栈内存”。虚拟机栈描述的是Java方法执行的内存模型:每一个方法在执行的同时都会建立一个栈帧(Stack Frame)用于存储局部变量表(局部变量表须要的内存在编译期间就肯定了因此在方法运行期间不会改变大小),操做数栈,动态连接,方法出口等信息。每个方法从调用至出栈的过程,就对应着栈帧在虚拟机中从入栈到出栈的过程。p.s: 关于栈帧这里咱们之后讲虚拟机字节码执行引擎的时候再来仔细分析。数组
本地方法栈和Java虚拟机栈相似,只不过是为JVM执行Native方法服务,这里就不解释了。eclipse
堆是用来存放对象的内存空间, 几乎全部的对象都存储在堆中。
堆的特色: 线程共享 整个Java虚拟机只有一个堆,全部的线程都访问同一个堆。而程序计数器、Java虚拟机栈、本地方法栈都是一个线程对应一个的。 在虚拟机启动时建立 垃圾回收的主要场所。 能够进一步细分为:新生代、老年代。 新生代又可被分为:Eden、From Survior、To Survior。 不一样的区域存放具备不一样生命周期的对象。这样能够根据不一样的区域使用不一样的垃圾回收算法,从而更具备针对性,从而更高效。 堆的大小既能够固定也能够扩展,但主流的虚拟机堆的大小是可扩展的,所以当线程请求分配内存,但堆已满,且内存已满没法再扩展时,就抛出OutOfMemoryError。工具
Java虚拟机的内存模型中一共有两个“栈”,分别是:Java虚拟机栈和本地方法栈。 两个“栈”的功能相似,都是方法运行过程的内存模型。而且两个“栈”内部构造相同,都是线程私有。 只不过Java虚拟机栈描述的是Java方法运行过程的内存模型,而本地方法栈是描述Java本地方法运行过程的内存模型。 Java虚拟机的内存模型中一共有两个“堆”,一个是本来的堆,一个是方法区。方法区本质上是属于堆的一个逻辑部分。堆中存放对象,方法区中存放类信息、常量、静态变量、即时编译器编译的代码。 堆是Java虚拟机中最大的一块内存区域,也是垃圾收集器主要的工做区域。 程序计数器、Java虚拟机栈、本地方法栈是线程私有的,即每一个线程都拥有各自的程序计数器、Java虚拟机栈、本地方法区。而且他们的生命周期和所属的线程同样。 而堆、方法区是线程共享的,在Java虚拟机中只有一个堆、一个方法栈。并在JVM启动的时候就建立,JVM中止才销毁。优化
JVM 分为堆区和栈区,还有方法区,初始化的对象放在堆里面,引用放在栈里面,class类信息常量池(static常量和static变量)等放在方法区new:方法区:主要是存储类信息,常量池(static常量和static变量),编译后的代码(字节码)等数据堆:初始化的对象,成员变量 (那种非static的变量),全部的对象实例和数组都要在堆上分配栈:栈的结构是栈帧组成的,调用一个方法就压入一帧,帧上面存储局部变量表,操做数栈,方法出口等信息,局部变量表存放的是8大基础类型加上一个应用类型,因此仍是一个指向地址的指针本地方法栈:主要为Native方法服务程序计数器:记录当前线程执行的行号
spa
引用计数法:指的是若是某个地方引用了这个对象就+1,若是失效了就-1,当为0就会回收可是JVM没有用这种方式,由于没法断定相互循环引用(A引用B,B引用A)的状况
引用链法: 经过一种GC ROOT的对象(方法区中静态变量引用的对象等-static变量)来判断,若是有一条链可以到达GC ROOT就说明,不能到达GC ROOT就说明能够回收操作系统
先标记,标记完毕以后再清除,效率不高,会产生碎片
复制算法:分为8:1的Eden区和survivor区,就是上面谈到的YGC
标记整理:标记完毕以后,让全部存活的对象向一端移动
线程
jstack能够看当前栈的状况,jmap查看内存,jhat 进行dump堆的信息 mat(eclipse的也要了解一下)
加载、验证、准备、解析、初始化。而后是使用和卸载了经过全限定名来加载生成class对象到内存中,而后进行验证这个class文件,包括文件格式校验、元数据验证,字节码校验等。准备是对这个对象分配内存。解析是将符号引用转化为直接引用(指针引用),初始化就是开始执行构造器的代码
Bootstrap ClassLoader:启动类加载器,负责将Java_Home /lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。ApplicationClassLoader:它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者能够直接使用系统类加载器双亲委派模型是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,若是父类加载器能够完成类加载任务,就成功返回;只有父类加载器没法完成此加载任务时,才本身去加载。-----例如类java.lang.Object,它存在在rt.jar中,不管哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,所以Object类在程序的各类类加载器环境中都是同一个类。相反,若是没有双亲委派模型而是由各个类加载器自行加载的话,若是用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不一样的Object类,程序将混乱
好比GC的时候必需要等到Java线程都进入到safepoint的时候VMThread才能开始执行GC, 循环的末尾 (防止大循环的时候一直不进入safepoint,而其余线程在等待它进入safepoint) 方法返回前 调用方法的call以后 抛出异常的位置
判断一个对象是否存活有两种方法:
2.可达性算法(引用链法) 该算法的思想是:从一个被称为GC Roots的对象开始向下搜索,若是一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。 在java中能够做为GC Roots的对象有如下几种:
虚拟机栈中引用的对象
方法区类静态属性引用的对象
方法区常量池引用的对象
本地方法栈JNI引用的对象
虽然这些算法能够断定一个对象是否能被回收,可是当知足上述条件时,一个对象比不必定会被回收。当一个对象不可达GC Root时,这个对象并
不会立马被回收,而是出于一个死缓的阶段,若要被真正的回收须要经历两次标记
若是对象在可达性分析中没有与GC
Root的引用链,那么此时就会被第一次标记而且进行一次筛选,筛选的条件是是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者已被虚拟机调用过,那么就认为是不必的。
若是该对象有必要执行finalize()方法,那么这个对象将会放在一个称为F-Queue的对队列中,虚拟机会触发一个Finalize()线程去执行,此线程是低优先级的,而且虚拟机不会承诺一直等待它运行完,这是由于若是finalize()执行缓慢或者发生了死锁,那么就会形成F-Queue队列一直等待,形成了内存回收系统的崩溃。GC对处于F-Queue中的对象进行第二次被标记,这时,该对象将被移除”即将回收”集合,等待回收。