也能够称为java内存区域,这是一种规范,具体实现和使用哪一种虚拟机有关。运行时数据区和java内存模型不是一回事,不要弄混。html
官方文档地址:https://docs.oracle.com/javase/specs/jvms/se8/html/index.htmljava
线程共享,类装载过程当中产生的java.lang.Class对象保存在方法区,而不是堆,请参考《深刻理解java虚拟机》P215。数组
jdk1.8以前HotSpot经过永久带实现方法区,为了对方法区的GC能够像堆同样管理内存,可以复用代码,其余虚拟机没有永久带的概念,永久代的设计实现方法去并非一个好的选择,由于更容易出现内存溢出。数据结构
方法区主要存放类信息、常量、静态变量、即时编译后的代码等。oracle
垃圾回收主要是针对常量池回收和类型的卸载,这块区域的回收很难,尤为是类型卸载,能够选择不进行垃圾回收,可是回收颇有必要的。jvm
PS:jdk1.8及之后,方法区被移除,经过Metaspace实现,而Metaspace使用的是直接内存,可使用参数:-XX:MetaspaceSize
来指定元工具
数据区的大小。布局
Tomcat中配置打印GC相关信息:能够证实MetaSpace的存在性能
JAVA_OPTS="-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:$CATALINA_HOME/logs/gc.log"
而后直接打开,或者经过GC分析工具打开,就能够发现存在Metaspace内存,Server模式下默认使用Parallel垃圾收集器学习
一、MetaSpace使用的是本地内存,PermGen使用的是jvm内存
二、java.lang.OutOfMemoryError: PermGen Space这个异常不存在了
三、字符串常量池存放在PermGen,容易出现性能问题和内存溢出,因此jdk1.7移动到了堆中
四、类、方法等信息大小比较难肯定,因此很难直接设置PermGen的大小
五、永久代会为 GC 带来没必要要的复杂度,而且回收效率偏低
六、还有一个很重要的点,要合并HotSpot和JRockit的代码,而JRockit没有MetaSpace,事实也证实了经过PermGen实现是一个错误的选择
线程私有,生命周期和线程相同,每执行一个方法都会建立一个栈帧,从执行到结束,对应着栈帧在虚拟机栈的入栈到出栈过程,能够类比数据结构中的栈,java方法两种返回方式:
一、return语句
二、抛出异常
这两种方式都会致使栈帧被弹出。
栈帧:
保存着局部变量表、操做数栈、方法出入口等。
局部变量表:
用来保存方法参数和返回值,也就是基本数据类型、对象的引用、returnAddress类型(指向一个字节码指令的地址)。
double和long占用两个局部变量空间(variable slot),其他占用1个,slot空间大小在编译期间就肯定,方法运行期间没法改变,也就是当程序发生异常,打印的堆栈信息,就是虚拟机栈。
举个栗子:解释局部变量表和操做数栈
public static Integer f1() { int a = 1; int b = 2; return a + b; }
咱们经过javap进行反编译查看字节码知道,1和2这种int类型保存在局部变量表,而a+b的操做是从局部变量表中load数据到操做数栈,而后完成加法的操做。
PS:可能出现Stack OverflowError、OutOfMemoryError错误。
线程私有,和虚拟机栈类似,一个为Java方法服务,一个为了本地方法服务,本地方法栈中方法实现的语言、方式等没有规定,由具体的虚拟机肯定,在HotSpot中只有栈,没有虚拟机栈和本地方法栈的区别,其余的虚拟机如J九、JRocket等可能实现就不一样,咱们默认使用都是HotSpot。
本地方法被执行的时候,在本地方法栈也会建立一个栈帧,用于存放该本地方法的局部变量表、操做数栈、动态连接、出口信息。
PS:可能出现Stack OverflowError、OutOfMemoryError错误
线程共享,这是虚拟机内存最大的一块区域,也是GC的主要区域,几乎全部的对象和数组都保存在这里。
内存回收的角度分为:
新生代:Eden Space、From Survivor、To Survivor
老年代:
一、主要用来保存大对象(能够经过-XX:PretenureSizeThreshold 设置大对象的阀值)。
二、或者重新生代通过15次 minor GC存活下来的对象(-XX:MaxTenuringThreshold)。
三、第二条不是绝对的,VM动态判断,若是Survivor空间中相同年龄全部对象大小的综合大于Survivor空间的一半,年龄大于或等于该年龄的对象就能够直接进入老年代,无须等到MaxTenuringThreshold要求的年龄。
默认Eden Space:From Survivor:To Survivor=8:1:1,能够经过-XX:SurvivorRatio调节,不一样垃圾收集器的调优策略不一样的,因此不要百度到须要调节这个参数,就认为必定有效。
-XX:NewRatio:为Young区和Old区的比例
PS:进一步划分的目的是更好地回收内存,或者更快地分配内存。
线程私有,是一块很小的内存区域,记录着当前虚拟机字节码指令的地址(对于JNI,值为undefined),字节码解释器经过改变计数器的值来选择下一条执行的字节码,分支、循环、跳转、异常处理、线程恢复等功能都要依靠计数器。
线程上下文切换的时候,为了可以恢复到正确的执行位置,须要每一个线程都拥有一个独立的线程计数器,互不影响。
PS:惟一一个没有规定OOM的区域
不属于Java内存区域,有可能出现OOM,jdk1.4出现了NIO,它能够经过Native Libraries分配堆外内存,经过Java堆中的DirectByteBuffer对象做为引用进行操做,在某些场景明显提升性能,觉得避免了Java堆和Native堆来回复制数据。
直接内存的分配不受Java堆大小限制,而是收到本机总内存的限制。
上面说了jvm运行时数据区是一种规范,而对于HotSpot来讲,堆区和非堆区(就是jdk1.8以前的方法区)的内存结构以下:
堆区的结构在上面有介绍过,在jdk1.8中,方法区由Metaspace实现,包含CCS(压缩类空间),只有启用短指针才会存在这部份内存。
Metaspace:
存放的就是方法区的的数据。包含Class、Package、Method、Field、字节码、常量池、符号引用等
CCS:
堆中的对象都有一个指向class对象的指针,64位系统中每一个指针都是64位,为了性能考虑,使用短指针32位的,若是使用短指针就会启用压缩类空间,将这些class对象保存在CCS当中。也就是保存着32位指针的Class。
CodeCache:
JIT即便编译的Native代码、JNI使用的C代码
咱们能够验证一下CCS的启用与关闭:默认开启
经过jps -l查找Java进程,而后经过
[root@iZuf6fkfhthmdm1nwdg5isZ bin]# jstat -gc 23631 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 20480.0 19968.0 0.0 0.0 283136.0 144816.6 52736.0 13931.5 35416.0 34236.6 4480.0 4208.6 10 0.215 2 0.220 0.435
而后在Catalina.sh中JAVA_OPTS加入-XX:-UseCompressedClassPointers进行禁用,而后重启
[root@iZuf6fkfhthmdm1nwdg5isZ bin]# jstat -gc 24008 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 20992.0 20480.0 0.0 0.0 282112.0 160448.0 51712.0 14654.7 35416.0 34133.3 0.0 0.0 10 0.192 2 0.153 0.345
验证CodeCache的存在:
由于CodeCache保存的是即时编译的代码的代码,咱们经过-Xint解释执行的方式启动,固然启动确定会变慢的,由于默认以Mix方式启动而后重启Tomcat
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 19456.0 20480.0 14800.5 0.0 282624.0 260677.0 52224.0 22069.4 32000.0 30489.7 0.0 0.0 10 0.140 1 0.065 0.205
咱们发现MC(Metaspace Capacity)变小了,也证实了CodeCache的存在
由于你们通常都是经过周志明老师的书里面学习jvm的内容,对于常量池的部分的讲解很容易让人搞懵逼,最开始说Class文件中包含常量池,指的是Class常量池,又说运行时常量池能够经过intern()动态添加字符串,看到这里我都蒙了,intern()是操做字符串常量池的,难道字符串常量池是运行时常量池的一部分吗?
而实现方法区的内存溢出是就是经过intern()实现,说的也是运行时常量池致使的内存溢出,因此我只能认为字符串常量池是运行时常量池的一部分,并且jdk1.7以后,运行时常量池和字符串常量池都从方法区移到堆中,因此,我不得不相信
不知道理解的对不对,有不一样意见的能够评论提出来。。。。
在HotSpot中经过StringTable类实现功能,StringTable是一个hash表,默认长度大小1009,被全部类共享。字符串常量由字符组成,保存在StringTable上面。在jdk1.6当中,StringTable的长度是固定1009,若是存放在StringTable中的字符串不少,形成hash冲突的概率很大,链表过长,当经过String.intern()查找String Pool时,性能就会下降。
在jdk1.7当中,StringTable的长度能够经过-XX:StringTableSize设置
存放的内容:
String.intern()主要是为了复用字符串常量,节省内存空间
在jdk1.6及之前的版本,存放的都是字符串常量,使用""声明的字符串都存储在这,例如:String str = "abc";
在jdk1.7以后,String.intern()发生变化,若是字符串常量池不存在这个String对象,若是堆区存在这个对象,直接复制到字符串常量池,不然仍是要建立字符串,而后返回字符串对象的引用。所以除了字符串常量,也能够存放堆中字符串常量的引用
PS:在jdk1.7以后,字符串常量池从方法区转移到堆中
首先java代码经过javac编译成class文件,class文件中保存着类的相关信息(版本,类、字段、方法、接口等信息),除此以外,还有Class常量池,用来存放编译器产生的各类字面量(Literal)和符号引用(Symbolic References),每一个class文件都有一个class常量池。
字面量:1.String 2.基本数据类型 3.声明final的常量
符号引用:1.类和方法的全限定名(相似于com.cfets.**.**这种) 2.字段的名称和描述符 3.方法的名称和描述符
就是class常量池被加载到方法区以后的版本,区别就是:字面量能够经过String.intern()动态添加,符号引用解析为直接引用(类加载的解析阶段)
当类加载到内存之中,jvm会把class常量池的内存存放到运行时常量池,因此,运行时常量池也是每一个class都有的。
符号引用:上述有说明。以一组符号来描述所引用的目标,只要能定位到目标,不管是任何形式的字面量,和jvm实现的内存布局无关。
直接引用:直接指向目标的指针、相对偏移量或者间接定位到目标的句柄,句柄和指针对应对象的访问定位方式,和jvm实现的内存布局有关。
PS:JDK1.7及以后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池.因此jdk1.7以后,运行时常量池和字符串常量池都从方法区移到堆中
解释执行:
逐条翻译字节码为可运行的机器码,优点在于不用等待。
即时编译:
以方法为单位将字节码翻译成机器码,实际运行当中效率更高。
在HotSpot中默认采用混合模式,其先解释执行字节码,而后将其中的热点代码(屡次执行,循环等)直接编译成机器码,下次就不用再编译了,让其更快速地运行。使用混合模式的缘由有二八定律和jvm优化的考虑在里面
经过java -version命令能够查看:
# java -version java version "1.8.0_102" Java(TM) SE Runtime Environment (build 1.8.0_102-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)
经过-Xint(解释执行), -Xcomp(即时编译), 和-Xmixed设置编译方式,不过通常状况下不须要修改。
原文出处:https://www.cnblogs.com/huigelaile/p/diamondshine.html