【JVM深入理解系列4】精通String

遗留问题

String s2 = “dd” + new String(“test”);
四个 string
3个char

数组

UseCompressedoops
压缩的是对象指针的长度

UseCompressedClassPointers
压缩的是klass对象指针的长度

如果不压缩,则在arrayOopDesc中声明的非静态字段之后分配,如果压缩,它将占用oopDesc中_klass字段的后半部分。

  • 数组长度怎么算

    数组
    对应的klass是:TypeArrayKlass实例
    对应的oop:TypeArrayOop

    对象的内存布局

      对象头
          mark word
          klass pointer
          
      实例数据
      填充
    

    不开启解压的情况:
    Mark word 8B
    klass point
    metadata._compressed_klass
    数组长度4B

      这种情况下,klass + 数组长度用了多少字节
          12 = 8 + 4
      
      开启压缩的情况下:
      _metadata._compressed_klass 数组长度4B
      klass + 数组长度= 4 + 4 = 8B
    

=====

垃圾回收算法

可重入锁很像 lock + 1 unlock -1 计数==0
释放

对象存活依据

  • 引用计数

    例外,无法处理循环依赖。

    初始化死锁

  • 可达性分析

    GC Roots
    oop
    局部变量表
    字符串常量池

JVM的GC

给存活的对象打标机,回收没有标记的对象。

内存池

  • 内存分配算法
    指针碰撞(openJdk, 无限CAS-失败就是已经被使用了 )

    空闲列表:维护一个表存放 已用,空闲,已回收 描述。

  1. Memory Pool内存池
    管理内存块。
    list<MemoryChunk *> m_chunks;
    是一个列表,存放所有向OS申请的内存块。

    • 能做的事情
      向OS要内存
      mall哦错,calloc
      释放内存
      没有垃圾回收器,需要手动释放

      其他
      打印chunk信息

  2. Memory chunk
    直接持有内存

  3. Memory Cell
    chunk中的单元。内存块。

Memory Pool

  1. 根据要的内存对齐后计算出要申请的内存大小。

  2. 向操作系统申请内存。

  3. 根据不同的垃圾回收算法填充不同的list
    标记清除,标记-整理,回收的是整个chunk.

     m_avaliable_table
     m_used_table
    

    分代+复制算法,
    空闲
    可用
    已用
    待交换

  • 标记-清除算法
    面向整个堆
    产生碎片

    如果你需要分配大对象,需要连续的空间。但是内存是碎片化的。

  • 标记-整理算法
    内存碎片合并
    老年代基本属于这个算法。

    合并内存,解决碎片问题。
    耗CPU

    Eden区,对象朝生夕死。碎片很多。
    碎片很多,合并碎片的时候需要STW。

    合并碎片的时候有两种对象需要处理:
    1. 这个空间已被释放。直接合并
    2. 这个空间的空间未被释放
    对象搬家(合并内存,数据移动,指针移动)

  • 分代-复制算法
    JVM使用这个算法,解决标记合并碎片消耗性能过高、GC停止用户线程过长问题。
    举例

    1. 将内存分段。 一半用(0-10) from,一半闲(11-20)to
    2. 发生GC的时候不需要整理,交换空间标记,角色切换。
      标记
      角色切换
      原先from区的内存处理
      标记的对象清理。存活的对象需要移动到新的内存区域(to 区)
      数据整理
      指针整理

注意:
不管现在的9种垃圾回收器,还是以后出现的垃圾回收器,都是这三种垃圾回收算法。

指针移动,老的指针地址不能变,这个怎么做到?(对象的hashCode不变)
指针是动态计算出来的。
公式依赖的变量在变。

return (pvoid)((ulong)chunk->get_data() + get_start()* chunk->get_align_size() )
卡表,记忆集

总结

内存池,JVM不需要手动释放内存,由垃圾回收器自动回收。

自动回收算法

标记-清除算法。面向整个堆,会产生碎片,在申请大对象时候,会有问题。

标记-整理算法。老年代常用,会在标记清理后整理内存,但是CPU耗费太大,STW时间会比较长。
标记,清理,数据整理,指针整理。

数据块(chunk cell)整理过程
从前到后合并一次
从后到前合并一次
分代-复制
解决整理算法性能太低,而且新生代的特点是朝生夕死。

不需要清理,只需要转移数据和指针切换 from,to角色

问题:  
    指针切换了以后为什么可以访问?
    hashcode会一直变。运行时计算出来的,所以没影响。

PS:对象模型,为什么要字节对齐。8B
CPU提升速率。
CPU读是 4

当数据从1字节开始的时候,问题很复杂,首先先将前4个字节读到寄存器,并再次读取4-7字节的数据进寄存器,接着把0字节,4,6,7字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器,对一个内存未对齐的寄存器进行了这么多额外操作,大大降低了CPU的性能。
但是这还属于乐观情况,上文提到内存对齐的作用之一是平台的移植原因,因为只有部分CPU肯干,其他部分CPU遇到未对齐边界就直接罢工了。