classLoader 类加载器通过加载class 文件的二进制字码文件在堆中形成java.lang.Class 对象对象
2 虚拟机就会为其分配内存来存放对象自己的实例变量及其从父类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏也会被分配空间)。在为这些实例变量分配内存的同时,这些实例变量也会被赋予默认值(零值)
3 进行类初始化(实例变量初始化、实例代码块初始化 以及 构造函数初始化。)
4 进行类的实例化
Java内存分配机制
这里所说的内存分配,主要指的是在堆上的分配,一般的,对象的内存分配都是在堆上进行,但现代技术也支持将对象拆成标量类型(标量类型即原子类型,表示单个值,可以是基本类型或String等),然后在栈上分配,在栈上分配的很少见,我们这里不考虑。
Java内存分配和回收的机制概括的说,就是:分代分配,分代回收。对象将根据存活的时间被分为:年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)。如下图
年轻代(Young Generation):对象被创建时,内存的分配首先发生在年轻代(大对象可以直接 被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉(IBM的研究表明,98%的对象都是很快消 亡的),这个GC机制被称为Minor GC或叫Young GC。注意,Minor GC并不代表年轻代内存不足,它事实上只表示在Eden区上的GC。
年轻代上的内存分配是这样的,年轻代可以分为3个区域:Eden区(伊甸园,亚当和夏娃偷吃禁果生娃娃的地方,用来表示内存首次分配的区域,再贴切不过)和两个存活区(Survivor 0 、Survivor 1)。内存分配过程为
绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡。Eden区是连续的内存空间,因此在其上分配内存极快;
当Eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区Survivor0(此时,Survivor1是空白的,两个Survivor总有一个是空白的);
此后,每次Eden区满了,就执行一次Minor GC,并将剩余的对象都添加到Survivor0;
当Survivor0也满的时候,将其中仍然活着的对象直接复制到Survivor1,以后Eden区执行Minor GC后,就将剩余的对象添加Survivor1(此时,Survivor0是空白的)。
当两个存活区切换了几次(HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制,大于该值进入老年代)之后,仍然存活的对象(其实只有一小部分,比如,我们自己定义的对象),将被复制到老年代。
从上面的过程可以看出,Eden区是连续的空间,且Survivor总有一个为空。经过一次GC和复制,一个Survivor中保存着当前还活 着的对象,而Eden区和另一个Survivor区的内容都不再需要了,可以直接清空,到下一次GC时,两个Survivor的角色再互换。因此,这种方 式分配内存和清理内存的效率都极高,这种垃圾回收的方式就是著名的“停止-复制(Stop-and-copy)”清理法(将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中),这不代表着停止复制清理法很高效,其实,它也只在这种情况下高效,如果在老年代采用停止复制,则挺悲剧的。
在Eden区,HotSpot虚拟机使用了两种技术来加快内存分配。分别是bump-the-pointer和TLAB(Thread- Local Allocation Buffers),这两种技术的做法分别是:由于Eden区是连续的,因此bump-the-pointer技术的核心就是跟踪最后创建的一个对象,在对 象创建时,只需要检查最后一个对象后面是否有足够的内存即可,从而大大加快内存分配速度;而对于TLAB技术是对于多线程而言的,将Eden区分为若干 段,每个线程使用独立的一段,避免相互影响。TLAB结合bump-the-pointer技术,将保证每个线程都使用Eden区的一段,并快速的分配内 存。
年老代(Old Generation):对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次 Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时, 将执行Major GC,也叫 Full GC。
可以使用-XX:+UseAdaptiveSizePolicy开关来控制是否采用动态控制策略,如果动态控制,则动态调整Java堆中各个区域的大小以及进入老年代的年龄。
如果对象比较大(比如长字符串或大数组),Young空间不足,则大对象会直接分配到老年代上(大对象可能触发提前GC,应少用,更应避免使用短命的大对象)。用-XX:PretenureSizeThreshold来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。
可能存在年老代对象引用新生代对象的情况,如果需要执行Young GC,则可能需要查询整个老年代以确定是否可以清理回收,这显然是低效的。解决的方法是,年老代中维护一个512 byte的块——”card table“,所有老年代对象引用新生代对象的记录都记录在这里。Young GC时,只要查这里即可,不用再去查全部老年代,因此性能大大提高。
内存分配:
初始对象会先在Eden 区上分配,然后如果大对象会直接在老年代上分配
package javatest.cn.itcast_03_jvm.demo;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class JVMTest {
public JVMTest() {
//开辟一段内存空间,由于是局部变量在最后的时候是被回收的
byte [] m = new byte[20*1024*1024];
}
private Object instance;
/**
* -XX:+HeapDumpOnOutOfMemoryError -Xms20m -Xmx20m
* @param args
*/
@Test
public void testOutOfMemoryError() {
List demoList =new ArrayList();
while(true){
demoList.add(new TestMemory());
}
}
/**
* 判断对象是否是垃圾:
* 1 引用计数器
* -verbose:gc 打印GC日志的简要信息
* -verbose:gc -XX:+PrintGCDetails
* -XX:+PrintGCDetails 打印GC日志的详细信息
*/
@Test
public void testJishuqi() {
JVMTest m1 = new JVMTest();
JVMTest m2 = new JVMTest();
//相当于两者相互引用
m2.instance =m1;
m1.instance =m2;
//删除掉两者的地址引用
m1 =null;
m2 =null;
System.gc();//强制进行一次FullGC
}
/**
* PSYoungGen 是使用parallel的标志
* -verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC 使用SeriesGC
*/
@Test
public void testJVMFenpei() {
//新创建的对象是在eden区的,可以看内存看出来
byte [] m = new byte[2*1024*1024];
byte [] m1= new byte[2*1024*1024];
byte [] m2 = new byte[2*1024*1024];
byte [] m3= new byte[4*1024*1024];
// System.gc();
//同样是运行上面的部分主线程和其他线程运行的结果是不一样的
}
}
import org.junit.Test;
public class NeiCunFenPeiTest {
/**
* //同样是运行上面的部分主线程和其他线程运行的结果是不一样的
*/
public static void main(String[] args) {
// byte [] m = new byte[2*1024*1024];
// byte [] m1= new byte[2*1024*1024];
// byte [] m2 = new byte[2*1024*1024];
byte [] m3= new byte[7*1024*1024];
//通常情况下的垃圾回收是minor GC
/*[GC [DefNew: 6981K->484K(9216K), 0.0048746 secs] 6981K->6628K(19456K), 0.0048979 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
def new generation total 9216K, used 5072K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
eden space 8192K, 56% used [0x00000000f9a00000, 0x00000000f9e7af60, 0x00000000fa200000)
from space 1024K, 47% used [0x00000000fa300000, 0x00000000fa3791e0, 0x00000000fa400000)
to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
tenured generation total 10240K, used 6144K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
the space 10240K, 60% used [0x00000000fa400000, 0x00000000faa00030, 0x00000000faa00200, 0x00000000fae00000)
compacting perm gen total 21248K, used 2553K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 12% used [0x00000000fae00000, 0x00000000fb07e6a8, 0x00000000fb07e800, 0x00000000fc2c0000)
No shared spaces configured.
*/
//对于以上日志的解析
}
/**
* PSYoungGen 是使用parallel的标志
* -verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC 使用SeriesGC
*/
@Test
public void testJVMFenpei() {
//新创建的对象是在eden区的,可以看内存看出来
byte [] m = new byte[2*1024*1024];
byte [] m1= new byte[2*1024*1024];
byte [] m2 = new byte[2*1024*1024];
byte [] m3= new byte[4*1024*1024];
// System.gc();
}
}