运行时数据区值方法区和字符串常量池

运行时数据区之方法区和字符串常量池

一、运行时数据区概述
1.JVM运行时数据区规范

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ilNcqrmT-1628493578073)(C:\Users\孤风\AppData\Local\Temp\WeChat Files\f242cfdb31d6edc52c2703def013e78.png)]

按照线程使用情况和职责分成俩大类:

  • 线程独享(程序执行区域)

    • 不需要垃圾回收
    • 虚拟机栈、本地方法栈、程序计数器
  • 线程共享(数据存储区域)

    • 垃圾回收
    • 存储类的静态数据和对象数据
    • 堆和方法
2.分配JVM内存空间
分配堆的大小
-Xms (堆的初始容量)
-Xmx (堆的最大容量)

-XX:InitialHeapSize=268435456
-XX: MaxHeapSize=4294967269
#如果为了提高性能,可以考虑去浪费空间,就是将初始容量和最大容量相等

分配方法区的大小

-XX:PermiSize
永久代的初始容量
-XX:MaxPermisze
永久代的最大容量

-XX:MetaspaceSize
元空间的初始大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize
最大空间,默认是没有限制的。

除了上面俩个指定大小的选项以外,还有俩个与GC相关的属性:

-XX:MinMetaspaceFreeRatio
在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio
在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
分配线程空间的大小
-Xss:
为JVM启动的每个线程分配的内存大小,默认JDK1.4中是256k,JDK1.5+中是1M
二、方法区
1.方法区存储什么数据
类型信息,比如Class(com.test.User类)
方法信息,比如Method(方法名称,方法参数列表,方法返回值信息)
字段信息,比如Field(字段类型,字段名称需要特殊设置才能保存的住)

Code区,存储的是方法执行对应的字节码指令
方法表(方法调用的时候)在A类的main方法中调用B类的method1方法,是根据B类的方法表去查找合适的方法,进行调用的。

静态变量(类变量)  ---JDK1.7知乎,转移到堆中存储

运行时常量池(字符串常量池) ---从class中的常量池加载而来---JDK1.7中后,转移到堆中存储
* 字面量类型
	*双引号引起来的字符串值,比如"USER" ---- 会进入字符串常量池(StringPool)
	*final修饰的变量
	*非final修饰的变量,比如long,double,floa
*引用类型-->内存地址
	*类的符合引用
	*方法
	*字段

JIT编译器编译之后的代码缓存

如果需要访问方法区中类的其他信息,都必须先获得Class对象,才能去访问该Class对象管理的方法信息或者字段信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZQ9UVCcK-1628493578076)(C:\Users\孤风\AppData\Local\Temp\WeChat Files\83bee7a499caa09e5d6ded5315cfe5a.png)]

1.1类型信息(重点)
  • 类型的全限定名
  • 超类的全限定名
  • 直接超接口的全限定名
  • 类型标志(该类是类类型还是接口类型)
  • 类的访问描述符(public ,private,default,abstract,final,static)
1.2类型的常量池

存放该类型所用到的常量的有序集合,包括直接常量(如字符串、整数、浮点数的常量)和对其他类型、字段、方法的符合引用。

常量池中每一个保存的常量都有一个索引,就像数组中的字段一样。因为常量池中报酬着所有类型使用到的类型、字段、方法的字符引用,所以它也是动态链接的主要对象(在动态连接中起到核心作用).

1.3字段信息(重点)
  • 字段修饰符(public、protect、private、default);
  • 字段的类型
  • 字段名称
1.4方法信息(重点)

方法信息中包含类的所有方法,每个方法包含以下信息:

  • 方法修饰符
  • 方法返回类型
  • 方法名
  • 方法参数个数、类型、顺序等
  • 方法字节码
  • 操作数栈和该方法在栈帧中的局部变量区大小
  • 异常表
1.5 类变量(重点)

指该类所有对象共享的变量,即使没有任何实例对象时,也可以访问的类变量。他们与类进行绑定。

1.6指向类加载器的引用

每一个被JVM加载的类型,都保存这个类加载器的引用,类加载器动态链接时会用到

1.7指向Class实例的引用

类加载的过程中,虚拟机会创建该类型的Class实例,方法区中必须保存对该对象的引用。通过Class.forName(String className)来查找获得该实例的引用,然后创建该类的对象。

1.8方法表(重点)

为了提高访问效率,JVM可能会对每个装置的非抽象类,都创建一个数组,数组的每个元素是实例可能调用的方法的直接引用,包括父类中继承过来的方法。这个表在抽象类或者接口中是没有的。

1.9运行时常量池

(Runtime Constant Pool)

class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种字面常量和符合引用,这部分内容被类加载后进入方法去的运行时常量池中存放。

运行时常量池相对于class文件常量池的另外一个特征具有动态性,可以在运行期间将新的常量放入池中(典型的如String类的intern()方法).

永久代和元空间的区别是什么?
  1. JDK1.8之前使用的方法区实现是永久代,JDK1.8及以后使用的方法区实现是元空间。
  2. 存储位置不同,永久代所使用的内存区域是JVM进程所使用的区域,它的大小受整个JVM的大小所限制。元空间所使用的内存区域是物理内存区域。那么元空间的使用大小只会受物理内存大小的限制。
  3. 存储内容不同,永久代存储的信息基本上就是上面方法区存储内容中的数据。元空间只存储类的元信息,而静态变量和运行时常量池都挪到堆中。
3.为什么要使用元空间来替换永久代?
  1. 字符串存在永久代中,容易出现性能问题和永久代内存溢出。
  2. 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
  3. 永久代会为GC带来不必要的复杂度,并且回收效率偏低。
  4. Oracle计划将HotSpot与JRockit合二为一
结论

其实,移除永久代的工作从JDK1.7就开始了。

JDK1.7中,存储在永久代的部分数据就已经转移到Java Heap.

但永久代仍存在于JDK1.7中,并没完全移除,譬如字面量(interned strings)转移到java heap;类的静态变量(class statics)转移到了java heap.

4.方法区异常演示
4.1.类加载导致OOM异常

1)案例代码

我们现在通过动态生成类来模拟方法区的内存溢出:

public static void main(String[] args) { 
 URL url = null; 
 List<ClassLoader> classLoaderList = new
ArrayList<ClassLoader>(); 
 try { 
 url = new File("/tmp").toURI().toURL(); 
 
 URL[] urls = {url}; 
 while (true){ 
 ClassLoader loader = new
URLClassLoader(urls);
 classLoaderList.add(loader); 
 
loader.loadClass("com.kkb.test.memory.Test"); 
 } 
 } catch (Exception e) { 
 e.printStackTrace(); 
 }
 }

2)JDK1.7分析

指定的PermGen区的大小为8M:

最典型的场景就是,在jsp页面比较多的情况,容易出现永久代内存溢出。

3)JDK1.8+分析

现在我们在JDK8下重新运行一下案例代码,不过这次不在指定PermSize和MaxPermiSize.而是指定MetaSpaceSize和MaxMetaSpaceSize的大小。输出结果如下:

从输出结果,我们可以看出,这次不再出现永久代溢出,而是出现了元空间的溢出。

4.2字符串OOM异常
1)案例代码

一下这段程序以2的指数级不断的生成新的字符串,这样可以比较快速的消耗内存:

public class StringOomMock { 
 static String base = "string"; 
 public static void main(String[] args) { 
 List<String> list = new ArrayList<String>(); 
 for (int i=0;i< Integer.MAX_VALUE;i++){ 
 String str = base + base; 
 base = str; 
 list.add(str.intern()); 
 } 
 }
}

JDK1.6

JDK1.6的运行结果:

在JDK1.6下,会出现永久代的内存溢出。

3)JDK1.7在JDK1.7中,

会出现堆内存溢出。结论是:JDK1.7已经将字符串常量由永久代转移到堆中。

4)JDK1.8+

在JDK1.8中,也会出现堆内存溢出,并且显示JDK1.8中PermSize和MaxPermGen.因此,可以验证JDK1.8中已经不存在永久代的结论。

字符串常量池

1.三种常量池区别

class常量池(静态常量池)

  • 字面量

    • 数值型 int,float,long,double等
    • 双引号引起来的字符串值
  • 符号引用

    • Class,Method,Field等

一个Class文件中只有一个class常量池

JVM运行时数据区

  • 运行时常量池

    • 字面量

      • 数值型:int,float,long,double等
    • 符号引用

      • Class,Method Field等

        一个Class对象,一个运行时常量池

  • 字符串常量池

    • 双引号引起来的字符串值

全局只有一个字符串常量池

2.字符串常量池中如何存储数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J0vcxxDf-1628493578077)(C:\Users\孤风\AppData\Local\Temp\WeChat Files\9e84eeafd6c925494378cb01bb2acb4.png)]

字符串常量池查找字符串的方式:
  • 根据字符串的hashcode找到对应entry.

  • 如果没冲突,它可能只是一个entry.

  • 如果有冲突,它可能是一个entry链表,然后java再遍历entry链表,匹配引用对应的字符串。

  • 如果找得到字符串,返回引用。

  • 如果找不到字符串,在使用intern方法的时候,会将intern方法调用者的引用放入stringtable中

为了提高匹配速度,即更快的查找某个字符串是否存在与常量池,Java在设计字符串常量池的时候,还搞了一张【stringtable】,stringtable有的类似与我们的hashtable,里面保存了【字符串的引用】.

hashtable中数据存储到数组中的位置是通过以下计算得来的:

  • hash(字符串值)/数组长度=余数(数组下标)
  • 数组中存储的元素都是一个Entry对象(next指针)
  • Entry通过next指针可以形成链表。

hashtable会存在俩种问题:hash冲突问题,rehash问题

hash冲突问题:链地址法,也就是使用链表

一旦导致hash冲突之后,就会形成链表

链表是增删快,查找慢。

在jdk7+,‘StringTable’的长度可以通过一个参数指定:-XX:StringTableSize=99991

字符串常量池案例分析
public class Test {
 public void test() {
 String str1 = "abc";
 String str2 = new String("abc");
 System.out.println(str1 == str2);
 String str3 = new String("abc");
 System.out.println(str3 == str2);
 String str4 = "a" + "b";
 System.out.println(str4 == "ab");
 final String s = "a";
 String str5 = s + "b";
 System.out.println(str5 == "ab");
 String s1 = "a";
 String s2 = "b";
 String str6 = s1 + s2;
 System.out.println(str6 == "ab");
 String str7 = "abc".substring(0, 2);
 System.out.println(str7 == "ab");
 String str8 = "abc".toUpperCase();
 System.out.println(str8 == "ABC");
 String s5 = "a";
 String s6 = "abc";
String s7 = s5 + "bc";
 System.out.println(s6 == s7.intern());
 }
}
结论:
1.单独使用“”引号创建的字符串都是常量,编译器就已经确定存储到StringPoo中。
2.使用new String("")创建的对象会存储到heap中,是运行期新创建的。
3.使用只包含常量的字符串连接符如“aa"+"bb"创建的也是常量,编译期就能确定已经存储到String Pool中。
4.使用包含变量的字符串连接如“aa"+s创建的对象是运行期才创建的,存储到heap中
5.运行期调用String的intern()方法可以向String Pool中动态添加对象。
String的Intern方法详解
4.1.intern的作用
  1. 返回stringtable中对应字符串对象的引用值
  2. 如果stringtable中没有对应字符串对象的一条记录,则动态添加字符串对象到stringtable中。
4.2 intern方法的好处

测试案例:

import java.util.Random;

public class TestIntern {
    
    //字符串数组的长度
    static final int MAX = 1000 * 1000;
    //字符串数组
    static final String[] arr = new String[MAX];

    public static void main(String[] args) {

        Integer[] DB_DATA = new Integer[10];

        Random random = new Random(10 * 10000);
        
        //产生10个随机数,放入DB_DATA数组中保存
        for (int i = 0; i < DB_DATA.length; i++) {
            DB_DATA[i] = random.nextInt();
        }
        long t = System.currentTimeMillis();

        for (int i = 0; i < MAX; i++) {
            arr[i] = new String(String.valueOf(DB_DATA[i %
                    DB_DATA.length])).intern();
        }
        System.out.println(System.currentTimeMillis() - t);
        System.gc();
    }
    
}

以上陈旭回应很多重复的相同的字符串产生,但是这些字符串的值都是只有在运行期才能确定的。所以,只能我们通过intern显示的将其加入常量池,这样可以减少很多字符串的重复创建。

JDK6中常量池位于PremGen区,大小受限,不建议使用String.intern()方法,不过JDK7将常量池移到了Java堆区,大小可控,可以重新考虑使用String.intern()方法,但是由对比测试可知,使用该方法的耗时不容忽视,所以需要慎重考虑该方法的使用;

String.intern()方法主要适用于程序中需要保存有限个会被反复使用的值的场景,这样可以减少内存消耗,同时在进行笔记操作时减少时耗,提高程序性能。

4.3intern案例分析
public static void main(String[] args) {
        String s = new String("1");
        String s2 = "1";
        s.intern();
        System.out.println(s == s2);

        String s3 = new String("1") + new String("1");

        String s4 = "11";
        s3.intern();

        System.out.println(s3 == s4);
    }
四、Java堆
堆内存分配

堆内存划分

  • 堆(Heap)线程共享(-Xms -Xmx)
    • 新生代(Young 1/3) -Xmn Minor GC
      • Eden(8/10)
      • Survivor From(1/10)
      • Survivor To(1/10)
    • 老年代 Tenured(2/3) Major GC

Java堆在JVM启动时拆改那就,是通过【垃圾收集器】去实现内存分配。它是虚拟机管理最大的一块内存。也是垃圾回收的主要区域,而且主要采用【分代回收算法】。

堆空间的参数设置

-XX:+PintFlagsInitial:查看所有参数的默认初始值

-XX:+PrintFlagsFinal:查看最终值(初始值可能被修改掉)

-Xms:初始堆空间内存(默认为物理内存的1/64)

-Xms:最大堆空间内存(默认为物理内存的1/4)

-Xmn:设置新生代的大小。(初始值及最大值)

-XX:NewRatio:配置新生代与老年代在堆结构的占比

-XX:SurvivorRatio:设置新生代中Eden和SO/S1空间的比例

-XX:MaxTenuringThreshold设置新生代垃圾的最大年龄

-XX:+PrintGCDetails:输出详细的GC处理日志 打印GC简要信息:

-XX:PrintGc -verbose:gc

-XX:HandlePromotionFailure:是否设置空间担保。

通过工具查看堆内存信息
1.jvisualvm工具

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cmsgkZ47-1628493578079)(E:\笔记\3e6089d2388e94f68488ea4150d4dcf.png)]

2.VM options -XX:+PrintGCDetails
 /**
     * -Xmx600m -Xms600m -XX:+PrintGCDetails
     * @param args
     */
    public static void main(String[] args) {
        ArrayList<Picture> list = new ArrayList<Picture>();
        while (true){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            list.add(new Picture(new Random().nextInt(1024*1024)));
        }
    }
    
}
class Picture{
    private byte[] pixels;
    public Picture(int length){
        this.pixels = new byte[length];
    }

}

停止进程即打印日志

Heap
 PSYoungGen total 2560K, used 1498K
[0x00000007bfd00000, 0x00000007c0000000,
0x00000007c0000000)
 eden space 2048K, 73% used
[0x00000007bfd00000,0x00000007bfe76bb8,0x00000007bff00000)
 from space 512K, 0% used
[0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
 to space 512K, 0% used
[0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
 ParOldGen total 7168K, used 0K [0x00000007bf600000,
0x00000007bfd00000, 0x00000007bfd00000)
 object space 7168K, 0% used
[0x00000007bf600000,0x00000007bf600000,0x00000007bfd00000)
 Metaspace used 2713K, capacity 4486K, committed
4864K, reserved 1056768K
 class space used 289K, capacity 386K, committed 512K,
reserved 1048576K

3.jstat命令
- jps -l | grep 'HeapDemo'
52089 com.HeapDemo
- jstat -gc 52089
S0C S1C S0U S1U EC EU OC 
 OU MC MU CCSC CCSU YGC YGCT FGC
 FGCT GCT
512.0 512.0 0.0 483.9 2048.0 59.9 7168.0 
464.0 4864.0 3202.6 512.0 349.4 1 0.006 0 
 0.000 0.00
- SOC:第一个幸存区的大小
- S1C:第二个幸存区的大小
- SOU:第一个幸存区的使用大小
- S1U:第二个幸存区的使用大小
- EC:伊甸园区的大小
- EU:伊甸园区的使用大小
- OC:老年代大小
- OU:老年代使用大小
- MC:方法区大小
- MU:方法区使用大小
- CCSC:压缩卷空间大小
- CCSU:压缩机空间使用大小
- YGC:年轻代垃圾回收次数
- YGCT:年轻代垃圾回收消耗时间
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT: 垃圾回收消耗总时间
4.jmap命令
➜ ~ jmap -heap 52089
Attaching to process ID 52089, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
 MinHeapFreeRatio = 0
 MaxHeapFreeRatio = 100
 MaxHeapSize = 10485760 (10.0MB)
 NewSize = 3145728 (3.0MB)
 MaxNewSize = 3145728 (3.0MB)
 OldSize = 7340032 (7.0MB)
 NewRatio = 2
 SurvivorRatio = 8
 MetaspaceSize = 21807104 (20.796875MB)
 CompressedClassSpaceSize = 1073741824 (1024.0MB)
 MaxMetaspaceSize = 17592186044415 MB
 G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
 capacity = 2097152 (2.0MB)
 used = 61336 (0.05849456787109375MB)
 free = 2035816 (1.9415054321289062MB)
 2.9247283935546875% used
From Space:
 capacity = 524288 (0.5MB)
 used = 495544 (0.47258758544921875MB)
 free = 28744 (0.02741241455078125MB)
 94.51751708984375% used
To Space:
 capacity = 524288 (0.5MB)
 used = 0 (0.0MB)
 free = 524288 (0.5MB)
 0.0% used
PS Old Generation
 capacity = 7340032 (7.0MB)
 used = 475168 (0.453155517578125MB)
 free = 6864864 (6.546844482421875MB)
 6.473650251116071% used
2143 interned Strings occupying 151616 bytes
演示
package example;

/**
 * 1.设置堆空间大小的参数
 * -X是jvm的运行参数
 * -Xms 用来设置堆空间(年轻代+老年代)的初始内存大小
 * -Xms 用来设置堆空间(年轻代+老年代)的最大内存大小
 * 2.手动设置 -Xms600m -Xmx600m
 *  开发中建议将初始化堆内存和最大的堆内存设置成相同的值
 * 3.查看设置的参数: 方式一:  jps / jstat -gc 进程id
 *                  方式二: -XX:+PrintGCDetails
 * 4.默认值
 *  初始内存大小:物理电脑内存大小 /64; 最大内存大小:物理电脑内存大小 /4
 */
public class HeapSpaceInitial {
    public static void main(String[] args) {
        long initMemory = Runtime.getRuntime().totalMemory()/1024/1024;
        long maxMemory = Runtime.getRuntime().maxMemory()/1024/1024;
        System.out.println("-Xms:"+initMemory+" M");
        System.out.println("-Xmx:"+maxMemory+" M");
        System.out.println("系统内存⼤⼩:"+initMemory*64.0/1024+" G");
        System.out.println("系统内存⼤⼩:"+maxMemory*4.0/1024+" G");
    }
}

对象内存分配
对象内存的分配原则
序号介绍
1优先在Eden分配,如果Eden空间不足虚拟机则会进行溢出MinorGC
2[大对象]直接进入老年代,【大对象】一般指的是【很长的字符串或数组】,默认是0
3【长期存活的对象】也会进入老年代,每个对象都有一个【age】,当age到达设定的年龄的时候就会进入老年代,默认是15岁。
对象内存分配方式

内存分配的方法有俩种:指针碰撞(Bump the Pointer)和空闲列表(Free List)

分配方法说明收集器
指针碰撞内存地址是连续的(年轻代)Serial和ParNew收集器
空闲列表内存地址不连续(年老代)CMS收集器和Mark-Sweep收集器
对象内存分配安全问题

在分配内存的时候,虚拟机给A线程分配内存过程中,指针未修改。此时B线程同时使用了同样一块内存。

在JVM中有俩种解决办法:
  1. CAS是乐观锁的一种实现方式。虚拟机采用CAS配上失败重试的方法保证更新操作的原子性。

  2. TLAB,本地线程分配缓冲(Thread Local Allocation Buffer即TLAB):为每一个线程预先分配一块内存

    JVM在第一次给线程中的对象分配内存时,首先使用CAS进行TLAB的分配。
    当对象大于TLAB张宏的剩余内存或TLAB的内存已用尽时,在采用上述的CAS进行内存分配
    
对象内存分配担保(老年代)
在新生代无法分配内存的时候,我们想把新生代的对象转移到老生代,
然后把新对象放入腾空的新生代。此时就需要内存担保机制。

JVM参数:-Xms20M、-Xmx30M、-Xmn10M

分配三个2MB的对象和一个4MB的对象。

代码如下:

package example;

/**
 * 内存分配担保案例
 */
public class MemoryAllocationGuarantee {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        memoryAllocation();
    }

    public static void memoryAllocation() {
        byte[] allocation1, allocation2, allocation3,
                allocation4;
        allocation1 = new byte[2 * _1MB];//2M
        allocation2 = new byte[2 * _1MB];//2M
        allocation3 = new byte[2 * _1MB];//2M
        allocation4 = new byte[4 * _1MB];//4M
    // allocation4 = new byte[5 * _1MB];//4M
    // allocation4 = new byte[3 * _1MB];//4M
        System.out.println("完毕");
    }
}

堆内存分配情况如下:

假设:新生代总可用空间为9216KB

eden space(8192K)

Survivor区 from space(1024K) to space(1024K)

串行垃圾收集器案例

设置JVM参数:

-Xms20M -Xmx20M -Xmn10M -XX:PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
担保机制在JDK1.5以及之前的版本中是默认关闭的,需要通过HandlePromotionFailure手动指定,JDK1.6之后就默认开启。这里我们使用的是JDK1.8,所以不用在手动去开启担保机制。
串行垃圾收集器案例结论
  1. 当Eden去存储不下新分配的对象时,会触发minorGC
  2. GC之后,还存活的对象,按照正常逻辑,需要存入到Survivor区(幸存区)。
  3. 当无法存入到幸存区时,此时会触发担保机制
  4. 发生内存担保时,需要将Eden区GC之后还存活的对象放入老年代。后来的新对象或者数组放入Eden区
并行垃圾收集器案例

设置JVM参数:

-Xms20M -Xmx20M -Xmn10M -XX:PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseParalleGC

修改GC组合为(Paralle Scavenge+Serial Old的组合)

发现当我没使用ParalleGC收集器组合(Parallel Scavenge+Serial Old的组合)下,担保机制的实现和之前的Client模式下(SerialGc收集器组合)有所变化。
注意点:
在GC前还会进行一次判断,如果要分配的内存>=Eden区大小的一半,那么会直接把要分配的内存放入老年代中。否则会进入担保机制。
对象创建与访问

对象创建

Student stu = new Student();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dZ66Z09f-1628493578080)(E:\笔记\4d65fe3f98f8c7458916a057c163c65.png)]

对象的内存布局

对象在内存中存储的布局可以分为三块区域:对象头(Header),实例数据(Instance Data)和对其填充(Padding).

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YkluCQMr-1628493578082)(E:\笔记\35bc01a610b70454839d916b434a2bd.png)]

1)对象头

​ 对象头包括俩部分信息:

​ 一部分是用于存储对象自身的运行数据,如哈希码(HashCode),GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。

​ 另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪一个类的实例。当对象是一个java数组的时候,那么对象头还必须有一块用于记录数组长度的数据,,因此虚拟机可以通过普通java对象的元数据信息确定java对象的大小,但是从数组的元数据中无法确定数组的大小。

2)实例数据

存储的是对象真正有效的信息。

3)对齐填充

这部分并不是必须要存在的,没有特别的含义,在jvm中对象的大小必须是8字节的整数倍,而对象头也是8字节的倍数,当对象实例数据部分没有对齐时,就需要通过对其填充来补全。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值