最近看了一下JVM的内存分配,还是比较复杂的。这里做个总结,首先一个common sense就是操作系统会为每个java进程实例化一个jvm实例。jvm然后再来运行java程序,具体的过程就不多说了,简单来说就是核心classloader如bootstrap, extention, System对类的加载(一定是此顺序,jvm对类的加载采取的是代理委托方式,防止核心类被hack),找到对应的main入口来运行。
这里主要是想总结一下,每个java进程对应的jvm对内存的分配,运行时是什么样的。我们都知道jvm内存分为
1. PC计数器
2. 堆
3. 栈(jvm栈,本地方法栈)
4. 方法区(包括class信息,静态变量,class文件常量池,运行时常量池,JIT缓存代码)。
其中,PC计数器,栈是线程私有的,而堆和方法区是线程共享的。操作系统分配给jvm的内存的总大小是有限的,这和操作系统以及是32bit还是64bit,具体的jvm实现都有关系。另外,堆和方法区内存的大小是可以在jvm启动参数中配置的。我们程序员平时经常说的内存操作,OOM都是指的堆内存。
这里顺便说一下JVM的GC机制,JVM的GC不是采用的引用计数算法,而是可达性分析算法。堆根据GC回收的分代算法,又可以分为新生代Eden+2*Survivor, 和老年代。对新生代进行标记-复制算法,进行一次Minor GC,而对老年代进行标记-整理算法,进行一次Major/Full GC,当然肯定有一次Minor GC。程序中绝大部分new的对象放置在Eden区,少数比如大型对象,数组,和长期存活对象直接进入老年代。进入新生代的根据对象年龄计数器可以逐步晋升至老年代中。
那么对于不同的区域,什么时候存放什么东西都是有规范的。
总结如下:
1. 堆: 1) 类的成员变量引用,这条实际上是对2)的说明。 2)new且只有new出来的对象放在堆里。
2. 栈:运行时,成员函数的局部变量引用及其字面量。
3. 方法区:1) class 文件常量池,存放成员变量里的字面量,字符串常量。 2) 静态成员变量。 3)运行时常量池,存放成员函数运行时的常量。
现在举例说明:
class A{
int i1 = 1; //i1在堆中A对象里,1在方法区的class文件常量池中
String s1 = "abc"; //s1在堆中A对象里,"abc"在方法区的class文件常量池中
String s2 = new String("abc"); //s2在堆中A对象里,"abc"在方法区的class文件常量池中,只有一份,new出来的String对象在堆里
static int i2 = 2; //i2在方法区,2在方法区的class文件常量池中
//s3在方法区,"xyz"在方法区的class文件常量池中
static String s3 = "xyz";
//s4在方法区,"xyz"在方法区的class文件常量池中,只有一份,new出来的String对象在堆里
static String s4 = new String("xyz");
public void func(){
int i3 = 3; //i3在栈中,字面量3也在栈中
int i4 = 3; //i4在栈中,字面量3已经存在,此时只有一份
String s5 = "china"; //s5在栈中,"china"在方法区的运行时常量池中
String s6 = new String("china"); //s6在栈中,"china"在方法区的运行时常量池中已经有一份相同拷贝,不再存, new出来的对象在堆中
}
}
那么,当A被classloader装载并且调用func函数的时候,其所在的jvm内存中不同的地方都有哪些关于A的信息呢?分析如下:
因此,对于equals相等的字符串,在常量池(class文件常量池或者运行时常量池)中永远只有一份,在堆中有多份。因为String类重写/覆盖了Object类的equals方法,只要字符串内容相等即为true,而Object是必须同一个对象的引用地址才为true。但是String并没有重写/覆盖==操作符,所以String对象的==还是只有同一个对象的地址引用才为true。
并且,延伸出来很多面试题的答案,比如:
1) String s = new String(“xyz”); 产生几个对象?
一个或两个。如果常量池中原来没有 ”xyz”, 就是两个。如果原来的常量池中存在“xyz”时,就是一个。
2) String作为一个对象来使用
例子一:对象不同,内容相同,”==”返回false,equals返回true
String s1 = new String(“java”);
String s2 = new String(“java”);
System.out.println(s1==s2); //false
System.out.println(s1.equals(s2)); //true
例子二:同一对象,”==”和equals结果相同
String s1 = new String(“java”);
String s2 = s1;
System.out.println(s1==s2); //true
System.out.println(s1.equals(s2)); //true
String作为一个基本类型来使用
如果值不相同,对象就不相同,所以”==” 和equals结果一样
String s1 = “java”;
String s2 = “java”;
System.out.println(s1==s2); //true
System.out.println(s1.equals(s2)); //true
如果String缓冲池内不存在与其指定值相同的String对象,那么此时虚拟机将为此创建新的String对象,并存放在String缓冲池内。
如果String缓冲池内存在与其指定值相同的String对象,那么此时虚拟机将不为此创建新的String对象,而直接返回已存在的String对象的引用。