JVM学习——内存空间(学习过程)

JVM——内存空间

关于内存的内容,内存的划分。JVM1.7 -> 1.8的变化比较大

JVM指令执行的时候,是基于栈的操作。每一个方法执行的时候,都会有一个属于自己的栈帧的数据结构。栈的深度,在编译为Class文件的时候,就已经能够确定好了。还能够看到栈的最大深度。

要涉及的知识点(学习路线):

JVM包含的内存空间布局

  1. 虚拟机栈:Stack Frame 栈帧。引用本身是一个变量,在虚拟机栈中存储。
  2. 程式计数器(Program Counter)。
  3. 本地方法栈:native,主要用于执行本地方法。
  4. 堆(heap):最大的空间-共享区域。存放一个个的对象实例。与堆相关的一个重要概念是垃圾收集器。现在几乎所有的垃圾收集器都是采用分代收集算法。所以堆空间也基于这一点进行了相应的划分:新生代与老年代。Eden空间,From Survivor空间与To Survivor空间。
  5. 方法区(method Area):存储元信息。永久代(Permanent Generation)从JDK1.8开始,已经彻底废弃了永久代。使用元空间(meta space)来替代。
  6. 运行时的常量池:方法区的一部分。
  7. 直接内存:Direct Memory。不是JVM管理的,是由计算机或者硬件来操作的。JVM只是能够申请一片内存区使用。与Java NIO密切相关。JVM通过堆上的DirectByteBuffer来操作直接内存。

Java对象内存分配原理与布局

一个对象由两部分组成:数据,元数据。(不在同一个地方)。元数据是存在堆的方法区的。

对象的两种实现方式

image-20200217210728517

  1. 指针指向 两个都是指针的
  2. 指针指向 一个指针的

堆的压缩:涉及到对象的移动。如果对象发生移动,地址就会变了。如果采用上面第一次,就会经常发生变化。如果用第二种,第二种就算位置变,也只是引用的位置变。所以Oracle的HotSpot采用的是第二种实现方式。

image-20200217210952728

  • 程序计数器和栈 都是线程私有的。
  • 如果是8个基本类型的值,是直接放在栈里面的。
  • 引用是放在栈里面的。生成的对象是方法
  • 本地方法栈:主要是跟native方法有关的。 和虚拟机栈没什么本质区别。
  • 堆是主要的内存空间。堆内存的空间是共享的。
  • 老年代和新年待。老年代的回收频率比新生代频率低的很多。
  • java的堆空间,在物理空间上,既可以是连续的。也可以是不连续的
  • 方法区:不是存放方法的区域。主要存放的是元信息。元信息和永久代不是一个概念。永久代永远不会被回收掉。元信息也是一般情况下,不会被收集的。这也是容易混淆的原因。但是比如Class的元信息。在Class被卸载的时候,元信息也是会被回收的。

关于Java对象创建的过程:

new 关键字创建对象的三个步骤:

  1. 在堆内存中创建出对象实例
  2. 为对象的成员变量赋初始值
  3. 将对象的引用返回
  • 指针碰撞(前提是堆中的空间通过一个指针进行分割,一侧是已经被占用的空间,另一侧是未被占用的空间)
  • 空闲列表(前提是堆内存空间中已被使用与未被使用的空间是交织在一起的,这时,虚拟机就需要通过一个列表来记录哪些空间是可使用的,哪些空间是已经被使用的,接下来找出可以容纳下新创建对象的且未被使用的空间,在此空间存放该对象。同时还要修改列表上的记录)
  • 对象在内存中的布局
    • 对象头
    • 实例数据(即我们在一个类中所声明的各项信息)
    • 对其填充(可选)
  • 引用访问对象的方式:
    1. 使用句柄的方式。
    2. 使用直接指针的方式。

堆内存溢出异常

运行此程序,内存溢出:(此时电脑会起飞)

package com.dawa.jvm.memory;

import java.util.ArrayList;
import java.util.List;

public class MyTest {
    public static void main(String[] args) {
        List<MyTest> list = new ArrayList<>();

        for (; ; ) {
            list.add(new MyTest());
        }
    }
}

使用虚拟机参数设定整个JVM对空间的大小,并且溢出的时候,打印到硬盘上(转储)。

参数:-Xms5m -Xmx5m -XX:+HeapDumpOnOutOfMemoryError

image-20200218165638081

溢出异常

image-20200218171015703

使用jvisualVM工具:(我本地Mac打不开,可能跟安装的版本有关) 还有个 Jconsole

Last login: Tue Feb 18 17:25:06 on ttys000
➜  ~ jvisualvm 
Unable to locate an executable at "/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/bin/jvisualvm" (-1)
➜  ~ 

image-20200218172712586

image-20200218172756599

image-20200218172830687

当我们内存溢出的时候,我们可以很好的借助这种工具来判断是哪个类运行的多,哪个类浪费了空间。

接下来,我们加一行 System.gc()

image-20200218173212181

再次运行的时候:内存不会溢出

image-20200218173808779

内存没满

并且垃圾回收活动很频繁

堆空间大小:一直保持在2m左右

还有一个工具:Jconsole(如下图)

image-20200218175118382

虚拟机栈溢出异常

虚拟机栈溢出案例

使用递归,并且设置虚拟机栈的大小:

image-20200218175756654

设置虚拟机栈大小的虚拟机参数:-Xss100k

栈大小最少需要160K。

image-20200218180031154

设置为160k

image-20200218180111324

所以:运行之后就直接栈溢出。Stack Overflow

image-20200218180139878

image-20200218181543093

点击右上角的Dump。抓取一些线程的信息

image-20200218181607054

看看就行了。

这些工具就在这里,一些人不知道,所以没用过。如果有需要,就拿去用。

JConsole 工具的使用

image-20200218181948462

还提供了 检测死锁的功能。

image-20200218182203403

如何用代码写出死锁,检测死锁和分析工具的深度解析

死锁:产生的条件:两个线程,互相持有相应的锁,对象去访问

package com.dawa.jvm.memory;

public class MyTest3 {
    public static void main(String[] args) {
        new Thread(A::method,"Thread-A").start();
        new Thread(B::method,"Thread-A").start();
    }
}
//方法的锁,是所在类的锁.
class A{
    public static synchronized void method(){
        System.out.println("method from A");
        try {
            Thread.sleep(5000);
            B.method();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
class B{
    public static synchronized void method(){
        System.out.println("method from B");
        A.method();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

image-20200218194620929

image-20200218194451792

JVisualVM软件更好使,进来之后,直接提示线程死锁。(Dump一下)

image-20200218194911047

image-20200218194940860

准备锁定的和已经锁定的。AB线程相互相反

image-20200218195113617

over,合理利用工具。

方法区产生内存溢出异常

采用手段

  1. 显示设定元空间的大小。让它不会自动扩展

  2. 因为存放的都是数据的元信息。采取特殊手段进行(运行期动态生成类 如CGlib)

  3. 方法区产生内存溢出异常.
    在运行期间不断的生成新的对象,元数据会不断的放入元空间.
    元空间默认占用内存21兆.如过不修改配置,会自动扩容(扩容到物理内存大小)
    
  4. 创建一个程序,不断的创建子类。元信息会不断的放入元空间当中。

  5. 虚拟机参数设置:

    ![image-20200218200626828](/Users/shangyifeng/Library/Application Support/typora-user-images/image-20200218200626828.png)

  6. 编写程序:

    image-20200218200508824

查看结果: Metaspace(1.7版本之前,是老年代。所以看网上文字的时候,注意时效性和带着批判的眼光去学习)

image-20200218200719744

使用Jconsole 连接此线程

image-20200218200934605

如果勾选,详细输出,那么控制台也会打印很多东西:

image-20200218201126530

通过CGlib 在运行过程中生成的类:

image-20200218201216049

使用JVisualVM来查看 元空间的变化呈现上升趋势

image-20200218201348523

当到达200M的上线的时候,程序退出

image-20200218201451402

那么,元空间到底是什么?

https://www.infoq.cn/article/Java-PERMGEN-Removed/

通过这篇文章,对其理解。可以理清JDK8中去除永久代的重要事实。

JMap 和Jstat和JCMD的使用

JDK提供的一些命令行工具。

jmap

➜  ~ jmap
Usage:
    jmap [option] <pid>
        (to connect to running process)
    jmap [option] <executable <core>
        (to connect to a core file)
    jmap [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server)

where <option> is one of:
    <none>               to print same info as Solaris pmap
    -heap                to print java heap summary
    -histo[:live]        to print histogram of java object heap; if the "live"
                         suboption is specified, only count live objects
    -clstats             to print class loader statistics
    -finalizerinfo       to print information on objects awaiting finalization
    -dump:<dump-options> to dump java heap in hprof binary format
                         dump-options:
                           live         dump only live objects; if not specified,
                                        all objects in the heap are dumped.
                           format=b     binary format
                           file=<file>  dump heap to <file>
                         Example: jmap -dump:live,format=b,file=heap.bin <pid>
    -F                   force. Use with -dump:<dump-options> <pid> or -histo
                         to force a heap dump or histogram when <pid> does not
                         respond. The "live" suboption is not supported
                         in this mode.
    -h | -help           to print this help message
    -J<flag>             to pass <flag> directly to the runtime system
➜  ~

列出所有的进程,找到端口号

ps -ef | grep java

➜  ~ ps -ef | grep java
  501  4619  1183   0  7:44下午 ??         0:36.29 /Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/bin/java -XX:MaxMetaspaceSize=256m -XX:+HeapDumpOnOutOfMemoryError -Xmx512m -Dfile.encoding=UTF-8 -Duser.country=CN -Duser.language=zh -Duser.variant -cp /Users/shangyifeng/.gradle/wrapper/dists/gradle-5.2.1-all/bviwmvmbexq6idcscbicws5me/gradle-5.2.1/lib/gradle-launcher-5.2.1.jar org.gradle.launcher.daemon.bootstrap.GradleDaemon 5.2.1
  501  5368  4053   0  8:41下午 ttys000    0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn java
➜  ~

image-20200218204305829

直接通过Jmap获取当前线程的类加载器

jmap -heap 端口号:4619

image-20200218204757683

下一个工具:

jstat

➜  ~ jstat
invalid argument count
Usage: jstat -help|-options
       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

Definitions:
  <option>      An option reported by the -options option
  <vmid>        Virtual Machine Identifier. A vmid takes the following form:
                     <lvmid>[@<hostname>[:<port>]]
                Where <lvmid> is the local vm identifier for the target
                Java virtual machine, typically a process id; <hostname> is
                the name of the host running the target Java virtual machine;
                and <port> is the port number for the rmiregistry on the
                target host. See the jvmstat documentation for a more complete
                description of the Virtual Machine Identifier.
  <lines>       Number of samples between header lines.
  <interval>    Sampling interval. The following forms are allowed:
                    <n>["ms"|"s"]
                Where <n> is an integer and the suffix specifies the units as
                milliseconds("ms") or seconds("s"). The default units are "ms".
  <count>       Number of samples to take before terminating.
  -J<flag>      Pass <flag> directly to the runtime system.
➜  ~

image-20200218205120577

MC:Current Metaspace:当前元空间的大小

MU:Metaspace Utillization :已经被使用的元空间大小

查看当前系统的进程(通用的操作系统的命令)

ps -ef | grep java

JSP命令:查看当前操作系统中的所有进程

➜  ~ jps
5584 Jps
4619 GradleDaemon
1183

JCMD指令

➜  ~ jcmd
5602 sun.tools.jcmd.JCmd
4619 org.gradle.launcher.daemon.bootstrap.GradleDaemon 5.2.1
1183
➜  ~

从JDK1.7开始引入。

➜  ~ jcmd -help
Usage: jcmd <pid | main class> <command ...|PerfCounter.print|-f file>
   or: jcmd -l
   or: jcmd -h

  command must be a valid jcmd command for the selected jvm.
  Use the command "help" to see which commands are available.
  If the pid is 0, commands will be sent to all Java processes.
  The main class argument will be used to match (either partially
  or fully) the class used to start Java.
  If no options are given, lists Java processes (same as -p).

  PerfCounter.print display the counters exposed by this process
  -f  read and execute commands from the file
  -l  list JVM processes on the local machine
  -h  this help
➜  ~

JCMD(常用命令)详解

  1. 查看JVM运行参数 jcmd pid VM.flags

    image-20200218210213471

  2. 列出当前进程,我们能对此进程的操作 jcmd pid help

    image-20200218210628687

  3. 查看当前某个进程的具体命令的具体参数。 jcmd pid JFR.dump

    image-20200218211022283

  4. 列出JVM xing能相关的一些参数jcmd pid PerfCounter.print

    image-20200218211147881

  5. 查看JVM的启动时长jcmd pid VM.uptime

    image-20200218211431934

  6. 查看系统中类的统计信息 jcmd pid GC.class_historgram

    image-20200218211452501

  7. 查看当前线程的堆栈信息 Thred.print

  8. 导出hprof文件,导出的文件可以通过JvisualVM查看。dump jcmd pid GC.heap_dump [filepath]

    image-20200218212008935

  9. 查看VM的信息。 jcmd pid VM.system_properties

    image-20200218212413207

  10. 查看目标JVM进程的版本信息。jcmd pid VM.version

    image-20200218212617064

  11. 查看JVM启动的时候携带的参数。jcm pid VM.command_line

    image-20200218212657397

综上,通过JUI能看到的东西,在命令行都是能够出来的。但是有些时候是不能用UI工具的。JDK在官方发布的时候,不只是源码,还携带了很多便携性的工具。

jstack 专门用来查看或者导出Java应用中线程的堆栈信息。

jstack pid

➜  ~  jstack
Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message
➜  ~

image-20200218212907517

Jmc 和 Jhat 工具的使用

另外两个可视化工具,比JvisualVM显示的内容更多,并且在运行的时候也能够使用。

jmc : Java mission Contrl (我的OpenJDK 打不开)

image-20200218213052467

image-20200218213232472

![image-20200218213323367](/Users/shangyifeng/Library/Application Support/typora-user-images/image-20200218213323367.png)image-20200218213323548

image-20200218213353707

相当于把所有的jcmd继承在这个 诊断命令这里了

飞行记录器: jfr : java fly recoard. (下图是截取的一段飞行记录器)

image-20200218213611470

image-20200218213643149

image-20200218213702635

热点方法,加载,异常。编译,调用树。等都能查看的非常非常详细。

不管一个人对JVM多了解,一般都是借助UI工具来进行调优的。

借助 此工具,再看一下源空间的数据

image-20200218213927161

Jhat 工具 对堆存储文件的分析工具

jhat filepath

➜  ~ jhat -help
Usage:  jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>

	-J<flag>          Pass <flag> directly to the runtime system. For
			  example, -J-mx512m to use a maximum heap size of 512MB
	-stack false:     Turn off tracking object allocation call stack.
	-refs false:      Turn off tracking of references to objects
	-port <port>:     Set the port for the HTTP server.  Defaults to 7000
	-exclude <file>:  Specify a file that lists data members that should
			  be excluded from the reachableFrom query.
	-baseline <file>: Specify a baseline object dump.  Objects in
			  both heap dumps with the same ID and same class will
			  be marked as not being "new".
	-debug <int>:     Set debug level.
			    0:  No debug output
			    1:  Debug hprof file parsing
			    2:  Debug hprof file parsing, no server
	-version          Report version number
	-h|-help          Print this help and exit
	<file>            The file to read

For a dump file that contains multiple heap dumps,
you may specify which dump in the file
by appending "#<number>" to the file name, i.e. "foo.hprof#3".

All boolean options default to "true"
➜  ~

执行命令,启动一个服务器能够浏览器直接访问。

image-20200218214516979

提供了OQL查询,以一种SQL查询的方式,查询相关的数据。相关语法结构等到用的时候现用现学就行了。

![image-20200218214743118](/Users/shangyifeng/Library/Application Support/typora-user-images/image-20200218214743118.png)

这个功能在JvisualVM里面也有所体现。

image-20200218214828924

posted @ 2020-02-23 11:43  dawa大娃bigbaby  阅读(423)  评论(0编辑  收藏  举报