JVM的组成:
堆,方法区;(共享)
本地方法栈,虚拟机栈,程序计数器;(线程私有)
堆:存储数组,new出来的对象;(是存储数据的地方)
堆分为新生代和老年代。持久代在物理层面是属于堆的,但是在逻辑
层面是属于方法区的,其实方法区在物理上也是堆中的,但是由于功
能和作用的区别,逻辑上他又独立于堆。
堆分为新生代和老年代,默认分配比例时1:2。新生代又分为Eden区和survivor区,
survivor又分为survivor1和survior2区,也就是from和to两个区,内存分配时8:1:1.
一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我
们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor
区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时
候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯
闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认
识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。
这也侧面说明了jvm在这三个区域的垃圾回收的机制,就是当Eden区域满了之后会进行
一次minor GC,存活下来的就会放到from区域,当from区域满了之后进行major GC,
就会将还存活的对象放到to区域,清除完之后,from和to区域的指针会进行调换,保
证to区域始终是空的,from区域始终是有数据的。
方法区:此处存储的是一些唯一性的东西,像类的class对象;存储被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
方法区中还有常量池,常量池中存储的有两种信息,一个就是类的信息,一个就是常量,像被final修饰的值,String a = “asd”;这里的asd也会存储到常量池中。
接着就到了常量池表cp_info。此处常量池表就是之前文章Java虚拟机—堆、栈、运行时数据区中提到的,方法区中的运行时常量池。
运行时常量池
运行时常量池(Runtime Constant Pool)是.class文件中每一个类或接口的常量池表(constant pool table)的运行时表示形式,属于方法区的一部分。每一个运行时常量池都在Java虚拟机的方法区中分配,在加载类和接口道虚拟机后,就创建对应的运行时常量池。常量池的作用是:
存放编译器生成的各种字面量和符号引用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建或运行时解析、翻译到具体的内存地址之中。
字面量(Literal),通俗理解就是Java中的常量,如文本字符串、声明为final的常量值等。
符号引用(Symbolic References)则是属于编译原理中的概念,包括了下面三类常量:
1.类和接口的全限定名
2.字段的名称和描述符
3.方法的名称和描述符
本地方法栈:当我们调用本地方法的时候就是在本地方法栈中运行。
虚拟机栈:当我们运行到一个方法的时候我们就会在虚拟机栈中形成一个栈帧。
栈帧中有
局部变量表(用于存放方法参数和方法内定义的局部变量);
操作数栈(方法中的运算在此处位置);
动态链接(首先运行时常量区中的符号引用在加载或者第一次使用的时候一部分符号引用会转化为直接引用,这种叫静态解析;另一部分就是在每次运行方法的时候,这种叫动态链接,所以每一个栈帧都会保存一个指向运行时常量区栈中当前方法的一个符号引用,目的就是为了支持方法调用过程中的动态链接。);
方法的返回(当方法执行完成之后,肯定会返回方法调用处,这时候就执行此处保存的字节码就可以。)
基本数据类型也会存储在虚拟机栈中;
程序计数器:这里面保存的就是下一条字节码执行的位置
如何减少GC的次数(GC优化)
1.对象不用时最好显式置为 Null
一般而言,为 Null 的对象都会被作为垃圾处理,所以将不用的对象显式地设为 Null,有利于 GC 收集器判定垃圾,从而提高了 GC 的效率。
2.尽量少用 System.gc()
此函数建议JVM 进行主GC,虽然只是建议而非一定,但很多情况下它会触发主 GC,从而增加主 GC 的频率,也即增加了间歇性停顿的次数。
3.尽量少用静态变量
静态变量属于全局变量,不会被 GC 回收,它们会一直占用内存。
4.尽量使用 StringBuffer, 而不用String 来累加字符串。
由于 String 是固定长的字符串对象,累加 String 对象时,并非在一个 String对象中扩增,而是重新创建新的 String 对象,如 Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的 String 对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用 StringBuffer 来累加字符串,因 StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
5.分散对象创建或删除的时间
集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM 在面临这种情况时,只能进行主 GC,以回收内存或整合内存碎片,从而增加主 GC 的频率。
集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主 GC 的机会。
6.尽量少用 finalize 函数。
因为它会加大 GC 的工作量,因此尽量少用finalize 方式回收资源。
7.如果需要使用经常用到的图片,可以使用软引用类型,它可以尽可能将图片保存在内存中,供程序调用,而不引起 OutOfMemory。
8.能用基本类型如 int,long,就不用 Integer,Long 对象
基本类型变量占用的内存资源比相应包装类对象占用的少得多,如果没有必要,最好使用基本变量。
9.增大-Xmx
Full GC
是清理整个堆空间—包括年轻代和老年代。
什么时候触发:
-
调用System.gc
-
方法区空间不足
2.老年代空间不足,包括:
新创建的对象都会被分配到Eden区,如果该对象占用内存非常大,则直接分配到老年代区,此时老年代空间不足
做minor gc操作前,发现要移动的空间(Eden区、From区向To区复制时,To区的内存空间不足)比老年代剩余空间要大,则触发full gc,而不是minor gc