目录
死磕JVM(一)内存区域 https://blog.youkuaiyun.com/u012133048/article/details/85344025
死磕JVM(二)内存模型 https://blog.youkuaiyun.com/u012133048/article/details/87886352
死磕JVM(三)内存溢出 https://blog.youkuaiyun.com/u012133048/article/details/87891398
死磕JVM(四) 垃圾回收机制 https://blog.youkuaiyun.com/u012133048/article/details/85413539
死磕JVM(五)对象的创建 https://blog.youkuaiyun.com/u012133048/article/details/87938452
死磕JVM(六) 类加载机制 https://blog.youkuaiyun.com/u012133048/article/details/85378148
死磕JVM (七) 锁优化 https://blog.youkuaiyun.com/u012133048/article/details/85490843
死磕JVM (八) 总结 https://blog.youkuaiyun.com/u012133048/article/details/88069289
1 内存区域
1.1 程序计数器
1.1.1 内容
程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
1.2 java虚拟机栈
1.2.1 存储内容
与程序计数器一样,Java 虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。每个栈帧有自己对应的局部变量表和操作数栈
1.2.2 局部变量表部分
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
1.2.3 异常
重点来了!!!虚拟机栈规定了两种异常状况:
1、如果线程请求的栈深度大于虚拟机栈锁允许的深度,将会抛出StackOverflowError异常。
2、如果虚拟机栈可以动态扩展(大部分虚拟机都可扩展,也可配置为固定值),如果扩展时无法申请到足够的内存,就会抛出OOM。
1.3 本地方法栈
与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。(栈的空间大小远远小于堆),异常情况和虚拟机栈类似。
1.4 Java 堆
1.4.1 存储内容
Java堆可以细分为新生代和老年代;再细致一些新生代可以分为:Eden空间、From Survivor空间,To Survivor空间。
堆内存是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,该内存区域存放了对象实例及数组(但不是所有的对象实例都在堆中)。其大小通过-Xms(最小值)和-Xmx(最大值)参数设置(最大最小值都要小于1G),前者为启动时申请的最小内存,默认为操作系统物理内存的1/64,后者为JVM可申请的最大内存,默认为物理内存的1/4,默认当空余堆内存小于40%时,JVM会增大堆内存到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小堆内存的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,当然为了避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。堆内存 = 新生代+老生代+持久代。在我们垃圾回收的时候,我们往往将堆内存分成新生代和老生代(大小比例1:2),新生代中由Eden和Survivor0,Survivor1组成,三者的比例是8:1:1,新生代的回收机制采用复制算法,在Minor GC的时候,我们都留一个存活区用来存放存活的对象,真正进行的区域是Eden+其中一个存活区,当我们的对象时长超过一定年龄时(默认15,可以通过参数设置),将会把对象放入老生代,当然大的对象会直接进入老生代。老生代采用的回收算法是标记整理算法。
1.4.2 异常
java堆也可以配置成可扩展和固定大小两种,如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OOM。
1.5 方法区
方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java 堆区分开来。方法区的垃圾收集是和老年代(old generation)捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。
1.5.1 存储内容
笼统的说方法区存储类的信息,常量和静态变量,即类被编译后的数据。这样描述过于简略,详细一点:
方法区里存放着:
1、类信息(包括静态变量和静态代码段,因为静态变量和类是同步的);
- 常量:Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(类文件常量池或者是静态常量池),用于存放编译器生成的各种符号引用和字面量,这部分内容将在类加载(解析阶段)后放到方法区的运行时常量池中。
静态常量池:字面变量和编译器生成的符号引用
字面量表示等号右边的值,如
int
a;//a变量
const
int
b=10;//b为常量,10为字面量
string str="hello world"
;//str为变量,hello world为也字面量
符号引用
符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,只要使用时能无歧义地定位到目标 即可,符号引用和虚拟机的布局无关。符号引用的作用是存储字符串在常量池里的索引,在类加载解析时,替换为直接引用。
- 静态变量:又称为类变量,类中被static修饰的成员变量都是静态变量(类变量)静态变量之所以又称为类变量,是因为静态变量和类关联在一起,随着类的加载而存在于方法区(而不是堆中)。对于引用类型的静态变量如果用new关键字为引用类型的静态变量分配对象(如:static Person person = new Person()),那么对象的引用person 会存储在方法区中,并且该对象在堆中的地址也会存储在方法区中(注意此时静态变量只存储了对象的堆地址,而对象本身仍在堆内存中)。
- 方法:这个过程中静态变量(类变量)和静态方法及普通方法对应的字节码加载到方法区。
2、运行时常量池(动态常量池)。
class文件被加载完成后,java虚拟机会将静态常量池里的内容转移到动态常量池里,在静态常量池的符号引用有一部分是会被转变为直接引用的,比如说类的静态方法或私有方法,实例构造方法,父类方法,这是因为这些方法不能被重写其他版本,所以能在加载的时候就可以将符号引用转变为直接引用,而其他的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。也就是说,是在运行时常量池里,来做引用变量到直接变量的替换。
盗图:
注意:方法区中没有实例变量,这是因为,类加载先于对应类对象的产生,而实例变量是和对象关联在一起的,没有对象就不存在实例变量,类加载时没有对象,所以方法区中没有实例变量。
这里不得不提一下字符串常量池(关于字符串常量池会另写一篇)
在JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代
在JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代
在JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
1.5.2 异常
1、一个明显的问题是,当JVM加载的类信息容量超过了参数-XX:MaxPermSize设定的值时,应用将会报OOM的错误。参数是通过-XX:PermSize和-XX:MaxPermSize来设定的。
1.6 直接内存
直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小.
链接:死磕JVM(二)内存模型https://blog.youkuaiyun.com/u012133048/article/details/87886352