其余更多java基础文章:
java基础学习(目录)html
学习资料
Java虚拟机规范
方法区的Class信息,又称为永久代,是否属于Java堆?
JVM 内部原理(一)— 概述java
Java虚拟机定义了在程序执行期间使用的各类运行时数据区域。其中一些数据区域是在Java虚拟机启动时建立的,仅在Java虚拟机退出时销毁。其余数据区域是每一个线程。线程数据区域是在线程退出时建立和销毁线程时建立的。
JVM所管理的几个运行时数据区域:方法区、虚拟机栈、本地方法栈、堆、程序计数器,其中方法区和堆是由线程共享的数据区,其余几个是线程隔离的数据区。程序计数器,虚拟机栈,本地方法栈,随线程而生,线程亡而亡。程序员
线程独享面试
线程共享编程
程序计数器是一块较小的内存,他能够看作是当前线程所执行的行号指示器。每一个Java虚拟机线程都有本身的程序计数器,一般程序计数器会在执行指令结束后增长,所以它须要保持下一将要执行指令的地址。 字节码解释器工做的时候就是经过改变这个计数器的值来选取下一条须要执行的字节码的指令,分支、循环、跳转、异常处理、线程恢复等基础功能都须要依赖这个计数器来完成。若是线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;若是正在执行的是Native方法,这个计数器则为空。此内存区域是惟一一个在Java虚拟机规范中没有规定任何OutOfMemotyError状况的区域数组
每一个线程都有本身的栈(stack),栈内以帧(frame)的形式保持着线程内执行的每一个方法。栈是一个后进先出(LIFO)的数据结构,因此当前执行的方法在栈顶部。每次方法调用时,都会建立新的帧而且压入栈的顶部。当方法正常返回或抛出未捕获的异常时,帧或从栈顶移除。除了压入和移除帧对象的操做,栈没有其余直接的操做,所以帧对象分配在堆中,内存并不要求连续。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。执行引擎所运行的全部字节码指令都只针对当前栈帧进行操做。
在Java 虚拟机规范中,对虚拟机栈规定了两种异常情况:若是线程请求的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError
异常;若是虚拟机栈能够动态扩展(当前大部分的Java虚拟机均可动态扩展,只不过Java虚拟机规范中也容许固定长度的虚拟机栈),当扩展时没法申请到足够的内存时会抛出OutOfMemoryError
异常。bash
每次方法调用时,新的帧都会建立并被压入栈顶。当方法正常返回或抛出未捕获异常时,帧会从作退栈操做。详细的异常处理参加后面 异常表(Exception Table)部分。数据结构
每一个帧都包括oracle
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序被编译成Class文件时,就在方法的Code属性的max_locals数据项中肯定了该方法所须要分配的最大局部变量表的容量。
Java虚拟机使用局部变量在方法调用上传递参数。在类方法调用中,任何参数都在从局部变量0开始的连续局部变量中传递。在实例方法调用中,局部变量0始终用于传递对调用实例方法的对象的引用(Java编程语言里的this)。随后,任何参数都在从局部变量1开始的连续局部变量中传递。app
局部变量能够是:
全部的类型都在本地变量数组中占一个槽,而 long
和 double
会占两个连续的槽,由于它们有双倍宽度(64-bit 而不是 32-bit)。reference
类型虚拟机规范没有明确说明它的长度,但通常来讲,虚拟机实现至少都应当能今后引用中直接或者间接地查找到对象在Java堆中的起始地址索引和方法区中的对象类型数据。returnAddress
类型是为字节码指令jsr、jsr_w和ret服务的,它指向了一条字节码指令的地址。
每一个帧包含一个后进先出(LIFO)堆栈,称为其操做数堆栈。帧的操做数堆栈的最大深度在编译时肯定,并与帧相关的方法的代码一块儿提供。
虚拟机把操做数栈做为它的工做区——大多数指令都要从这里弹出数据,执行运算,而后把结果压回操做数栈。好比,iadd指令就要从操做数栈中弹出两个整数,执行加法运算,其结果又压回到操做数栈中,看看下面的示例,它演示了虚拟机是如何把两个int类型的局部变量相加,再把结果保存到第三个局部变量的:
begin
iload_0 // push the int in local variable 0 ontothe stack
iload_1 //push the int in local variable 1 onto the stack
iadd // pop two ints, add them, push result
istore_2 // pop int, store into local variable 2
end
复制代码
在这个字节码序列里,前两个指令iload_0
和iload_1
将存储在局部变量中索引为0和1的整数压入操做数栈中,其后iadd
指令从操做数栈中弹出那两个整数相加,再将结果压入操做数栈。第四条指令istore_2
则从操做数栈中弹出结果,并把它存储到局部变量区索引为2的位置。下图详细表述了这个过程当中局部变量和操做数栈的状态变化,图中没有使用的局部变量区和操做数栈区域以空白表示。
方法的返回分为两种状况,一种是正常退出,退出后会根据方法的定义来决定是否要传返回值给上层的调用者,一种是异常致使的方法结束,这种状况是不会传返回值给上层的调用方法。
不过不管是那种方式的方法结束,在退出当前方法时都会跳转到当前方法被调用的位置,若是方法是正常退出的,则调用者的PC计数器的值就能够做为返回地址,若是是由于异常退出的,则是须要经过异常处理表来肯定。
方法的的一次调用就对应着栈帧在虚拟机栈中的一次入栈出栈操做,所以方法退出时可能作的事情包括:恢复上层方法的局部变量表以及操做数栈,若是有返回值的话,就把返回值压入到调用者栈帧的操做数栈中,还会把PC计数器的值调整为方法调用入口的下一条指令。
虚拟机运行的时候,运行时常量池会保存大量的符号引用,这些符号引用能够当作是每一个方法的间接引用。若是表明栈帧A的方法想调用表明栈帧B的方法,那么这个虚拟机的方法调用指令就会以B方法的符号引用做为参数,可是由于符号引用并非直接指向表明B方法的内存位置,因此在调用以前还必需要将符号引用转换为直接引用,而后经过直接引用才能够访问到真正的方法。
若是符号引用是在类加载阶段或者第一次使用的时候转化为直接应用,那么这种转换成为静态解析,若是是在运行期间转换为直接引用,那么这种转换就成为动态链接。
本地方法栈与虚拟机栈所发挥的做用是很是类似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并无强制规定,所以具体的虚拟机能够自由实现它。甚至有的虚拟机(譬如Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。
与虚拟机栈同样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
堆是运行时分配类实例和数组内存的地方。数组和对象是不能存在栈里的,由于栈帧(frame)不是被设计用做此目的,一旦栈帧建立了,它的大小不可更改。帧只用来存储指向对中对象或数组的引用。与帧内本地变量数组里基本变量和引用不一样,对象老是存储在堆内的,因此在方法结束前,它们不会被移除。并且,对象只能被垃圾回收器移除。
为了支持垃圾回收的机制,堆一般被分为三部分:
对象和数组不会被显式的移除,而是会被 GC 自动回收。一般的顺序是这样:
关于新生代、老年代、GC的详细讲解请关注JVM学习后续文章
本文重点介绍方法区。由于jdk6,7,8中分别对方法区的实现永久代
作了修改。
在JVM虚拟机规范中:
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization. The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous. A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.
在 Java 虚拟机中,方法区( Method Area) 是可供各条线程共享的运行时内存区域。方法区与传统语言中的编译代码储存区( Storage Area Of Compiled Code)或者操做系统进程的正文段( Text egment)的做用很是相似,它存储了每个类的结构信息,例如运行时常量池( Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法。
方法区在虚拟机启动的时候被建立,虽然方法区是堆的逻辑组成部分,可是简单的虚拟机实现能够选择在这个区域不实现垃圾收集。这个版本的 Java 虚拟机规范也不限定实现方法区的内存位置和编译代码的管理策略。方法区的容量能够是固定大小的,也能够随着程序执行的需求动态扩展,并在不须要过多空间时自动收缩。方法区在实际内存空间中能够是不连续的。
Java 虚拟机实现应当提供给程序员或者最终用户调节方法区初始容量的手段,对于能够动态扩展和收缩方法区来讲,则应当提供调节其最大、最小容量的手段
若是方法区的内存空间不能知足内存分配请求,那 Java 虚拟机将抛出一个OutOfMemoryError异常。
总之,就是用来存储类的结构信息。它有一个别名叫作Non-Heap(非堆)。
永久代是HotSpot中方法区的实现。
平时,说到永久代(PermGen space)的时候每每将其和方法区不加区别。这么理解在必定角度也说的过去。 由于,JVM虚拟机规范只是规定了有方法区这么个概念和它的做用,并无规定如何去实现它。那么,在不一样的 JVM 上方法区的实现确定是不一样的了。 同时,大多数用的JVM都是Sun公司的HotSpot。在HotSpot上把GC分代收集扩展至方法区,或者说使用永久代来实现方法区。
虽然能够牵强的解释这种将方法区和永久带等同对待观点。但最终方法区和永久带仍是不一样的。一个是标准一个是实现。
java7以前,方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时能够设置一个固定值,不可变。这里有个在面试中常常问的问题,就是String.intern()方法,详情能够阅读以前我写的一篇文章java基础:String — 字符串常量池与intern(二)
在Highlights of Technology Changes in Java SE 7中,咱们能够看到
In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String.intern() method will see more significant differences.
在JDK 7中,interned strings再也不分配在Java堆的永久代中,而是和应用程序建立的其余对象同样一块儿分配在Java堆的主要部分(称为年轻代和年老代)。此更改将致使主Java堆中的数据更多,而永久代中的数据更少,所以可能须要调整堆大小。因为这种变化,大多数应用程序在堆使用方面只会看到相对较小的差别,可是加载许多类或大量使用String.intern()方法的大型应用程序会看到更显著的差别。
以及方法区的Class信息,又称为永久代,是否属于Java堆? 中,R大的讲解:
Oracle JDK7 / OpenJDK 7的HotSpot VM是把Symbol的存储从PermGen移动到了native memory,而且把静态变量从instanceKlass末尾(位于PermGen内)移动到了java.lang.Class对象的末尾(位于普通Java heap内)。
“常量池”若是说的是runtime constant pool,这个仍是在PermGen里;
“常量池”若是说的是SymbolTable / StringTable,这俩table自身本来就一直在native memory里,是它们所引用的东西在哪里更有意思。
上面说了,7是把SymbolTable引用的Symbol移动到了native memory,而StringTable引用的java.lang.String实例则从PermGen移动到了普通Java heap。
R大说的runtime constant pool即为运行常量池,SymbolTable 即为标识符表,StringTable即字符串常量池。从而咱们能够得知,即 java7中,存储在永久代的部分数据就已经转移到Java Heap或者Native memory。但永久代仍存在于JDK 1.7中,并无彻底移除。
在JEP 122: Remove the Permanent Generation中
Move part of the contents of the permanent generation in Hotspot to the Java heap and the remainder to native memory.
Hotspot's representation of Java classes (referred to here as class meta-data) is currently stored in a portion of the Java heap referred to as the permanent generation. In addition, interned Strings and class static variables are stored in the permanent generation. The permanent generation is managed by Hotspot and must have enough room for all the class meta-data, interned Strings and class statics used by the Java application.
The proposed implementation will allocate class meta-data in native memory and move interned Strings and class statics to the Java heap. Hotspot will explicitly allocate and free the native memory for the class meta-data. Allocation of new class meta-data would be limited by the amount of available native memory rather than fixed by the value of -XX:MaxPermSize, whether the default or specified on the command line.
java8中,取消永久代,方法区存放于元空间(Metaspace),元空间仍然与堆不相连,但与堆共享物理内存,逻辑上可认为在堆中。
直接内存并非虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。 NIO类是一种基于通道和缓冲区的I/O方式,它可使用Native函数库直接分配堆外内存,而后经过一个储存在Java堆中的DirectByteBuffer对象做为这块直接内存的引用进行操做,这样避免了java堆和navie堆中来回复制数据