⽅法区之1:⽅法区介绍
⼀、简介
⽅法区在JVM中也是⼀个⾮常重要的区域,它与堆⼀样,是被线程共享的区域。在⽅法区中,存储了每个类的信息(包括类的名称、⽅法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。
⽅法区(method area)只是JVM规范中定义的⼀个概念,⽤于存储类信息、常量池、静态变量、JIT编译后的代码等数据,具体放在哪⾥,不同的实现可以放在不同的地⽅。⽽永久代是Hotspot虚拟机特有的概念,是⽅法区的⼀种实现,别的JVM都没有这个东西。
⼆、⽅法区结构
2.1、先看classLoader是如何加载class⽂件和存储⽂件信息的
当⼀个classLoder启动的时候,classLoader的⽣存地点在jvm中的堆,然后它会去主机硬盘上将A.class装载到jvm的⽅法区,⽅法区中的这个字节⽂件会被虚拟机拿来new A字节码(),然后在堆内存⽣成了⼀个A字节码的对象,然后A字节码这个内存⽂件有两个引⽤⼀个指向A的class对象,⼀个指向加载⾃⼰的classLoader。那么⽅法区中的字节码内存块,除了记录⼀个class⾃⼰的class对象引⽤和⼀个加载⾃⼰的ClassLoader引⽤之外,还记录了什么信息呢??见下图:
2.2、⽅法区关键信息介绍
1.类信息:修饰符(public final)
是类还是接⼝(class,interface)
类的全限定名(Test/ClassStruct.class)
直接⽗类的全限定名(java/lang/Object.class)
直接⽗接⼝的权限定名数组(java/io/Serializable)
  也就是 public final class ClassStruct extends Object implements Serializable这段描述的信息提取
2.字段信息:修饰符(pirvate)
字段类型(java/lang/String.class)
字段名(name)
  也就是类似private String name;这段描述信息的提取
3.⽅法信息:修饰符(public static final)
⽅法返回值(java/lang/String.class)
⽅法名(getStatic_str)
参数需要⽤到的局部变量的⼤⼩还有操作数栈⼤⼩(操作数栈我们后⾯会讲)
⽅法体的字节码(就是花括号⾥的内容)
异常表(throws Exception)
  也就是对⽅法public static final String getStatic_str ()throws Exception的字节码的提取
4.常量池:
4.1.直接常量:
1.1CONSTANT_INGETER_INFO整型直接常量池public final int CONST_INT=0;
1.2CONSTANT_String_info字符串直接常量池  public final String CONST_STR="CONST_STR";
1.3CONSTANT_DOUBLE_INFO浮点型直接常量池
等等各种基本数据类型基础常量池(待会我们会反编译⼀个类,来查看它的常量池等。)
4.2.⽅法名、⽅法描述符、类名、字段名,字段描述符的符号引⽤
字符常量池是什么意思
也就是所有编译器能够被确定,能够被快速查的内容都存放在这⾥,它像数组⼀样通过索引访问,就是专门⽤来做查的。
编译时就能确定数值的常量类型都会复制它的所有常量到⾃⼰的常量池中,或者嵌⼊到它的字节码流中。作为常量池或者字节码流的
⼀部分,编译时常量保存在⽅法区中,就和⼀般的类变量⼀样。但是当⼀般的类变量作为他们的类型的⼀部分数据⽽保存的时候,编译时常量作为使⽤它们的类型的⼀部分⽽保存
5.类变量(静态变量):
就是静态字段( public static String static_str="static_str";)
虚拟机在使⽤某个类之前,必须在⽅法区为这些类变量分配空间。
6.⼀个到classLoader的引⽤,通过Class().getClassLoader()来取得为什么要先经过class呢?思考⼀下,看⼀下上⾯的图,再回来思考。(class A 对象拥有A字节码和加载它的加载器地址引⽤)
7.⼀个到class对象的引⽤,这个对象存储了所有这个字节码内存块的相关信息。所有你能够看到的区域,⽐如:类信息,你可以通过Class().getName()取得
所有的⽅法信息,可以通过Class().getDeclaredMethods(),字段信息可以通过Class().getDeclaredFields(),等等,所有在字节码中你想得到的,调⽤的,通过class这个引⽤基本都能够帮你完成。因为他就是字节码在内存块在堆中的⼀个对象
8.⽅法表,如果学习c++的⼈应该都知道c++的对象内存模型有⼀个叫虚表的东西,java本来的名字就叫c++- -,它的⽅法表其实说⽩了就是c++的虚表,它的内容就是这个类的所有实例可能被调⽤的所有实例⽅法的直接引⽤。也是为了动态绑定的快速定位⽽做的⼀个类似缓存的查表,它以数组的形式存在于内存中。不过这个表不是必须存在的,取决于虚拟机的设计者,以及运⾏虚拟机的机器是否有⾜够的内存。
三、⽅法区与永久代的关系
很多⽂章⾥喜欢把⽅法区等同与永久代,永久代既然没了,⽅法区也就没了。但我认为⽅法区只是⼀种
逻辑上的概念,永久代指物理上的堆内存的⼀块空间,这块实际的空间完成了⽅法区存储字节码、静态变量、常量的功能等等。既然如此,现在元空间也可以认为是新的⽅法区的实现了。
⽅法区也叫永久代。在过去(⾃定义类加载器还不是很常见的时候),类⼤多是”static”的,很少被卸载或收集,因此被称为“永久的(Permanent)”。虽然Java 虚拟机规范把⽅法区描述为堆的⼀个逻辑部分,但是它却有⼀个别名叫做Non-Heap(⾮堆),⽬的应该是与Java 堆区分开来。同时,由于类class是JVM实现的⼀部分,并不是由应⽤创建的,所以⼜被认为是“⾮堆(non-heap)”内存。HotSpot 虚拟机的设计团队选择把GC 分代收集扩展⾄⽅法区,或者说使⽤永久代来实现⽅法区⽽已。对于其他虚拟机(如BEA JRockit、IBM J9 等)来说是不存在永久代的概念的。
  永久代也是各个线程共享的区域,它⽤于存储已经被虚拟机加载过的类信息,常量,静态变量(JDK7中被移到Java堆),即时编译期编译后的代码(类⽅法)等数据。这⾥要讲⼀下运⾏时常量池,它是⽅法区的⼀部分,⽤于存放编译期⽣成的各种字⾯量和符号引⽤(其实就是⼋⼤基本类型的包装类型和String类型数据(JDK7中被移到Java堆))(官⽅⽂档说明: 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)。
  在JDK1.7中的HotASpot中,已经把原本放在⽅法区的字符串常量池移到java heap中。
将interned String移到Java堆中(⽰例见《》中的⽰例)
将符号Symbols移到native memory(不受GC管理的内存)
  从JDK7开始永久代的移除⼯作,贮存在永久代的⼀部分数据已经转移到了Java Heap或者是Native Heap。但永久代仍然存在于
JDK7,并没有完全的移除:符号引⽤(Symbols)转移到了native heap;字⾯量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。随着JDK8的到来,JVM不再有PermGen。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,⽽是移动到叫做“Metaspace”的本地内存(Native memory)中。
四、jdk1.6,jdk1.7,jdk1.8变化
常量池主要可以分为以下⼏种:
(1)静态常量池:即*.class⽂件中的常量池,class⽂件中的常量池不仅仅包含字符串/数字这些字⾯量,还包含类、⽅法的信息,占⽤class⽂件绝⼤部分空间。这种常量池主要⽤于存放两⼤类常量:字⾯量和符号引⽤量,字⾯量相当于Java语⾔层⾯常量的概念,如⽂本字符串,声明为final的常量值等;符号引⽤则属于编译原理⽅⾯的概念,包括了如下三种类型的常量:类和接⼝的全限定名、字段名称描述
符、⽅法名称描述符。
类的加载过程中的链接部分的解析步骤就是把符号引⽤替换为直接引⽤,即把那些描述符(名字)替换为能直接定位到字段、⽅法的引⽤或句柄(地址)。
(2)运⾏时常量池:虚拟机会将各个class⽂件中的常量池载⼊到运⾏时常量池中,即编译期间⽣成的字⾯量、符号引⽤,总之就是装载class⽂件。
(3)字符串常量池:字符串常量池可以理解为运⾏时常量池分出来的部分。加载时,对于class的静态常量池,如果字符串会被装到字符串常量池中。
(4)整型常量池:Integer,类似字符串常量池,可以理解为运⾏时常量池分出来的。加载时,对于class的静态常量池装的东西,如果是整型会被装到整型常量池中。
类似的还有Character、Long等等常量池(基本数据类型没有哦)。。。
在永久代移除后,字符串常量池也不再放在永久代了,但是也没有放到新的⽅法区---元空间⾥,⽽是留在了堆⾥(为了⽅便回收?)。运⾏时常量池当然是随着搬家到了元空间⾥,毕竟它是装静态变量、字节码等信息的,有它的地⽅才称得上⽅法区。
,在jdk1.7,jdk1.8迁移从永久代迁移到heap堆中。见《》
五、垃圾回收
jdk1.6
常量池如何触发的垃圾回收?
Class⽂件中除了有类的版本、字段、⽅法、接⼝等描述等信息外,还有⼀项信息是常量表(constant_pool table),⽤于存放编译期已可知的常量,这部分内容将在类加载后进⼊⽅法区(永久代)存放。但是Java语⾔并不要求常量⼀定只有编译期预置⼊Class的常量表的内容才能进⼊⽅法区常量池,运⾏期间也可将新内容放⼊常量池(最典型的String.intern()⽅法)。
GC的作⽤主要是⽤来卸载类和回收常量池,当然有部分⽅法区,即使永久代(Perm Space)也会⼀定的回收
jdk1.7,1.8
Metaspace 垃圾回收:
对于僵死的类及类加载器的垃圾回收将在元数据使⽤到“MaxMetaspaceSize”参数的设定值时进⾏。
适时地监控和调整元空间对于减⼩垃圾回收频率和减少延时是很有必要的。持续的元空间垃圾回收说明,可能存在类、类加载器导致的内存泄漏或是⼤⼩设置不合适。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。