定义
逻辑上分为包括年轻代,老年代和永久代(方法区, java8后为远空间),标准的结构只包含年轻代和老年代
这样划分是为了根据jvm里面对象的特点进行分代管理
常用JVM命令
-Xms200m 设置堆的初始化内存
-Xmx200m 设置堆的最大内存
年轻代
存放一般的新生对象,是垃圾回收的主要区域,发生垃圾回收的频率较高,因为大部分对象都是朝生夕死的.默认占堆总空间的1/3.
eden
新生对象的存放区域,当新生对象的空间大于eden区剩余的空间的时候触发MinorGC(YoungGC)
TLAB
1.TLAB的全称是Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线程专用的内存分配区域。是eden区的一部分,TLAB空间的内存非常小,缺省情况下仅占有整个Eden空间的1%,也可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。
2.TLAB的本质其实是三个指针管理的区域:start,top 和 end,每个线程都会从Eden分配一块空间,例如说100KB,作为自己的TLAB,其中 start 和 end 是占位用的,标识出 eden 里被这个 TLAB 所管理的区域,卡住eden里的一块空间不让其它线程来这里分配。
3.TLAB只是让每个线程有私有的分配指针,但底下存对象的内存空间还是给所有线程访问的,只是其它线程无法在这个区域分配而已。从这一点看,它被翻译为 线程私有分配区 更为合理一点当一个TLAB用满(分配指针top撞上分配极限end了),就新申请一个TLAB,而在老TLAB里的对象还留在原地什么都不用管——它们无法感知自己是否是曾经从TLAB分配出来的,而只关心自己是在eden里分配的。
4.事务总不是完美的,TLAB也又自己的缺点。因为TLAB通常很小,所以放不下大对象。
4.1,TLAB空间大小是固定的,但是这时候一个大对象,我TLAB剩余的空间已经容不下它了。(比如100kb的TLAB,来了个110KB的对象)
4.2,TLAB空间还剩一点点没有用到,有点舍不得。(比如100kb的TLAB,装了80KB,又来了个30KB的对象)所以JVM开发人员做了以下处理,设置了最大浪费空间.当剩余的空间小于最大浪费空间,那该TLAB属于的线程在重新向Eden区申请一个TLAB空间。进行对象创建,还是空间不够,那你这个对象太大了,去Eden区直接创建吧!当剩余的空间大于最大浪费空间,那这个大对象请你直接去Eden区创建,我TLAB放不下没有使用完的空间。当然,又回造成新的病垢。
4.3,Eden空间够的时候,你再次申请TLAB没问题,我不够了,Heap的Eden区要开始GC,
4.4,TLAB允许浪费空间,导致Eden区空间不连续,积少成多。以后还要人帮忙打理。
链接:https://www.jianshu.com/p/8be816cbb5ed
from区和to区(S0区和S1区)
每经过一次GC现在的from区和to区会交换一下,即现在的from区是下次的to区
设计的原因
因为年轻代的对象大多是回收的,只有少数能活下来,该结构是为了更好的实现复制算法,即每次GC将eden区和from区中活着对象copy到to区中,对应的对象年纪+1,然后清空eden和from区,现在的to区变成from区,被清空的from区变成to区(谁是空的谁是to区)
老年代
默认当年纪达到15岁的时候进入老年代,或者大对象(eden 区发生垃圾回收后空间还放不下的对象)老年代的对象基本上都是常用的对象,这一片区域很少发生GC,老年的GC 叫MajorGC,老年代默认大小是堆大小的2/3.
问:所有创建的对象都是存放在堆中嘛
目前来说,就hotstop虚拟机来说是的.
1.逃逸分析:
-XX:+DoEscapeAnalysis
即时编译器会判断一个对象是不是没有发生逃逸(即该对象在该方法中创建,在方法中销毁,在方法中没开启其他线程引用该对象),在没有发生逃逸的对象,不会在堆中创建对象,直接在栈中创建对象,随着栈桢的消亡而消亡,达到系统优化,临时对象即时生即时死的作用.
2.同步省略:
public void Test(){
User user = new User();
synchronized (user){
System.out.println("xx");
}
}
基于逃逸分析,判断该对象是否没发生逃逸,上述代码user没发生逃逸,synchronized(user)毫无意义,即时编译器会直接省略,jdk7之后默认开启同步省略
3.标量替换:
-XX:+EliminateAllocations
同样是基于逃逸分析,jdk7之后默认开启,会将没有发生逃逸的对象,从一个大的聚合对象,转成常量和小的聚合对象
public class User{
public static void main(String[] args)
{
for (int i = 0; i < 100000; i++)
{
getUserMsg();
}
}
private static void getUserMsg()
{
Message message = new Message();
message.setAge("16");
message.setStringBuffer(new StringBuffer());
// 上述代码标量替换成下述代码,并没有在堆中创建了Message对象
/**String name = null;
String age = "16";
StringBuffer stringBuffer = new StringBuffer();**/
}
}
@Data
class Message
{
private String name;
private String age;
private StringBuffer stringBuffer;
}
验证: JVM里面添加-XX:+PrintGCDetails 看有没有发生GC ,确保没发生GC,查看堆中对象个数
先添加 -XX:-EliminateAllocations 关闭标量替换,用jdk自带的分析工具VisualVm 查看堆中对象个数可知,的确创建了10w个对象.没问题
去除-XX:-EliminateAllocations 发现在没发生垃圾回收的情况下,发生了标量替换,有4w多的Message
对象没有创建,而是转化成了标多个标量,其中StringBuffer个数还是10w+,关闭逃逸分析,标量替换也会失效.
总结:其实hotspot虚拟机还没有支持逃逸分析,然后在栈内创建对象,在没有发生GC的情况下造成堆中对象个数和代码不一致的情况是标量替换造成的,标量替换是基于逃逸分析,JDK1.7之后都是默认开启的