JAVA之自动内存管理机制

一、内存分配
1.JVM体系结构
2.运行时数据区域
3.内存分配
二、内存回收
1.垃圾收集算法
2.垃圾收集器
三、相关参考
一、内存分配
JVM体系结构

在了解自动内存管理的内存分配之前,我们先看下JVM的体系结构。代码编译的结果是从本地机器码转变为字节码,经过类加载器加载到虚拟机后才能执行程序。JVM的体系结构主要如下图所示:

JVM体系结构

运行时数据区域

在上图中我们可以清楚地看到,JVM在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域,分别是程序计数器、Java虚拟机栈、本地方法栈、方法区(包括运行时常量池)、堆。下面逐一介绍。

程序计数器(Program Counter Register)
线程私有,不会出现OOM,是一块较小的内存空间,作用可以看作是当前线程所执行的字节码的行号指示器,因为JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各条线程间的计数器互不影响,独立存储。

Java虚拟机栈(Java Virtual Machine Stacks)
线程私有,每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈,虚拟机栈描述的是Java方法执行的内存模型,每调用一个方法,就会生成一个栈帧(Stack Frame)用于存储方法的本地变量表、操作栈、方法出口等信息,当这个方法执行完后,就会弹出相应的栈帧。这部分区域,如果请求的栈的深度过大,虚拟机可能会抛出StackOverflowError异常,如果虚拟机的实现中允许虚拟机栈动态扩展,当内存不足以扩展栈的时候,会抛OutOfMemoryError异常。

本地方法栈(Native Method Stack)
线程私有,本地方法栈与虚拟机栈类似,只是在执行本地方法时使用。


线程共享,几乎所有的对象实例以及数组都是在这个区域进行分配,这里也是垃圾回收的主要区域就是这里(还可能有方法区)。Java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可。从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。从内存回收的角度看,Java堆中可细分为新生代和老年代,再细致点有Eden空间、From Survivor空间、To Survivor空间等。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,就会抛出OOM异常。

堆内存

方法区
线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等信息。当方法区无法满足内存分配需求时,会抛出OOM异常。运行时常量池是方法区的一部分,主要用来存储编译时生成的字面量和符号引用。

下面这张运行时数据区域图可能更形象一点。

内存分配

大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够的空间进行分配时,虚拟机将发起一次新生代GC(Minor GC)。

在设置虚拟机参数的时候,-Xmx20M -Xms20M -Xmn10M -XX:SurvivorRatio=8的含义是-Xmx与-Xms相等限制了堆大小为20MB,-Xmn表示新生代大小为10MB,剩下的10MB分配给老年代,-XX:SurvivorRatio=8表示新生代中Eden区与一个Survivor区空间比例大小为8:1,所以Eden区大小为8192K,一个Survivor区大小为1024K,新生代总可用空间为9216K。

大对象直接进入老年代,大对象是指需要连续内存空间的Java对象,比如很长的字符串及数组。长期存活的对象将进入老年代。

有道题是这样的:

问题

这道题是自己在之前遇到过的,题目本身表达有点歧义,答案是堆和字符串常量池中,当new String("abc")时,其实会先在字符串常量区生成一个abc的对象,然后new String()时会在堆中分配空间,然后此时会把字符串常量区中abc复制一个给堆中的String,故abc应该在堆中和字符串常量区。

二、内存回收
对象是否存活?

垃圾收集器在对堆进行回收前,首先需要判断对象是否存活,即是否可能再被使用。这里就要提到一个引用计数算法,每当一个对象被引用时,计数器就加1,引用失效时就减1,当计数器为0时就不可能被使用了,这是一个简单的判断对象是否存活的算法,但是Java中并没有选用,主要是因为它很难解决对象间相互循环引用的问题。

Java中判断对象是否存活,使用的是根搜索算法,基本思路就是通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,这个对象就是不可用的。

Java中可以作为GC Roots对象的主要有以下几种:

1)虚拟机栈(栈帧中的本地变量表)中引用的对象
2)方法区中类静态属性引用的对象
3)方法区中常量引用的对象
4)Native方法中引用的对象

垃圾收集算法

在确定哪些内存需要回收后,接下来就是怎么回收的问题了,也就是垃圾收集算法,常用的几种如下:标记-清除算法、复制算法、标记-整理算法以及分代回收算法。

标记-清除算法
这是最基础的收集算法,首先标记出所有需要回收的对象,标记完成后统一回收掉所有被标记的对象。缺点有两个,一个是效率问题,标记和清除过程的效率不高,另一个是空间问题,标记清除后会产生大量的不连续的内存碎片。

复制算法
这个算法主要是为了解决效率问题,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当一块的内存用完时,将活着的对象复制到另外一块上面,然后把已经使用过的内存空间清理掉。优点是每次都是对其中一块内存进行回收,不用考虑内存碎片等情况,实现简单,运行高效,缺点是内存缩小为原来的一半。

标记-整理算法
复制收集算法在对象存活率较高时就要执行较多的复制操作,效率会变低,标记整理算法首先标记出所有需要回收的对象,之后让所有存活的对象都向一端移动,然后清理掉端边界以外的内存。

分代收集算法
根据对象的存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,根据各个年代的特点采用适当地收集算法。在新生代中,每次垃圾收集时都有大量对象死去,只有少量存活,就选用复制算法,老年代中因为对象存活率高、没有额外空间进行 分配担保,就采用标记-清理或者标记-整理回收。

垃圾收集器

垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现,下面介绍几种常见的垃圾收集器。

Serial收集器
最基本、历史最悠久的收集器,在JDK 1.3.1之前是新生代收集的唯一选择,它是一个单线程收集器,在工作时必须暂停其他所有的工作线程。

ParNew收集器
Serial收集器的多线程版本,其它方面基本与Serial一致。使用-XX:+UseParNewGC开关来控制使用ParNew+Serial Old收集器组合收集内存;使用-XX:ParallelGCThreads来设置执行内存回收的线程数。

Parallel Scavenge 收集器
吞吐量优先的垃圾回收器,作用在新生代,使用复制算法,关注CPU吞吐量,即运行用户代码的时间/总时间。其它收集器主要尽可能地缩短垃圾收集时用户线程的停顿时间,停顿时间短适合需要与用户交互的程序,而高吞吐量则可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

Serial Old收集器
Serial收集器的老年代版本,单线程收集器,使用标记-整理算法。

Parallel Old收集器
Parallel Scavenge 收集器的老年代版本,使用标记-整理算法,多线程,这个收集器是JDK 1.6才开始提供的,在此之前新生代的Parallel Scavenge收集器比较尴尬,只能与Serial Old搭配,性能有待提高,Parallel Old出现后与Parallel Scavenge搭配很不错。

CMS(Concurrent Mark Sweep)收集器
致力于获取最短回收停顿时间(即缩短垃圾回收的时间),使用标记清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。

G1(Garbage-First)收集器
当今收集器技术发展的最前沿成果之一,G1收集器的主要特点有:
1)并行与并发 能充分利用多CPU、多核环境的硬件优势缩短Stop-The-World停顿的时间
2)分代收集 可以不需要其他收集器配合就能独立管理整个GC堆
3)空间整合 整体上的基于“标记-整理”算法及局部上的基于“复制”算法意味着运作期间不会产生内存空间碎片
4)可预测的停顿
————————————————
版权声明:本文为CSDN博主「HelloWorld搬运工」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wufaliang003/article/details/102474366

原文地址:https://www.cnblogs.com/dim2046/p/12059157.html

时间: 2024-10-31 05:28:20

JAVA之自动内存管理机制的相关文章

JVM自动内存管理机制——Java内存区域

一.JVM运行时数据区域概述 Java相比较于C/C++的一个特点就是,在虚拟机自动内存管理机制的帮助下,我们不需要为每一个操作都写像C/C++一样的delete/free代码,所以也不容易出现内存泄漏和内存溢出的问题.显然,这里的不容易只是相对而言的,如果我们想要降低这种代码隐患的发生,就需要对Java虚拟机怎样使用内存有了解,这样的话就算产生错误,排查起来也会相对容易.下面我们来说一说JVM运行时数据区域 1.程序计数器(PC寄存器): 被看作是当前线程所执行的字节码的行号指示器,字节码解析

JVM自动内存管理机制——Java内存区域(下)

一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a)下面是一些简单的使用参数 其中最后一个是一个运行时参数设置的简单实例.一般-XX是系统级别的配置(日志信息,或者是配置使用什么样的垃圾回收器等等),后面跟上+表示启用.不是-XX基本上是对于应用层面的配置信息 下面是一个简单的实例:表示设置初始堆大小为5M,最大堆大小为20M,并将虚拟机的参数设置打印出来,后

2.1 自动内存管理机制--Java内存区域与内存溢出异常

自动内存管理机制 第二章.Java内存区域与内存溢出异常 [虚拟机中内存如何划分,以及哪部分区域.什么样代码和操作会导致内存溢出.各区域内存溢出的原因] 一.运行时数据区域 Java虚拟机所管理的内存包括以下几个运行时数据区域[虚拟机内存模型]: 1.程序计数器: 可以看作是当前线程所执行的字节码的行号指示器.在虚拟机中,字节码解释器工作时就是通过程序计数器的值来选择下一条需要执行的字节码指令.Java虚拟机中多线程是通过线程轮流切换并分配处理机执行时间的方式实现的,在任何一个确定的时刻,一个处

Java虚拟机一 内存管理机制

Java虚拟机的内存管理主要分两点:内存分配以及内存回收.· 一.内存分配图: 注: 所占区域的大小与实际的内存大小比例并无直接关系. 解读: 1.如图,分成两种颜色的内存区域,其中蓝色的是线程隔离的数据区,也就是说每一个线程都有自己的这么一个区域存放自己的数据,而青色区域则是线程共享的,里面的数据为所有线程共有,原则上都有权限访问. 2.程序计数器: 用途:用来给程序导航指路的.这个是一块较小的内存空间,可以看做是当前线程执行的字节码的行号指示器.理解就是虚拟机把java源代码编译成了字节码,

Java自动内存管理机制学习(一):Java内存区域与内存溢出异常

备注:本文引用自<深入理解Java虚拟机第二版> 2.1 运行时数据区域 Java虚拟机在执行Java程序的过程中把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁.如下图所示: 2.1.1 程序计数器 程序计数器是一块较小的内存空间,它是线程的私有内存,可以看作时当前线程所执行的字节码的行号指示器.在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去

java自动内存管理机制

java程序员把内存管理的工作交给虚拟机,一旦出现内存泄露或者溢出问题,如果不了解内存是怎样工作的,那么排查错误将是一件异常艰难的工作. java内存区域与内存溢出异常 java运行时数据区域划分: 线程隔离的 1.程序计数器(Program Counter Register) 当前线程执行代码的行号指示器,当线程切换并分配处理器执行时间,为了保证线程恢复到正确的执行位置,每个线程都有独立的计数器 2.虚拟机栈(VM Stack) 虚拟机栈描述的是java方法执行的内存模型:每个方式执行的同时会

深入理解JVM(二)自动内存管理机制

2.1 C.C++内存管理是由开发人员管理,而Java则交给了JVM进行自动管理 2.2 JVM运行时数据区:方法区.堆(运行时线程共享),虚拟机栈.本地方法栈.程序计数器(运行时线程隔离,私有) 2.2.1 程序计数器(Program Counter Register):每一个线程都独有一个程序计数器,并且分配了一块线程私有的小块内存,程序运行时,这个计数器会记录字节码文件运行的行数,当线程切换时,则通过这个行数继续执行下面的操作 2.2.2 虚拟机栈(Java Virtual Machine

自动内存管理机制

一:Java内存区域与内存溢出异常 在运行Java程序时,Java虚拟机会把管理的内存划分为若干个不同的数据区域. Java虚拟机运行时数据区 数据区域图中,除了方法区和堆区是线程共享区外,其他三个是线程隔离的数据区(private) 程序计数器(Program Counter Register):属于线程私有的,占用的内存空间较少,可以看成是当前线程所执行字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选择下一条,需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基

自动内存管理机制-运行时数据区

java运行时数据区域分为:1. 程序计数器:程序计数器占据的内存空间较小,是当前运行线程执行的字节码的计数:分支.循环.跳转.异常处理.线程恢复等都要依赖技术器来对执行的字节码进行执行位置的计算来实现的.程序计数器的内存空间是每条线程独有的,也称之为"线程私有"的内存:计数器记录的是正在运行的字节码指令的地址,而如果是Native方法(本地方法),则计数器的值为空(Undefined).此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域.