栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

JVM学习记录

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JVM学习记录

目录

一.引言

1.什么是JVM

2.学习路线

二.内存结构

1.程序计数器

1.1定义

1.2作用

2.虚拟机栈

2.1定义

2.2栈内存溢出(StackOverflowError)

2.3线程运行诊断

3.本地方法栈

4.堆

4.1定义

4.2堆内存溢出(OutOfMemoryError)

4.3堆内存诊断

5.方法区

5.1定义

5.2组成

5.3方法区内存溢出

5.4运行时常量池


一.引言

1.什么是JVM

JVM:java virtual machine(java虚拟机),Java程序的运行环境,是二进制字节码运行环境

好处:

一次编写,到处运行自动内存管理,垃圾回收功能数组下标越界检查使用虚方法调用的机制实现多态

比较:jvm jre jdk

基础类库:集合类,线程类,IO类等

编译工具:javac,javap

应用服务器工具:tomcat

2.学习路线

二.内存结构

1.程序计数器

1.1定义

1.2作用
    java源代码经过编译后成为二进制字节码的jvm指令jvm指令经过解释器成为机器码机器码可以被cpu执行

而程序计数器就会记住下一条需要执行的的jvm指令的序号

 特点:

线程私有的(每个线程都会有自己的程序计数器)不会存在内存溢出问题

2.虚拟机栈

2.1定义

栈:线程运行的内存空间

栈帧:每个方法运行时需要的内存(存放参数,局部变量,返回地址)

每个线程运行时所需的内存,成为虚拟机栈每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存每个线程只能有一个活动栈帧,对应着当前正在执行的方法。

问题辨析:

垃圾回收是否涉及栈内存?

栈帧内存在每次方法执行完毕后都会弹出,垃圾回收是回收的堆内存。

栈内存分配越大越好吗?

不是,在物理内存一定的情况下,栈内存越大,可以运行的线程数量越少,从而会影响程序的运行速度。栈内存大小只决定连续调用方法的数量。例如:递归调用可能造成栈溢出问题。

方法内的局部变量是否线程安全?

如果方法内部的局部变量没有逃离方法的作用范围,他是线程安全的。如果局部变量引用了对象(传进来的参数),并逃离了(return出)方法的作用范围,则需要考虑线程安全。(因为无法考虑方法之外,变量是否被其他线程调用)static修饰的变量不是放在栈中的,是线程共享的,是线程不安全的。

2.2栈内存溢出(StackOverflowError)

造成原因:

栈帧过多导致栈内存溢出(错误的递归调用)栈帧过大导致栈内存溢出(一个方法中的局部变量过多,出现几率小)

2.3线程运行诊断

案例1:cpu占用过多

定位步骤:

用top命令定位哪个进程对cpu占用过高ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)。pid,tid,%cpu是选择查看的列。命令jstack 进程id。 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号

执行top命令:

 jstack 进程id命令

注:nid是pid的16进制转换

案例2:程序运行很长时间没有结果

定位同上,执行jstack 进程id命令。

查询结果是线程死锁。

3.本地方法栈

给本地方法运行提供内存空间。

本地方法:用c或c++写的系统本地的方法(native修饰的),例如:object类中的clone(),hashCode(),notify()等

4.堆

4.1定义

通过new关键字创建的对象都会使用堆内存。

特点:

是线程共享的,堆中的对象都需要考虑线程安全问题。有垃圾回收机制

4.2堆内存溢出(OutOfMemoryError)

代码演示:

package com.erp.payroll.test.VO;

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


public class JvmDemo {
    public static void main(String[] args) {
        int i = 0;
        try {
            List list = new ArrayList<>();
            String s = "haha";
            while (true) {
                list.add(s);
                s = s + s;
                i++;
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println(i);
        }
    }
}
java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at com.erp.payroll.test.VO.JvmDemo.main(JvmDemo.java:20)
26

堆空间内存也可以通过-Xmx 设置 ,例如:设置8m -Xmx8m。

4.3堆内存诊断

工具:

1.jps工具

查看当前系统中有哪些java进程

2.jmap工具(某一时刻)

查看堆内存占用情况

3.jconsole工具

图形界面的,多功能的检测工具,可以连续监测

演示:当执行1时候,堆中没有过多的内存被占用,但是在创建bytes后,就会增加10mb,在执行了2之后,会调用gc清理,堆内存占用又会减少。

package com.erp.payroll.test.VO;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


public class JvmDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("1=============");
        TimeUnit.SECONDS.sleep(30);
        byte[] bytes = new byte[1024 * 1024 * 10];//10mb
        System.out.println("2=============");
        TimeUnit.SECONDS.sleep(30);
        bytes = null;
        System.gc();
        System.out.println("3=============");
        TimeUnit.SECONDS.sleep(50);
    }
}

执行命令

F:***erp-payroll>jps

得到

8844 JvmDemo

 打印1=============之后

 执行

F:***erp-payroll>jmap -heap 8844

 堆内存使用:18187392

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 58720256 (56.0MB)
   used     = 18187392 (17.3448486328125MB)
   free     = 40532864 (38.6551513671875MB)
   30.972943987165177% used

 打印2=============之后,执行

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 58720256 (56.0MB)
   used     = 28673168 (27.344863891601562MB)
   free     = 30047088 (28.655136108398438MB)
   48.83011409214565% used

打印3=============之后,执行

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 58720256 (56.0MB)
   used     = 2348848 (2.2400360107421875MB)
   free     = 56371408 (53.75996398925781MB)
   4.000064304896763% used

 jconsole工具使用

启动main方法,执行命令

F:***erp-payroll>jconsole

 弹出视图工具

 动态查看堆内存使用情况

案例: 

垃圾回收后,内存占用仍然很高(详情见视频)

通过jsp可以看出新生代堆内存清理后,老年代还在被占用(对象被引用,无法释放)

另一个视图工具

执行命令:(高版本jdk没有内置,需自主安装)

F:****erp-payroll>jvisualvm

5.方法区

5.1定义

方法区是所有线程共享的区域。存储了跟类的结构相关的信息,有成员变量,方法数据,成员方法,构造方法,运行时常量池等

方法区是在类启动时被创建。

5.2组成

不同版本的jdk内存结构有所不同

 

1.6中方法区占用了堆内存;字符串常量池放在了方法中的运行时常量池中

1.8之后方法区放在了本地内存中;字符串常量池放进了堆中。

5.3方法区内存溢出

 1.8之后方法区放在本地内存中,所以内存溢出不太容易出现,可以把相关参数设置较小再进行测试。

元空间参数设置 -XX:MaxmetaspaceSize=8m
永生代参数设置 -XX:MaxPermSize=8m

并且两种版本号内存溢出报错也有所不同 

元空间内存溢出:OutOfMemoryError:metaspace
永生代内存溢出:OutOfMemoryError:PermGen space
package com.erp.payroll.test.VO;

import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;




public class JvmDemo extends ClassLoader {// 可以用来加载类的二进制字节码
    public static void main(String[] args) throws InterruptedException {
        int j = 0;
        try {
            JvmDemo test = new JvmDemo();
            for (int i = 0; i < 10000; i++, j++) {
                //ClassWriter作用是生成类的二进制字节码
                ClassWriter cw = new ClassWriter(0);
                //版本号,public,类名,包名,父类,接口
                cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i,
                        null, "java/lang/Object", null);
                //返回byte[]
                byte[] code = cw.toByteArray();
                //执行类加载器
                test.defineClass("Class" + i, code, 0, code.length);//Class对象
            }
        } finally {
            System.out.println(j);
        }
    }
}

5.4运行时常量池

 演示查看字节码详情中的常量池:

代码:

package com.erp.payroll.test.VO;


//二进制字节码(类的基本信息,常量池,类方法定义,包含了虚拟机指令)
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("hello world!");
    }
}

操作指令:

1.先cd到文件的上一级目录

2.使用javac编译出字节码

***testVO>javac HelloWorld.java

3.使用javap查看字节码详情

****testVO>javap -v HelloWorld.class

 二进制字节码详情:(类的基本信息,常量池,类方法定义,包含了虚拟机指令)

类的基本信息:

 Last modified 2022-2-18; size 450 bytes
  MD5 checksum 284a6f82e2f72e0f5adafd276b197cef
  Compiled from "HelloWorld.java"
public class com.erp.payroll.test.VO.HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER

运行时常量池:

Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // hello world!
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // com/erp/payroll/test/VO/HelloWorld
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               HelloWorld.java
  #15 = NameAndType        #7:#8          // "":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               hello world!
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               com/erp/payroll/test/VO/HelloWorld
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V

类的方法定义:包含了虚拟机指令

{
  public com.erp.payroll.test.VO.HelloWorld();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 9: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 11: 0
        line 12: 8
}
SourceFile: "HelloWorld.java"

虚拟机指令:

  Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

常量池作用:

例如行号3:对应的#3,表示进入运行时常量池中的#3进行翻译

运行时常量池中的#3对应的#18,继续翻译

#18对应 Utf8               hello world!

表示对应的是字符串形式的  hello world!

常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量信息。

运行时常量池,常量池是*.class文件中的,当该类被加载,他的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址。

5.5StringTable

面试题:

package com.erp.payroll.test.VO;

import java.sql.SQLOutput;


public class StringDemo {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b";
        String s4 = s1 + s2;
        String s5 = "ab";
        String s6 = s4.intern();

        //问
        System.out.println(s3 == s4);
        System.out.println(s3 == s5);
        System.out.println(s3 == s6);

        String x1 = new String("c") + new String("d");
        String x2 = "cd";
        x1.intern();

        //问
        System.out.println(x1 == x2);

        //调换最后两行代码位置呢,如果是jdk1.6呢
        String x3 = new String("c") + new String("d");
        x3.intern();
        String x4 = "cd";
        System.out.println(x1 == x2);
    }
}

常量池和串池的关系:

public class StringDemo2 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
    }
}

 查看二进制字节码:

  Last modified 2022-2-18; size 334 bytes
  MD5 checksum 7b08065901c43270c4a91a4d85f35540
  Compiled from "StringDemo2.java"
public class com.erp.payroll.test.VO.StringDemo2
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."":()V
   #2 = String             #16            // a
   #3 = String             #17            // b
   #4 = String             #18            // ab
   #5 = Class              #19            // com/erp/payroll/test/VO/StringDemo2
   #6 = Class              #20            // java/lang/Object
   #7 = Utf8               
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               StringDemo2.java
  #15 = NameAndType        #7:#8          // "":()V
  #16 = Utf8               a
  #17 = Utf8               b
  #18 = Utf8               ab
  #19 = Utf8               com/erp/payroll/test/VO/StringDemo2
  #20 = Utf8               java/lang/Object
{
  public com.erp.payroll.test.VO.StringDemo2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 9: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: return
      LineNumberTable:
        line 11: 0
        line 12: 3
        line 13: 6
        line 14: 9
}
SourceFile: "StringDemo2.java"

 常量池中的信息都会被加载到运行时常量池中(这时a,b,ab都是常量池中的符号,还没变为java字符串对象)。

当执行到引用的代码上才会成为java字符串对象,

String s1 = "a";
  0: ldc           #2                  // String a
  #2 = String             #16            // a
  #16 = Utf8               a

ldc #2会把a符号变成“a”字符串对象,在这个过程中会先准备串池StringTable[ ]的空间,并先以a为key查询串池中是否存在(不存在则放入串池)。  StringTable[ ]是hashTable结构

 s4执行时做了什么:

 String s4 = s1 + s2;

二进制字节码:

  9: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBu
ilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBu
ilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4

 在这个过程中它做了这些事情:new StringBuilder( ).append("a").append("b").toString( )

但是查看toString()源码会发现,它是new了一个新的String(new String(“ab”))。

所以s4指向了一个堆内的地址。s3不等于s4。

s5:

    String s5 = "a" + "b";
        29: ldc           #4                  // String ab
        31: astore        5

它是直接拿到了字符串ab。与String s3 = “ab”相同 (是在编译期确定的为ab的)

5.6StringTable特性

常量池中的字符串仅是符号,第一次用到时才会变为对象利用串池的机制,来避免重复创建创建字符串对象字符串变量拼接的原理是StringBuilder(1.8之后),存在于堆中字符串常量拼接的原理是编译期优化,存在于串池中可以使用intern方法,主动将串池中还没有的字符串对象放入串池。

     1.8 版本之后将字符串对象放到串池中。如果串池中有,则不会放入,如果没有,则会放入,并将串池中的对象返回 。 1.6版本将字符串对象放到串池中。如果串池中有,则不会放入,如果没有,则会把此对象复制一份放入(浅克隆地址不同),并将串池中的对象返回 。

案例1:

String s = new String("a") + new String("b");

动态的拼接是存在于堆中的,相当于new了一个“ab” 

String s1 = s.intern();

此方法会将s(“ab”)放到串池中。如果串池中有“ab”,则不会放入,如果没有,则会放入(s的地址指向串池中“ab”),并将串池中的对象返回 。s1一定是串池中的对象。

 结论:

        System.out.println(s=="ab");//true
        System.out.println(s1=="ab");//true

但是如果没有 String s1 = s.intern(); 则s==“ab”为false(ab是串池中的对象,s是堆中的对象)。

案例2:

        String x = "ab";
        String s = new String("a") + new String("b");
        String s1 = s.intern();

        System.out.println(s=="ab");
        System.out.println(s1=="ab");

此代码与案例1代码不同之处在于在执行到x=“ab”时就已经将ab放进了串池中,所以在执行到s1时候将s(在堆中)放进串池,发现串池中已经存在(则没有放进串池,地址就没办法指向串池),则s还是堆中的ab。s1还是指向的串池中的ab。

回看面试题:

package com.erp.payroll.test.VO;

import java.sql.SQLOutput;


public class StringDemo {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b";//常量拼接编译期就优化直接指向串池
        String s4 = s1 + s2;//动态拼接相当于在堆中new了一个ab
        String s5 = "ab";//常量池
        String s6 = s4.intern();//如果串池中没有则将s4放入串池。有则不放入。s6一定是串池中对象

        //问
        System.out.println(s3 == s4);//false,s4在堆中
        System.out.println(s3 == s5);//true,都在串池中
        System.out.println(s3 == s6);//true,都在串池中

        String x1 = new String("c") + new String("d");
        String x2 = "cd";
        x1.intern();

        //问
        System.out.println(x1 == x2);//false,x1在堆中,x2在串池中

        //调换最后两行代码位置呢,如果是jdk1.6呢
        String x3 = new String("e") + new String("f");
        x3.intern();//1.8将x3放入串池,如果没有则放入(堆与串池同地址),有则不放(与串池对象不同)
                    //1.6将x3浅复制放进串池,x3与串池对象一定不同。
        String x4 = "ef";
        System.out.println(x3 == x4);//true
    }
}
5.7StringTable位置

 

5.8StringTable垃圾回收 5.9StringTable性能调优

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/740549.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号