作者: 百事可乐丶 | 来源:发表于2020-05-20 11:26 被阅读0次

定义

逻辑上分为包括年轻代,老年代和永久代(方法区, java8后为远空间),标准的结构只包含年轻代和老年代
这样划分是为了根据jvm里面对象的特点进行分代管理


WechatIMG4.jpeg

常用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个对象.没问题


WechatIMG5.png

去除-XX:-EliminateAllocations 发现在没发生垃圾回收的情况下,发生了标量替换,有4w多的Message
对象没有创建,而是转化成了标多个标量,其中StringBuffer个数还是10w+,关闭逃逸分析,标量替换也会失效.


WechatIMG6.png

总结:其实hotspot虚拟机还没有支持逃逸分析,然后在栈内创建对象,在没有发生GC的情况下造成堆中对象个数和代码不一致的情况是标量替换造成的,标量替换是基于逃逸分析,JDK1.7之后都是默认开启的

相关文章

  • 堆 - 堆的应用

    堆有三个典型的应用场景:实现优先队列、求 Top K 、求中位数 实现优先队列 优先队列:队列的性质是先进先出,但...

  • 二叉堆是一棵满二叉树,父节点的值大于子节点。 用数组存储二叉堆,假如一个节点的下标为k,那么这个节点的左孩子就是2...

  • 应用: 排序,从小到大用最大堆,从大到小用最小堆 选出元素中的 top k 个top k 个最小数:数组前k个元素...

  • 完全二叉树 二叉堆 二叉堆有最大堆和最小堆的区别,最大堆只保证每个节点的父节点大于当前节点,但不保证上一层节点的值...

  • 堆的定义: n个元素序列{k1,k2,...,ki,...,kn},当且仅当满足下列关系时称之为堆: (ki...

  • http://bubkoo.com/2014/01/14/sort-algorithm/heap-sort/ 1 ...

  • 堆 …

    南山南,北山北,南山有谷堆,北山有花蕾,山坡下,大道中,野树停在石堆,秋风送,冷雪飘,旅途空旷叶儿飞,时间漫,皱纹...

  • 题目:100w个数中找出最大的100个。 维护一个100个元素的小根堆即可。 或者直接维护一个用来存储当前最大的1...

网友评论

      本文标题:

      本文链接:https://www.haomeiwen.com/subject/usbqnhtx.html