java对象详解

  • java对象详解

    • 内存布局

      • 普通对象布局
      • 数组的内存布局
      • 内部类的内存布局
    • 对象分解
      • 对象头-mark word(8字节)
      • 实例数据
      • 对齐填充(可选)
    • java锁分析

java对象详解

HotSpot虚拟机中,对象在内存中存储的布局可以分为对象头,实例数据,对齐填充三个区域。本文所说环境均为HotSpot虚拟机。即输入java -version返回的虚拟机版本:

java version "1.8.0_111"
Java(TM) SE Runtime Environment (build 1.8.0_111-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)

内存布局

普通对象布局

  • 在jvm中,任何对象都是8个字节为粒度进行对齐的,这是对象内存布局的第一个规则。

如果调用new Object(),由于Object类并没有其他没有其他可存储的成员,那么仅仅使用堆中的8个字节来保存两个字的头部即可。

除了上面所说的8个字节的头部(关于对象头,在下面会有详细解释),类属性紧随其后。属性通常根据其大小来排列。例如,整型(int)以4个字节为单位对齐,长整型(long)以8个字节为单位对齐。这里是出于性能考虑而这么设计的:通常情况下,如果数据以4字节为单位对齐,那么从内存中读4字节的数据并写入到处理器的4字节寄存器是性价比更高的。

为了节省内存,Sun VM并没有按照属性声明时的顺序来进行内存布局。实际上,属性在内存中按照下面的顺序来组织:

    1. 双精度型(doubles)和长整型(longs)
    1. 整型(ints)和浮点型(floats)
    1. 短整型(shorts)和字符型(chars)
    1. 布尔型(booleans)和字节型(bytes)
    1. 引用类型(references)

内存使用率会通过这个机制得到优化。例如,如下声明一个类:

class MyClass {
       byte a;
       int c;
       boolean d;
       long e;
       Object f;
}

如果JVM并没有打乱属性的声明顺序,其对象内存布局将会是下面这个样子:

[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[c:       4 bytes] 16
[d:       1 byte ] 17
[padding: 7 bytes] 24
[e:       8 bytes] 32
[f:       4 bytes] 36
[padding: 4 bytes] 40

此时,用于占位的14个字节是浪费的,这个对象一共使用了40个字节的内存空间。但是,如果用上面的规则对这些对象重新排序,其内存结果会变成下面这个样子:

[HEADER:  8 bytes]  8
[e:       8 bytes] 16
[c:       4 bytes] 20
[a:       1 byte ] 21
[d:       1 byte ] 22
[padding: 2 bytes] 24
[f:       4 bytes] 28
[padding: 4 bytes] 32

这次,用于占位的只有6个字节,这个对象使用了32个字节的内存空间。

  • 规则2:类属性按照如下优先级进行排列:长整型和双精度类型;整型和浮点型;字符和短整型;字节类型和布尔类型,最后是引用类型。这些属性都按照各自的单位对齐。

    现在我们知道如何计算一个继承了Object的类的实例的内存大小了。下面这个例子用来做下练习: java.lang.Boolean。这是其内存布局:

[HEADER:  8 bytes]  8
[value:   1 byte ]  9
[padding: 7 bytes] 16

Boolean类的实例占用16个字节的内存!惊讶吧?(别忘了最后用来占位的7个字节)。

  • 规则3:不同类继承关系中的成员不能混合排列。首先按照规则2处理父类中的成员,接着才是子类的成员。

    举例如下:

class A {
   long a;
   int b;
   int c;
}

class B extends A {
   long d;
}

类B的实例在内存中的存储如下:

[HEADER:  8 bytes]  8
[a:       8 bytes] 16
[b:       4 bytes] 20
[c:       4 bytes] 24
[d:       8 bytes] 32

如果父类中的成员的大小无法满足4个字节这个基本单位,那么下一条规则就会起作用:

  • 规则4:当父类中最后一个成员和子类第一个成员的间隔如果不够4个字节的话,就必须扩展到4个字节的基本单位。
class A {
   byte a;
}

class B {
   byte b;
}

[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[b:       1 byte ] 13
[padding: 3 bytes] 16

注意到成员a被扩充了3个字节以保证和成员b之间的间隔是4个字节。这个空间不能被类B使用,因此被浪费了。

  • 规则5:如果子类第一个成员是一个双精度或者长整型,并且父类并没有用完8个字节,JVM会破坏规则2,按照整形(int),短整型(short),字节型(byte),引用类型(reference)的顺序,向未填满的空间填充。
class A {
  byte a;
}

class B {
  long b;
  short c;
  byte d;
}

[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[c:       2 bytes] 14
[d:       1 byte ] 15
[padding: 1 byte ] 16
[b:       8 bytes] 24

在第12字节处,类A“结束”的地方,JVM没有遵守规则2,而是在长整型之前插入一个短整型和一个字节型成员,这样可以避免浪费3个字节。

数组的内存布局

数组有一个额外的头部成员,用来存放“长度”变量。数组元素以及数组本身,跟其他常规对象同样,都需要遵守8个字节的边界规则。

下面是一个有3个元素的字节数组的内存布局:

[HEADER:  12 bytes] 12
[[0]:      1 byte ] 13
[[1]:      1 byte ] 14
[[2]:      1 byte ] 15
[padding:  1 byte ] 16

下面是一个有3个元素的长整型数字的内存布局:

[HEADER:  12 bytes] 12
[padding:  4 bytes] 16
[[0]:      8 bytes] 24
[[1]:      8 bytes] 32
[[2]:      8 bytes] 40

内部类的内存布局

非静态内部类(Non-static inner classes)有一个额外的“隐藏”成员,这个成员是一个指向外部类的引用变量。这个成员是一个普通引用,因此遵守引用内存布局的规则。内部类因此有4个字节的额外开销。

以上引用:http://www.importnew.com/1305.html

对象分解

对象头-mark word(8字节)

对象头主要包含两部分信息,第一部分用于存储对象自身运行时数据,如哈希码,GC分代年龄(可以查看上一篇关于java内存回收分析的文章),锁状态标志,线程持有锁,偏向线程ID,偏向时间戳等。

如果对象是数组类型,则虚拟机用3个字宽存储对象头,如果是非数组类型,则用2个字宽存储对象头。下图是一个32位虚拟机mark部分占用内存分布情况

j1.jpeg

此图来源:http://blog.csdn.net/zhoufanyang_china/article/details/54601311

另一部分是klass类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,具体结构参考下图。

javao.png

在32位系统下,存放Class指针的空间大小是4字节,MarkWord是4字节,对象头为8字节。

在64位系统下,存放Class指针的空间大小是8字节,MarkWord是8字节,对象头为16字节。

64位开启指针压缩的情况下,存放Class指针的空间大小是4字节,MarkWord是8字节,对象头为12字节。

数组长度4字节+数组对象头8字节(对象引用4字节(未开启指针压缩的64位为8字节)+数组markword为4字节(64位未开启指针压缩的为8字节))+对齐4=16字节。

静态属性不算在对象大小内。

实例数据

对象实际数据,大下为实际数据的大小

对齐填充(可选)

按8字节对齐,参照上面内存布局部分

java锁分析

synchronized到底锁的是对象还是代码片段?

例:

package com.startclan.thread;

/**
 * Created by wongloong on 17-5-20.
 */
public class TestSync {
    public synchronized void test() {

        System.out.println("test1 start");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test1 end");
    }

    public synchronized void test2() {
        System.out.println("test2 start");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test2 end");
    }

}


package com.startclan.thread;

/**
 * Created by wongloong on 17-5-20.
 */
public class TestSyncStatic {
    public static synchronized void test() {
        System.out.println("test1 start");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test1 end");
    }

    public static synchronized void test2() {
        System.out.println("test2 start");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test2 end");
    }

}


package com.startclan;

import com.startclan.thread.TestSync;
import com.startclan.thread.TestSyncStatic;
import org.junit.Test;

/**
 * Created by wongloong on 17-5-18.
 */
public class TestWithThread {

    @Test
    public void testThread1() throws Exception {
        final TestSync t1 = new TestSync();
        /**
         * 测试synchronized同步非static代码块
         * 此处会先执行test方法然后执行test2方法,说明synchronized在同步非static方法时,
         * 只能同步同一对象的同一实例进行同步
         */
        new Thread(new Runnable() {
            @Override
            public void run() {
                t1.test();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                t1.test2();
            }
        }).start();
        Thread.sleep(4000);
    }

    @Test
    public void testThread2() throws Exception {
        final TestSync t1 = new TestSync();
        final TestSync t2 = new TestSync();

        /**
         * 测试synchronized同步非static代码块
         * t1 t2不同对象,
         * 不能同步方法
         */
        new Thread(new Runnable() {
            @Override
            public void run() {
                t1.test();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                t2.test2();
            }
        }).start();

        Thread.sleep(4000);
    }

    @Test
    public void testThread3() throws Exception {
        final TestSyncStatic tss1 = new TestSyncStatic();
        final TestSyncStatic tss2 = new TestSyncStatic();

        /**
         * 测试synchronized 同步 static代码块
         * 由于method1和method2都属于静态同步方法,
         * 所以调用的时候需要获取同一个类上monitor(每个类只对应一个class对象),
         * 所以也只能顺序的执行。
         */
        new Thread(new Runnable() {
            @Override
            public void run() {
                tss1.test();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                tss2.test2();
            }
        }).start();
        Thread.sleep(4000);
    }
}

此时输出结果为:

------------------------1-------------------------
test1 start
test1 end
test2 start
test2 end
-----------------------2-------------------------
test1 start
test2 start
test2 end
test1 end
-----------------------3-------------------------
test1 start
test1 end
test2 start
test2 end

结论:

  • synchronized(this)以及非static的synchronized方法,只能防止多个线程同时执行同一个实例的同步代码段(在第一段测试代码中,分别new了三个Mythread类,所以并不会执行同步)
  • synchronized(xx.class)及static的synchronized方法,可以防止多个线程同时执行同一个对象的多个实例同步的代码段
  • synchronize原理

    每一个对象头信息都包含一个锁定状态,可以看上面的mark word的图解。当线程进入对象中,尝试获取锁的所有权,如果为锁的值为0,则该线程进入,并设置为1,该线程为锁的拥有者。如果线程已经占用该锁,只是重新进入,并且锁值+1.当线程退出时则-1.如果其他线程访问这个对象实例,则改线程堵塞。直到锁值为0的时候,在重新尝试取得锁的所有权。

时间: 2024-08-01 06:37:20

java对象详解的相关文章

java 反射 详解

本文来自:blog.csdn.net/ljphhj JAVA反射机制:   通俗地说,反射机制就是可以把一个类,类的成员(函数,属性),当成一个对象来操作,希望读者能理解,也就是说,类,类的成员,我们在运行的时候还可以动态地去操作他们. 理论的东东太多也没用,下面我们看看实践 Demo - Demo: package cn.lee.demo; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import

Java synchronized详解

Java synchronized详解 第一篇: 使用synchronized 在编写一个类时,如果该类中的代码可能运行于多线程环境下,那么就要考虑同步的问题.在Java中内置了语言级的同步原语--synchronized,这也大大简化了Java中多线程同步的使用.我们首先编写一个非常简单的多线程的程序,是模拟银行中的多个线程同时对同一个储蓄账户进行存款.取款操作的. 在程序中我们使用了一个简化版本的Account类,代表了一个银行账户的信息.在主程序中我们首先生成了1000个线程,然后启动它们

Java虚拟机详解——JVM常见问题总结

[正文] 声明:本文只是做一个总结,有关jvm的详细知识可以参考之前的系列文章,尤其是那篇:Java虚拟机详解04--GC算法和种类.那篇文章和本文是面试时的重点. 面试必问关键词:JVM垃圾回收.类加载机制. 先把本文的目录画一个思维导图:(图的源文件在本文末尾) 一.Java引用的四种状态: 强引用:  用的最广.我们平时写代码时,new一个Object存放在堆内存,然后用一个引用指向它,这就是强引用. * 如果一个对象具有强引用,那垃圾回收器绝不会回收它*.当内存空间不足,Java虚拟机宁

Java引用类型详解

JVM  的垃圾回收器对于不同类型的引用有不同的处理方式.java中对于一个对象来说,只要有引用的存在,它就会一直存在于内存中.如果这样的对象越来越多,超出了JVM中的内存总数,JVM就会抛出OutOfMemory错误.虽然垃圾回收的具体运行是由JVM来控制的,但是开发人员仍然可以在一定程度上与垃圾回收器进行交互,其目的在于更好的帮助垃圾回收器管理好应用的内存.这种交互方式就是使用JDK1.2 引入的  java.lang.ref包. 强引用(strong reference) 在一般的 Jav

Java堆栈详解 .

1. Java中堆栈(stack)和堆(heap) (1)内存分配的策略 按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的. 静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不 允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的

Java网络详解

Java网络详解 Java网络基本概念 网络基础知识 1.计算机网络形式多样,内容繁杂.网络上的计算机要互相通信,必须遵循一定的协议.目前使用最广泛的网络协议是Internet上所使用的TCP/IP协议 2.网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯.网络编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输.在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上

Java面向对象详解

Java面向对象详解 前言:接触项目开发也有很长一段时间了,最近开始萌发出想回过头来写写以前学 过的基础知识的想法.一是原来刚开始学习接触编程,一个人跌跌撞撞摸索着往前走,初学的时候很多东西理解的也懵懵懂懂,后来实践的多了,有些东西才慢慢清 楚:二是经过一定的实践之后,反过头来再去学习一些基础东西才能够理解的更透彻:三是有些东西基础但是确很重要,是值得好好搞一搞的. 1.面向对象 面向对象(Object Oriented)是一种新兴的程序设计方法,或者是一种新的程序设计规范(paradigm),

Java反射详解

Java反射详解 分类:java, 基础日期:2012-07-20作者:ticmy 19 反射,是Java中非常重要的一个功能,如果没有反射,可以说很多框架都难以实现. 什么是反射?说白了就是可以通过Java代码获取装载到方法区的类信息的手段. 当装载一个类时,会在方法区产生一个数据结构,该结构中包含着装载的类的相关信息.字节码可以看成是数据流,那么方法区的这种数据结构可以说是字节码数据流的结构化表现.装载的最终产物就是java.lang.Class类的一个对象,它是Java程序与方法区内部数据

java多线程详解

转自:线程间通信.等待唤醒机制.生产者消费者问题(Lock,Condition).停止线程和守护线程.线程优先级 1  线程间通信 1.1  线程间通信 其实就是多个线程在操作同一个资源,但是操作的动作不同. 比如一个线程给一个变量赋值,而另一个线程打印这个变量. 1.2  等待唤醒机制 wait():将线程等待,释放了CPU执行权,同时将线程对象存储到线程池中. notify():唤醒线程池中一个等待的线程,若线程池有多个等待的线程,则任意唤醒一个. notifyAll():唤醒线程池中,所有