Java高级之内存模型分析

简介: 博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved !下文是博主感悟,请带着怀疑性的态度阅读!需要了解基本变量所占内存大小,请移步:读书笔记-类结构的认识Java存储空间有这么几块-来源于Java编程思想寄存器:位于处理器内部,不受外层代码控制,由处理器自行分配-C/C++可以建议分配方式,使用句柄(包含引用类型和引用地址)来操作数据。


博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved !

下文是博主感悟,请带着怀疑性的态度阅读!

需要了解基本变量所占内存大小,请移步:读书笔记-类结构的认识

Java存储空间有这么几块-来源于Java编程思想

寄存器:位于处理器内部,不受外层代码控制,由处理器自行分配-C/C++可以建议分配方式,使用句柄(包含引用类型和引用地址)来操作数据。

堆栈:位于RAM中  引用和基本数据类型存放的区块。 指针向下生成新对象,向上释放对象(new关键字),相当于链表结构。

堆:位于RAM中 对象存放的区块

常量存储:位于ROM中 存放于方法体中

非RAM存储:流对象和持久化数据-存储到硬盘

说到存储就难免讲到JVM的垃圾回收机制,需要了解的同学可以点进去看看


如果要实现处理器的高效率,那么就要压榨它的每一寸(byte)的运行能力,I3的处理器达到3.4GHz,即每秒运算3.4亿次,因此给它划分任务块,每块分配足够多的任务,实现高并发;所以对内存的模型需要详细了解。


由于硬件的读写速度与处理器的运算速度差距过大,一般都会写一层高速缓存来作为缓冲,一边从硬盘读数据到缓存,一边把处理器的处理结果写入缓存,一边把缓存中要写入的数据写到硬盘;因此很多程序会使用到中间件。


如果多个处理器同时处理缓存,就需要拟定协议谁先谁后,对于同一个处理器中的任务也是同样如此,有sychronzied关键字来处理;同时处理器还会对一段程序丧心病狂的进行(OOOE)乱序处理,也就是顺序在前面的代码并不一定先执行,对于依赖前段程序结果的代码来说,就需要通过其他途径来保证顺序性。

sychronized关键字的原子性在于,顺序执行到这一句时,当前线程被挂起,执行完毕再唤醒。

有时见到一种单例的写法

if(instance==null){

sychronized(Instance.class){

if(instance==null){

instance=new Instance();

}

return instance;

 }

}

return instance;

这种的好处在于外层实现高性能并发,内层加if判断防止多个初始化同时进行(即第一个初始化完成,处理状态的其他线程再进去初始化一次)。

内存模型定义的关键在于第一使各处理器的操作不具有歧义 第二不影响拓展各自的特性;它主要定义虚拟机存取数据的细节,定义所有变量都存储在主内存,每条线程都有自己的工作内存(主内存的副本,或者叫引用),不同线程的工作内存互不直接访问,通过主内存来影响各自对值的引用;拿虚拟机来做例子,寄存器、栈、堆缓存就像工作内存,硬件设备就是主内存。

定义了八种操作来完成上述存取过程

lock和unlock 作用于主内存,标识为某线程独占或释放,成对存在

read和load 读取和加载,从主内存将数据读给工作内存,再加载到工作内存,成对存在

use和assign 使用和赋值 作用于工作内存,将变量给工作引擎,将接收到的值进行处理 成对存在

store和write 存储和写入 从工作内存将数据存回主内存,再写入主内存 成对存在

顺序过程unlock放到write后面即可。不允许读不入工作内存,也不允许写不入主内存;新变量只能在主内存中产生,不能跳级执行,lock与unlock一样重复执行多次,只是每次lock工作内存则被清空。lock可类比为Java的Lock对象。

 讲完上面的存取过程,变量的原子性就很好讲了,原子性指对变量的存取过程顺序执行,要么执行完,要么不执行,不允许其他线程对其进行污染。而带有特殊含义的sychronzied和final关键字,就可以用原子性来解释:前者由于保障了unlock之前变量已同步到主内存,这里的变量指方法体或类中所有的;后者是避免构造器把this引用传递出去,因而像惰性气体一样稳定。

另外java的先行发生原则,很有意思,有以下几种表现形式

1、程序控制流顺序执行,即代码顺序执行

2、volitale和锁顺序执行,即前一个锁执行结束,后一个得到锁

3、Thread的start方法先于run方法内的方法执行

4、通过isAlive、interrupt和join方法判断线程是否存活

5、对象结束先于finilize方法执行

6、A先于B,B先于C,可得出A先于C执行的传递性。

最后再讲下volatile关键字,它有两个作用 

1、保证改变后马上通知其他线程(执行write操作后,变量马上刷新),即对其他线程的可见性

2、保障上面所指丧心病狂的处理器处理此变量不被乱序操作,即禁止指令重排优化

但是volatile没有原子性(PS:原子性指read-assign-store这3组,只要一个执行,就会全部执行),不能保证作为计数器而正确存在;所以一般如果很少对它标识的变量进行改变的场景比较适用,比如多条线程共同执行多个有父类的任务,一个条件通知结束,则所有线程一起结束;就像劳动节来临,不论工程师还是设计师,都可以休息一天。

补充一点,64位的long和double无原子性,会被当成两个32位变量来处理,但一般默认为具有原子性,占用两个局部变量的位置

虚拟机运行时的数据区域有以下几种

虚拟机栈 主要存放引用和基本数据类型

堆 主要存放对象

方法区 常见的类信息除对象以外的所有,包括类信息(数据类型),常量池,方法、接口、静态变量等

本地方法栈 用来执行native方法

程序计数器 存储下一条需要执行的字节码指令,每条线程都有一个

虚拟机的多线程是通过线程切换并分配执行时间,同时一个内核在任一时刻只处理一条线程的指令 

虚拟机栈和堆是线程共享的数据区,方法区、本地方法栈和程序计数器是线程所不能访问到的数据区

其中数据访问的方式有两种:一种是句柄形式,引用指向句柄,句柄包含对象地址和对象类型;一种是指针,直接存储对象地址,以句柄少一步,所以访问也会快一些,而HotSpot就是用这种;前者也有一定优化,值发生改变时,引用不用变,后者要改变指针才行。

内存异常有两种表现,一种叫OutOfMemoryError(内存溢出),请求的虚拟机扩展栈已无足够空间,分配给新对象,典型的标记-清理算法容易产品这种情况,另一种叫StackOverFlow,指请求深度超过限制。还有一种常见的Memory Leak(内存泄露),指已经申请的内存无法被回收。

接下来对中英文分别占多少字节进行解释

public static void main(String[] args) {
String[] charsetNames = { "utf-8", "utf-16", "UTF-16BE", "UTF-16LE", "UTF-32", "UTF-32BE", "UTF-32LE", "unicode", "GBK", "GB2312", "GB18030",
"ISO8859-1", "BIG5", "ASCII" };


for (int i = 0; i < charsetNames.length; i++) {
printByteLength(charsetNames[i]);
}
}

/**
public static void printByteLength(String charsetName) {
* String类的不带参数的getBytes()方法会以程序所运行平台的默认编码方式为准来进行转换,
* 在不同环境下可能会有不同的结果,因此建议使用指定编码方式的getBytes(String charsetName)方法。
*/
String a = "a"; // 一个英文字符
String b = "啊"; // 一个中文字符
try {
System.out.println();
System.out.println(charsetName + "编码英文字符所占字节数:" + a.getBytes(charsetName).length);
System.out.println(charsetName + "编码中文字符所占字节数:" + b.getBytes(charsetName).length);
} catch (UnsupportedEncodingException e) {
System.out.println("非法编码格式!");
}
}


utf-8编码英文字符所占字节数:1
utf-8编码中文字符所占字节数:3

utf-16编码英文字符所占字节数:4
utf-16编码中文字符所占字节数:4

UTF-16BE编码英文字符所占字节数:2
UTF-16BE编码中文字符所占字节数:2

UTF-16LE编码英文字符所占字节数:2
UTF-16LE编码中文字符所占字节数:2

UTF-32编码英文字符所占字节数:4
UTF-32编码中文字符所占字节数:4

UTF-32BE编码英文字符所占字节数:4
UTF-32BE编码中文字符所占字节数:4

UTF-32LE编码英文字符所占字节数:4
UTF-32LE编码中文字符所占字节数:4

unicode编码英文字符所占字节数:4
unicode编码中文字符所占字节数:4

GBK编码英文字符所占字节数:1
GBK编码中文字符所占字节数:2

GB2312编码英文字符所占字节数:1
GB2312编码中文字符所占字节数:2

GB18030编码英文字符所占字节数:1
GB18030编码中文字符所占字节数:2

ISO8859-1编码英文字符所占字节数:1
ISO8859-1编码中文字符所占字节数:1

BIG5编码英文字符所占字节数:1
BIG5编码中文字符所占字节数:2

ASCII编码英文字符所占字节数:1
ASCII编码中文字符所占字节数:1


Linux默认可以存放100个进程,放1个跟99个是一样的,其他均为sleep状态,意思是已经开辟这么大内存,用还是不用,反正都在那里放着,不会浪费CPU时间,因此后台进程只要不是说一直处于活动状态,跟IOS一样,无需杀死后台进程。

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); // 对象是否是同一个
System.out.println(str1.equals(str2)); // 字符串的话是可以直接相等,跟int等其他数据类型一样


String strObj1 = new String("hello");
String strObj2 = new String("hello");
System.out.println(strObj1 == strObj2);// 是否是同一个对象
System.out.println(strObj1.equals(strObj2));// 对象相等,因为char全相等


执行结果:

true
true
false
true

分析:拿String作为例子,其他容器类同样类似

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

首先判断是否是同一个对象,即两个引用指向同一个对象,那自然相等;

==判断两者是否是同一个对象,equals判断值是否相等。

其次比较是否是同一种类型,如不等则返回false,如相等则继续;

最后比较内部的值,String的char,List的value,Map的key和value,如果完全相等则相等。



目录
相关文章
|
12月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
1297 3
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
406 4
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
存储 弹性计算 缓存
阿里云服务器ECS经济型、通用算力、计算型、通用和内存型选购指南及使用场景分析
本文详细解析阿里云ECS服务器的经济型、通用算力型、计算型、通用型和内存型实例的区别及适用场景,涵盖性能特点、配置比例与实际应用,助你根据业务需求精准选型,提升资源利用率并降低成本。
876 3
|
11月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
423 4
|
11月前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
11月前
|
存储 Java Go
【Java】(3)8种基本数据类型的分析、数据类型转换规则、转义字符的列举
牢记类型转换规则在脑海中将编译和运行两个阶段分开,这是两个不同的阶段,不要弄混!
433 2
|
11月前
|
设计模式 缓存 Java
【JUC】(4)从JMM内存模型的角度来分析CAS并发性问题
本篇文章将从JMM内存模型的角度来分析CAS并发性问题; 内容包含:介绍JMM、CAS、balking犹豫模式、二次检查锁、指令重排问题
295 1
|
11月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
408 1
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
1133 17