前言

深入研究Java内存管理,将增强你对堆如何工作、引用类型和垃圾回收的认识。

你可能会思考,如果你使用Java编程,关于内存如何工作你需要了解哪些哪些信息?Java可以进行自动内存管理,而且有一个很好的、安静的垃圾回收器,它在后台工作,清理那些未使用的对象并释放一些内存。

因此,作为一名Java程序员,你不需要再为销毁无用对象这样的问题而烦恼了。但是,虽然这个过程在Java中是自动的,它也不能保证任何事情。由于不知道垃圾回收器和Java内存是如何设计的,有些对象即使你不再使用了,却也不符合垃圾回收的条件。

因此,了解Java中内存实际是如何工作的非常重要,因为它为你编写高性能和优化的应用程序提供了帮助,这些应用程序永远不会因内存不足而崩溃。另一方面,当你发现自己处于糟糕的境地时,你将能够很快发现内存的漏洞。

首先,让我们看看内存在Java中通常是如何组织的:

内存结构

通常,内存分为两大部分:堆栈和堆。请记住,内存类型在上图中的大小与实际内存大小不成比例。与堆栈相比,堆是一个巨大数量的内存。

堆栈

堆栈内存负责保存对堆对象的引用和存储值类型(在Java中也称为基元类型),值类型保存值本身而不保存对堆中对象的引用。

此外,堆栈上的变量具有一定的可见性,也称为作用域。只有活跃作用域内的对象才能被使用。例如,假设我们没有任何全局作用域变量(字段),只有局部变量,如果编译器执行方法的主体,它只能访问方法主体内堆栈中的对象。它不能访问其它局部变量,因为这些变量超出了作用域。一旦方法完成并返回,堆栈顶部就会溢出,活跃作用域也会发生变化。

或许你注意到了在上图中显示的多个堆栈内存,这是因为Java中的堆栈内存是按线程分配的。因此,每次一个线程被创建和启动时,它都有自己的堆栈内存,并且不能访问另一个线程的堆栈内存。

堆内存将实际对象存储在内存中。这些对象被堆栈中的变量引用。例如,让我们分析下面一行代码发生了什么:

“new”关键字负责确保堆上有足够的可用空间,在内存中创建一个StringBuilder类型的对象,并通过堆栈中的“builder”引用它。

每个正在运行的JVM进程只有一个堆内存。因此,无论运行多少线程,这都是内存中的一个共享部分。实际上,堆结构与上图中显示的略有不同。堆本身被分成几个部分,这有助于垃圾回收进程。

最大堆栈和堆大小都没有预定义 - 这取决于正在运行的计算机。 然而,在后文中,我们将研究一些JVM配置,这些配置允许我们为正在运行的应用程序明确设定它们的大小

引用类型

如果仔细观察内存结构图片,你或许会注意到,代表对堆中对象引用的箭头的样式实际是不同的。这是因为,在Java编程语言中,我们有不同类型的引用:强引用、弱引用、软引用和虚引用。引用类型之间的区别在于它们所引用堆上的对象在不同的条件下可以被作为垃圾回收。让我们来仔细认识一下每一种引用类型。

1. 强引用>>>

这种引用类型是我们都习惯并且最受欢迎的引用类型。在上面的StringBuilder示例中,我们实际上使用了对堆中对象的强引用。当有一个强引用指向堆上的对象时,或者通过一系列强引用可以强访问该对象,则该对象不会被作为垃圾回收。

2. 弱引用>>

简单来说,在下一个垃圾回收进程之后,对堆中对象的弱引用很可能不会继续存在了。弱引用的创建示例如下:

弱引用的一个很好的用例是缓存方案。假设你检索了一些数据,并且还希望将其存储在内存中—这样同样的数据可以被再次请求。另一方面,你不确定何时或者是否会再次请求这些数据。因此,你可以保留对它的弱引用,万一垃圾回收器运行,它可能会破坏堆中的对象。因此,过了一会儿,如果你想要检索你引用的对象,你可能会突然得到一个空的返回值。缓存方案的一个很好的使用是回收WeakHashMap。如果我们在Java API中打开WeakHashMap类,我们会看到它的条目实际上扩展了WeakReference类,并使用它的引用字段作为映射的关键字:

private static class Entry extends WeakReference implements Map.Entry { V value;......}

一旦WeakHashMap中的一个关键字被进行了垃圾回收,整个条目就会从映射中移除。

3. 软引用>>>

这种引用类型用于对内存更敏感的方案,因为只有当应用程序内存不足时,所引用的对象才会被作为垃圾回收。因此,只要没有迫切需要释放出一些内存空间,垃圾回收器就不会去回收软引用的对象。Java保证在抛出OutOfMemoryError之前清除所有软引用的对象。Javadocs表明:“在虚拟机抛出OutOfMemoryError之前,所有对可软访问对象的软引用都会确保被清除。”

与弱引用类似,软引用的创建示例如下:

SoftReference reference = new SoftReference<>(new StringBuilder());......}

4. 虚引用>>>

用于算法检查后的清理操作,因为我们知道有些对象不需要再存在。仅与引用队列一起使用,因为此类引用的.get()方法将始终返回空值。这些引用类型被认为是优于终结器的。

如何引用字符串

Java中对字符串类型的处理略有不同。字符串是不可变的,这意味着每次使用字符串执行操作时,实际上都会在堆上创建另一个对象。对于字符串,Java在内存中进行字符串池管理。这意味着Java会尽可能地存储和重用字符串。对于字符串文字,更是这样。例如:

String localPrefix = "297"; //1String prefix = "297"; //2if (prefix == localPrefix){ System.out.println("Strings are equal" );}else{ System.out.println("Strings are different");}

运行时,将输出以下内容:

Strings are equal

因此,可以看出在比较了字符串类型的两个引用之后,它们实际上指向了堆中的相同对象。但是,这对于被计算的字符串无效。假设我们对上述代码的//1行进行以下更改

输出:

Strings are different

在这种情况下,我们实际上看到堆上有两个不同的对象。如果我们考虑到计算出的字符串会被经常使用,我们可以强制JVM通过在计算的字符串末尾添加.intern()方法将计算的字符串添加到字符串池当中:

进行上述更改后输出如下:

Strings are equal

垃圾回收进程

正如前面所讨论的,根据堆栈中的变量对堆中对象的引用类型,在某个确定的时间点,该对象符合垃圾回收器的条件。

符合垃圾回收的对象

比方说,所有红色的对象都符合被垃圾回收器的条件。 你可能会注意到堆上有一个对象,它对同一堆上的其它对象进行了强引用(例如,可能是引用了自己项的列表,或者是具有两个引用类型字段的对象)。但是,由于堆栈中的引用丢失,这个对象就无法再被访问,因此它也成了垃圾。

为了更深入地了解细节,我们先提出以下几点:

1.这个过程是由Java自动触发的,何时启动以及是否启动此过程取决于Java。

2.实际上这个进程是昂贵的。当垃圾回收器运行时,应用程序中的所有线程都会暂停(取决于GC类型,稍后将对此进行讨论)。

3.这实际上是一个比垃圾回收和释放内存更复杂的进程。

尽管由Java决定何时运行垃圾回收器,你也可以直接调用System.gc( )并期望垃圾回收器在执行这行代码时运行,对吧?

这是一个错误的假设。

你只需要让Java运行垃圾回收器,但是是否运行垃圾回收器仍然取决于Java。无论如何,不建议直接调用System.gc( )。

由于这是一个非常复杂的过程,并且它可能会影响你程序的表现,它需要以一个智能的方式实现。 一个被称作“标记和扫描”的进程来完成此任务。Java分析堆栈中的变量并“标记”所有保持活跃的对象,然后清除所有不会使用的对象。

实际上,Java并没有回收任何垃圾。事实上,垃圾越多,标记为活跃的对象就越少,进程也就越快。为了使这个进程更加优化,堆内存实际由多个部分组成。我们可以通过JVisualVM(Java JDK附带的工具)可视化内存使用情况和其它一些有用的东西。您唯一需要做的就是安装一个名为Visual GC的插件,它允许您查看内存的实际结构。让我们放大一点,分解大局:

堆内存的衍生

当一个对象被创建时,它被分配到Eden(1)区。因为Eden区的空间没有那么大,它很快就满了。垃圾回收器在Eden区运行,并标记出活跃的对象。

一旦一个对象在一次垃圾回收进程中存活,它就会被移动到所谓的幸存者区S0(2)中。 垃圾器第回收二次在Eden区上运行时,它会将所有幸存的对象移动到S1(3)区中。此外,当前在S0(2)区上的所有内容都将被移动到S1(3)区中。

如果一个对象在X轮垃圾回收中存活了下来(取决于JVM的实现,在我的例子中是8轮),那么它很可能会永远存活下来,并被移入到Old(4)区。

结合目前为止所说的一切,如果你看一下图中标号(6)的垃圾回收器,它每次运行时,你都可以看到对象切换到幸存者空间,并且Eden区的空间增大了。如此反复。老一代也可以被作为垃圾回收,但由于它在内存中空间是比Eden区更大的部分,因此这种情况不会经常发生。Metaspace(5)用于在JVM中存储已加载类的元数据。

所呈现的图片实际上是一个Java 8的应用程序。在Java 8之前的版本,内存的结构有点不同。元空间实际上称为PermGen. 区。例如,在Java 6中,此空间还为字符串池存储了内存。因此,如果Java 6应用程序中有太多字符串,则它可能会崩溃。

垃圾回收器类型

实际上,JVM有三种类型的垃圾回收器,程序员可以选择应该使用哪种垃圾回收器。默认情况下,Java根据底层硬件选择要使用的垃圾回收器类型。

1.串行垃圾回收器 - 一个单线程回收器。 主要适用于数据使用量较小的小型应用程序。 可以通过指定命令行选项来启用:-XX:+ UseSerialGC

2.并行垃圾回收器 - 从命名可以看出,串行垃圾回收器和并行垃圾回收器之间的区别在于并行垃圾回收器使用多个线程来执行垃圾回收进行。并行垃圾回收器也被称作吞吐量回收器。可以通过直接指定选项来启用它:-XX:+ UseParallelGC

3.主要并发标记垃圾回收器 - 如果你还记得,在本文前面提到垃圾回收过程实际上相当昂贵,并且当它运行时,所有线程都被暂停。但是,我们有这种大多数并发GC类型,它声明它与应用程序并发工作。但是,它有“大多数”并发的原因。它不能100%同时应用于应用程序。线程暂停一段时间。尽管如此,暂停时间尽可能短,以实现最佳的GC性能。实际上,有两种类型的大多数并发GC:

3.1垃圾优先 - 应用程序合理暂停时间内的高吞吐量。 通过以下选项启用:-XX:+ UseG1GC

3.2并发标记扫描 - 应用程序暂停时间保持最短。可以通过指定选项来启用:-XX:+ UseConcMarkSweepGC。从JDK 9开始,这个垃圾回收器类型不推荐使用。。

提示和技巧

1.为了最小化内存的占用,请尽可能限制变量的作用域。请记住,每次堆栈中的顶级作用域溢出时,来自该作用域的引用都会丢失,这可能会导致相应的对象被作为垃圾回收。

2.直接对空的、废弃对象的引用,这会导致被引用的对象被作为垃圾回收。

3.避免成为终结者。 它们放慢了进程,不保证任何事情, 更喜欢进行对虚引用的清理工作。

4.当弱引用或软引用适用时,请不要使用强引用。最常见的内存缺陷是缓存方案,即使数据可能不需要,也会被保存在内存中。

5.JVisualVM还具有在某一点时间点进行堆转储的功能,因此你可以分析每一类所占用的内存量。

6.根据你的应用程序需求来配置JVM。运行应用程序时,明确指定JVM的堆大小。内存分配进程是宝贵的,因此要为堆分配一个合理的初始最大内存空间。如果你知道一开始使用较小的初始堆空间是没有意义的,JVM将扩展这个内存空间。 根据以下命令来明确内存空间:

(1)初始堆大小 -Xms512m 将初始堆大小设置为512 mb。

(2)最大堆大小 -Xmx1024m 将最大堆大小设置为1024 mb。

(3)线程堆栈大小 -Xss128m 将线程堆栈大小设置为128mb。

(4)新生代堆大小 -Xmn256m 将新生代堆大小设置为256mb。

7.如果Java应用程序崩溃并出现OutOfMemoryError,你需要一些额外的信息来检测漏洞,运行以下进程:-XX:HeapDumpOnOutOfMemory,它将在下次发生此错误时创建堆转储文件。

8.使用-verbose:gc选项获取垃圾回收输出。 每次进行垃圾回收时,都会生成一个输出

总结

从内存资源的角度看,了解内存是如何组织的,会为你编写良好、优化的代码提供优势。这样做的好处是,你可以通过提供最适合你所运行应用程序的不同配置,来优化你正在运行的JVM。如果使用正确的工具,发现和修复内存漏洞只是一件容易的事情。

Java虚拟机内存结构讲解视频

Java内存模型讲解视频

最后

欢迎大家一起交流,喜欢文章记得关注点赞转发哟,感谢支持!

java split空值也保留_Java内存大家都知道,但你知道要怎么管理Java内存吗?相关推荐

  1. java split 字符串作为分隔符_Java 字符串分隔 split

    Java中的我们可以利用 split 方法(Java.lang.string.split)把字符串按照指定的分割符进行分割,然后返回字符串数组,下面是string.split的用法实例及注意事项. s ...

  2. java split函数的用法_java中split函数用法以及注意事项

    java中split函数用法以及注意事项 发布时间:2020-04-23 10:28:23 来源:亿速云 阅读:215 作者:小新 本篇文章和大家了解一下java中split函数用法以及注意事项.有一 ...

  3. 编写Java程序 堆栈的接口_java - 错误:调用实现接口的通用方法时,java.lang.AbstractMethodError - 堆栈内存溢出...

    我正在尝试使用javassist以编程方式创建和编译实现接口的类(在运行时). 每当我调用该动态类的实例时,都会收到以下错误消息: java.lang.AbstractMethodError: Foo ...

  4. java图片强绘制表情符号_java - 具有表情符号的Graphics2D.drawString()无法正常工作 - 堆栈内存溢出...

    如果我将代码打包为.jar而不是仅使用classpath,则无法正常工作(就像我在IDE中按"播放"一样) 当我使用IDE时,可以使用g2.drawString (使用Apple ...

  5. java上传ftp数据丢失_Java:将文件上传到FTP问题(数据包丢失) - java

    我正在尝试将文件从Java应用程序传输到FTP服务器 该程序可以正常工作,文件已传输,但是当我在FTO文件夹中打开文件时,文件已损坏,我认为在文件传输过程中数据包丢失了.为什么?我该如何解决? 另一个 ...

  6. java spring 服务器关闭连接_java springboot websocket 服务 服务器主动关闭连接 导致 抛出java.io.EOFException异常...

    遇到这个问题不要慌,去查查众说纷纭.那我就在这里总结一下吧 存在此问题的有以下几种情况 1.ws连接不稳定经常断线: 答: 1)可能是客户和服务器之间的网络问题 2)可能是服务端内存不够用导致线程被异 ...

  7. java集成开发工具项目_Java项目开发(一)-不借助集成工具创建Java项目并编写编译执行脚本...

    java-project(项目根目录)|--src| |--main(主目录)| | |--java(存放项目的.java文件)| | | |--com(包目录)| | | | |--mycompan ...

  8. java面试题集 代码_java面试题集1.txt 源代码在线查看 - 常见的JAva面试试题 资源下载 虫虫电子下载站...

    63.抽象类与接口? 答:抽象类与接口都用于抽象,但是抽象类(JAVA中)可以有自己的部分实现,而接口则完全是一个标识(同时有多重继承的功能).编程题:1.现在输入n个数字,以逗号,分开:然后可选择升 ...

  9. java alt是什么意思_java中这都是什么意思? - 执着的笨蛋 - BlogJava

    从JDK5.0开始出现的泛型(Generics)功能.泛型提供编译时期的检查,不会将对象置于某个容器而失去其类型. http://www.onjava.com/pub/a/onjava/excerpt ...

最新文章

  1. 【如何管理开机自启动程序】
  2. IE下实现全屏两方法
  3. python语言入门与精通-终于懂得python从入门到精通教程
  4. 安装asterisk 时遇到的报错情况,及解决办法。
  5. 基于k8s多集群隔离环境下的devops实现
  6. 二鸟在林不如一鸟在手
  7. springboot+flowable第四节(设置监听事件)
  8. 用python写三角形_python写个三角形的问题
  9. python是什么语言-终于明白python语言的特点是什么
  10. Anroid开发中常用快捷键
  11. 如何使用SQL Server游标
  12. 北斗高精度定位在民航机场的创新应用
  13. 学生评语管理系统软件测试,学校教师老师综合评价评分系统软件
  14. 大众点评文字反爬破解
  15. 郭盛华技术有多牛?外媒:稳坐亚洲第一
  16. methodsignature java_Java MethodSignature.getMethod方法代碼示例
  17. 大数据如何赋能产品—用户特征分析
  18. 基于S变换的电压暂降检测研究及仿真设计
  19. SQL Server 2008 R2 企业版/开发版/标准版
  20. excel表计算机实践操作,Excel电子表格计算机实践任务书

热门文章

  1. 清华大学 TUNA 协会
  2. Java Spring-事务管理
  3. [设计模式] 23 访问者模式 visitor Pattern
  4. [转载]jQuery1.6.1源码分析系列
  5. Linux桌面虚拟化技术KVM
  6. 史上最高效Shell四剑客实操案例
  7. linux查看具体进程占用的网络流量
  8. UUID 查看linux的UUID 与 SVN 工程的 UUID。(两者之间没有联系)
  9. java中,数值计算时的类型转换 ( 两个int类型相加,赋值给double )
  10. php asort,PHP asort() 函数