如第6话所说:第6话.
[GC (Allocation Failure) [PSYoungGen: 2028K->495K(2560K)] 2028K->603K(9728K), 0.0008646 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
详解
[GC (Allocation Failure)
[PSYoungGen: 2028K->495K(2560K)] 2028K->603K(9728K), 0.0008646 secs]
在yong区发生GC,gc回收前新生代内存大小:占用:2028K,回收后:495K,总大小2560K
堆内存,回收前,占用:2028k,回收后603K,总大小9728K;
[Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics)
[PSYoungGen: 368K->0K(1536K)]
新生代
[ParOldGen: 5972K->1414K(7168K)] 6340K->1414K(8704K),
老年代
[Metaspace: 3091K->3091K(1056768K)], 0.0038133 secs]
元空间
[Times: user=0.01 sys=0.00, real=0.01 secs]
full GC同minorGC
JVM GC回收的3个区域:新生代,老年代,元空间
JVM在进行GC时,并非每次都对上述3个区域进行一起回收,大部分的回收都是新生代;
GC如上述分为了2种,一种是轻量级的普通GC(minorGC),一种是全局GC(fullGC);
####minorGC和fullGC的区别:
普通GC:只针对新生区域的GC,发生在新生代的收集器,因为java对象的存活率不高,所以minorGC非常频繁,一般回收速度都快
fullGC(majorGC/fullGC):只发生在老年代的垃圾收集动作,出现了majorGC,经常会伴随至少一次minorGC(但不是绝对),majorGC的速度要比minorGC慢10倍以上;
慢的原因:范围空间更大;
一个对象的引用,每有一处引用,则这个对象即+1;少一个引用则-1;
当该对象的引用数=0时,则清除;
缺点:
public class Test2 {
private Object instance ;
private byte[] bigSize = new byte[2*1024*1024]; //用来占用内存
public static void loopQuote(){
Test2 objectA = new Test2();
Test2 objectB = new Test2();
objectA.instance = objectB;
objectB.instance = objectA;
objectA = null;
objectB = null;
System.gc();//让gc线程执行gc,注:开发环境一般禁用此方法
}
}
故JVM的实现一般不采用此算法
年轻代采用的是minorGC,这种gc一般采用复制算法;
复制算法的基本思想是将内存分为两块,每次只用其中一块,当这一块内存(from)快用完时,就将对象复制到另一块(to区)上面,复制算法不会产生内存碎片;
from区和to区,谁空谁是to区,即minorGC的复制算法的核心;但是实际上,eden区会有部分对象依然会进入from区;
原理:从根集合(GC Root)开始,通过Tracing从From找到存活对象,拷贝到To中;
From和to区交换身份;
采用该算法的原因:eden区的对象存活率不高,一般采用两块10%的内存作为空闲和活动空间,而另外80%的内存,用来个给新建对象分配内存,
优点:不会产生内存碎片;没有标记和清除过程,效率高;
缺点:
因为复制算法的浪费空间的,故而设计的标记清除算法;
用于老年代;老年代特点:对象存活时间长,对象存活率高
原理:算法分为标记和清除两个阶段,先标记要清除的对象,然后统一回收;形如:
优点:
缺点:
当程序运行时,当可以使用的内存被耗尽时,GC线程就会被触发,并将程序暂停,随便把要回收的对象标记一遍,最终统一回收这些对象,完成标记清除工作后,让程序恢复;
全称:标记-清除-压缩算法,即相比标记清除算法,多一步压缩内存空间的一步;
多出来了一步压缩内存空间的一步,但是加大了时间的消耗;
缺点:
故可以合并标记清除和标记压缩;
没有最好的算法,只有合适的算法
内存效率:复制算法>标记清除>标记压缩
内存整齐度:复制>标记清除>标记压缩
内存利用率:标记压缩>标记清除>复制
效率上:复制算法最佳,浪费太多空间;
JMM(Java Memory Model)本事是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范,定义了程序的各个变量(包括实例,静态字段和构成数组对象的元素)的访问方式;
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作空间(栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都在主内存(堆),主内存是共享区域,所有线程都可以访问,但线程堆对变量的操作(读写)必须在工作内存中进行,首先将变量(对象的属性)从主内存中拷贝到线程自己的工作内存中,然后对变量进行操作,操作完成后,再将变量写回主内存中,不同线程无法访问对方的工作内存,所以线程的通信必须借助主内存来完成;如下图所述:
代码验证:
public class Test3 {
public static void main(String[] args) {
Number number = new Number();
new Thread(()->{
try{
System.out.println(Thread.currentThread().getName()+" start");
Thread.sleep(3000);
number.addTo1024();
System.out.println(Thread.currentThread().getName()+" update number, number: "+ number.number);
}catch (Exception e){
e.printStackTrace();
}
},"thread 1").start();
while (number.number==10){
//能否跳出此循环?,
}
System.out.println("mission over ");
}
}
class Number{
int number = 10;
public void addTo1024(){
this.number=1024;
}
}
1.修改了值,但是while循环依然没有退出,原因如上所述;
以上是线程安全性的保证
volatile关键字
volatile是java虚拟机提供的轻量级的同步机制 ;
代码修改
class Number{
volatile int number = 10;
public void addTo1024(){
this.number=1024;
}
}
结果