java non-heap_成都汇智动力-Java内存相关

原标题:成都汇智动力-Java内存相关

Java内存模型

可见性:一个线程对共享变量值的修改,能够及时地被其他线程看到。语言层面上支持两种 synchronized 和 volatile。

共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量

Java 内存模型 (JMM) 描述了 Java 程序中各种变量 (线程共享变量) 的访问规则,以及在 JVM 中将变量存储到内存中和从内存中读取变量这样的底层细节。

所有的变量都存储在主内存中;

每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本 (主内存中该变量的一份拷贝)。

两个限制

线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写;

不同线程之间无法直接访问其他线程工作内存中的变量,线程变量值得传递需要通过主内存来完成。

**synchronized 实现可见性 **JMM 关于 synchronized 的两条规定:

线程解锁前,必须把共享变量的最新值刷新到主内存中

线程加锁前,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新值 (加锁、解锁为同一把锁)

所以线程执行互斥代码过程为:获得互斥锁,清空工作内存,从主内存拷贝变量的最新值到工作内存,执行代码,最后将更改后的共享变量值刷新到主内存,释放互斥锁。

volatile 实现可见性 volatile 通过加入内存屏障 (一条特殊的指令) 和禁止重排序优化来实现内存可见性。对 volatile 变量执行写操作是,会在写操作后加入一条 store 屏障指令;执行读操作时,会在读操作前加入一条 load 屏障指令。 线程写 volatile 变量的过程是改变线程工作内存中 volatile 变量副本的值,将改变后的副本的值从工作内存刷新到主内存;

线程读 volatile 变量的过程是从主内存读取 volatile 变量的最新值到线程的工作内存中,从工作内存中读取 volatile 变量的副本。

volatile 不能保证原子性!!!所以在多线程中安全使用 volatile 变量,必须同时满足:

对变量的写入操作不依赖其当前值,所以 number++、count *= 5 不满足,而 boolean 变量、记录温度变化的变量满足

该变量不包含在具有其他变量的不变式中,所以 low < up 不满足。

指令重排序 代码书写的顺序与实际执行的顺序不同,这主要是编译器或处理器为了提高程序性能而做的优化。主要有编译器优化重排序、指令级并行重排序和内存系统重排序。

as-if-serial 无论如何重排序,程序执行结果应该与代码顺序执行结果一致。Java 编译器、处理器都会保证 Java 在单线程下遵循这一规定。

Java 内存管理

JDK、JRE、JVM的区别和联系

JDK Java Development ToolKit (Java 开发工具包)。JDK 是整个 Java 的核心,包括了Java 运行环境(Java Runtime Envirnment),一堆 Java 工具 (javac\/java\/jdb 等) 和 Java 基础的类库 (即 Java API 包括 rt.jar)。

JRE Java Runtime Enviromental (Java 运行时环境)。也就是我们说的 Java 平台,所有的 Java 程序都要在 JRE 下才能运行。包括JVM 和 Java 核心类库和支持文件。与 JDK 相比,它不包含开发工具——编译器、调试器和其它工具。

**JVM **Java Virtual Mechinal (Java 虚拟机)。JVM 是 JRE 的一部分,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM 有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 的主要工作是解释自己的指令集(即字节码)并映射到本地的 CPU 的指令集或 OS 的系统调用。Java 语言是跨平台运行的,其实就是不同的操作系统,使用不同的 JVM 映射规则,让其与操作系统无关,完成了跨平台性。JVM 对上层的 Java 源文件是不关心的,它关注的只是由源文件生成的类文件(class file)。类文件的组成包括 JVM 指令集,符号表以及一些补助信息。

da412e79f72b2608c4787b223d8fd54c.gif

Java虚拟机的内存组成以及堆内存介绍

Java 内存组成介绍

按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在 JVM 中堆之外的内存称为非堆内存 (Non-heap memory)”。可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是 Java 代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,所以方法区、JVM 内部处理或优化所需的内存(如 JIT 编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。

437c228b9c783aa3cdf59daa6b17a3b8.png

JVM 内存区域模型

程序计数器 (PC Register) 是最小的一块内存区域,它的作用是当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。

虚拟机栈 (JVM Stack) 描述的是 Java 方法执行的内存模型:每个方法被执行的时候都会创建一个“栈帧”用于存储局部变量表(包括参数)、操作栈、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。声明周期与线程相同,是线程私有的。局部变量表存放了编译器可知的各种基本数据类型 (boolean、byte、char、short、int、float、long、double)、对象引用(引用指针,并非对象本身),其中 64 位长度的 long 和 double 类型的数据会占用 2 个局部变量的空间,其余数据类型只占 1 个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部变量表的大小空间。

本地方法栈 (Native Method Stack) 与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的 Java 方法服务,而本地方法栈则是为 Native 方法服务。

堆 (Heap) 也叫做 Java 堆、GC 堆是 Java 虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,在 JVM 启动时创建。该内存区域存放了对象实例及数组(所有 new 的对象)。其大小通过-Xms(最小值)和-Xmx(最大值)参数设置,-Xms为JVM 启动时申请的最小内存,默认为操作系统物理内存的 1\/64 但小于 1G,-Xmx为 JVM 可申请的最大内存,默认为物理内存的 1\/4 但小于 1G,默认当空余堆内存小于 40% 时,JVM 会增大 Heap 到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于 70% 时,JVM 会减小 heap 的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,对于运行系统,为避免在运行时频繁调整 Heap 的大小,通常-Xms与-Xmx的值设成一样。由于现在收集器都是采用分代收集算法,堆被划分为新生代和老年代。新生代主要存储新创建的对象和尚未进入老年代的对象。老年代存储经过多次新生代 GC(Minor GC) 仍然存活的对象。

方法区 (Method Area) 也称”永久代” 、“非堆”, 它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。运行时常量池是方法区的一部分,Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

795255f683bc17217b8e87d8bc864785.png

新生代与旧生代:

新生代: 程序新创建的对象都是从新生代分配内存,新生代由 Eden Space 和两块相同大小的 Survivor Space (通常又称 S0 和 S1 或 From 和 To) 构成,可通过-Xmn参数来指定新生代的大小,也可以通过-XX:SurvivorRation来调整 Eden Space 及 Survivor Space 的大小。

旧生代: 用于存放经过多次新生代 GC 仍然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:①.大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0) 来代表超过多大时就不在新生代分配,而是直接在老年代分配。②.大的数组对象,切数组中无引用外部对象。 老年代所占的内存大小为-Xmx对应的值减去-Xmn对应的值。

持久代:在 Sun 的 JVM 中就是方法区的意思,尽管有些JVM大多没有这一代。主要存放常量及类的一些信息默认最小值为 16MB,最大值为 64MB,可通过 -XX:PermSize 及 -XX:MaxPermSize 来设置最小值和最大值。

Java 堆内存的 10 个要点

Java 堆内存是操作系统分配给 JVM 的内存的一部分。 当我们创建对象时,它们存储在 Java 堆内存中。 为了便于垃圾回收,Java 堆空间分成三个区域,分别叫作 New Generation, Old Generation 或叫作 Tenured Generation,还有 Perm Space。 你可以通过用 JVM 的命令行选项 -Xms, -Xmx, -Xmn 来调整 Java 堆空间的大小。不要忘了在大小后面加上”M”或者”G”来表示单位。举个例子,你可以用 -Xmx256m来设置堆内存最大的大小为 256MB。 你可以用 JConsole 或者 Runtime.maxMemory(), Runtime.totalMemory(), Runtime.freeMemory()来查看 Java 中堆内存的大小。 你可以使用命令“jmap”来获得 heap dump,用“jhat”来分析 heap dump。 Java 堆空间不同于栈空间,栈空间是用来储存调用栈和局部变量的。 Java 垃圾回收器是用来将死掉的对象(不再使用的对象)所占用的内存回收回来,再释放到 Java 堆空间中。 当你遇到 java.lang.outOfMemoryError 时,不要紧张,有时候仅仅增加堆空间就可以了,但如果经常出现的话,就要看看 Java 程序中是不是存在内存泄露了。 请使用 Profiler 和 Heap dump 分析工具来查看 Java 堆空间,可以查看给每个对象分配了多少内存。

JVM 中堆和栈的区别

JVM中的堆

Java Runtime 使用Heap为Object分配内存。所有的对象,无论是何时何地创建的,都保存在 Heap 中。垃圾回收 (Garbage Collection) 在 Heap 上运行,释放不被引用的 Object。Heap 中生存的 Object 能在程序的任何地方被引用。

JVM中的栈

Stack memory 是为执行的 thread 分配的,包含一些生存时间短的值和指向 Heap 中对象的引用。Stack Memory 总是 LIFO的。当调用一个 method 时,Stack Memory 会为它分配一块区域,用来存储本地的 primitive value 和对 Object 的引用。一旦这个 method结束,这块区域将变得不可用,下一次 method 调用时又可以使用它。

相比 Heap,Stack 要小得多。

区别

存储内容:栈存放局部变量以及引用,堆存放所有对象。 被谁占有:堆被整个程序共享,栈中的对象被所有线程可见;栈属于单个线程,存储的变量只在其所属的线程中可见。 空间管理:Stack 内存满足 LIFO,但 Heap 就复杂多了。Heap 被分为 Young Generation, Old Generation, Permanent Generation,在它基础上会运行垃圾回收机制。 生存时间:Stack Memory 伴随调用它的 method 存在、消失,而 Heap Memory 从程序的开始一直存活到终止。 体积大小:Stack Memory 体积远小于 Heap Memory。由于 Stack 用 LIFO 调度,它的访问速度也快得多。可以用-Xms或者-Xmx定义 Heap 的初始大小,用-Xss定义 Stack 的初始大小。 异常错误:当 Stack 满了,Java Runtime 会抛出 java.lang.StackOverFlowError。当 Heap 满了,会抛出 java.lang.OutOfMemoryError: Java Heap Space Error。返回搜狐,查看更多

责任编辑:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值