IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> JVM学习记录 -> 正文阅读

[系统运维]JVM学习记录


前言


一、JVM是什么?

Java Virtual Machine -java程序运行环境(Java二进制字节码运行环境)

好处:
1.一次编写,到处运行
2.自动内存管理,垃圾回收机制
3.数组下标越界检查(抛出异常,否则覆盖其他内存资源)
4.多态

概念比较:JVM JRE JDK
在这里插入图片描述

二、常见JVM

在这里插入图片描述

三、学习路线

在这里插入图片描述
JVM内存结构→GC垃圾回收→类的字节码结构→类加载器→运行时优化(解释器)

四、内存结构

1. 程序计数器

在这里插入图片描述
蓝色路径是Java源代码的实现过程,源代码→jvm指令→解释器→机器码→CPU。
程序计数器的作用体现在解释器解释jvm指令时,是当前线程所执行的字节码的行号指示器,通俗点说就是记住下一条jvm指令的执行地址。由于读写地址非常频繁,因此物理硬件上通过寄存器实现。

特点:
1. 线程私有
每个线程都有自己的程序计数器,线程切换时记录指令执行地址。
2. 没有内存溢出

2. Java虚拟机栈(JVM Stacks)

2.1定义

虚拟机栈——线程运行所需要的内存空间,由多个栈帧组成
栈帧——每个方法调用时所需要的内存空间
活动栈帧——当前正在执行的(虚拟机栈顶的)那个方法

特点:
线程私有
在这里插入图片描述
问题辨析:
1.垃圾回收是否设计栈内存?
不涉及,栈内存的占用在每次调用方法时产生,随着方法调用的结束栈帧内存被自动回收,不需要垃圾回收来管理。垃圾回收管理的是堆内存。

2.栈内存分配是否越大越好?
栈内存的大小在运行时指定虚拟机参数 -Xss,如不指定,不同的操作系统对应的栈内存大小分别为:Linux/x64:1024KB; macOS:1024KB; Oracle Solaris/x64:1024KB; Windows:取决于虚拟内存。
物理内存的大小一定,栈内存划的越大,则线程数越少。栈内存大能进行更多次的方法递归调用,并不能影响运行速度。

3.方法内的局部变量是否线程安全?
取决于局部变量对于每一个线程是共享的还是私有的。若为私有则每个线程对应一个栈,局部变量是线程安全的。若为共享(static类型),线程不安全。
举个例子:
在这里插入图片描述
m1方法中的局部变量sb是线程私有的,线程安全;
m2方法中的局部变量sb作为方法参数,是多个线程共享的,线程不安全,可以改为StringBuffer型(考虑了同步机制);
m2方法中的局部变量sb作为方法返回值,是多个线程共享的,线程不安全。
总结:对于引用类型变量来说,如果方法的局部变量没有逃离方法的作用范围,是线程安全的。对于逃离了方法的作用范围的,若只有一个线程操作这个变量,一定是线程安全的,如果有多个线程操作,且变量考虑了同步机制,是线程安全的,反之,是不安全的。

2.2内存溢出

Exception:StackOverflowError
产生原因:

  • 栈帧过多(如:循环引用…)
  • 栈帧过大

2.3线程运行诊断

案例1:CPU占用过多,定位(Linux系统)

  1. top定位占用cpu较多的进程
  2. ps 命令定位线程
  3. jstack根据线程id找到有问题的线程(16进制),定位到源码行

案例2:程序运行很久都没有结果
top定位占用cpu较多的进程
4. ps 命令定位线程
5. jstack根据线程id找到有问题的线程(16进制),定位到源码行

3. 本地方法栈(Native Method Stacks)

本地方法:与操作系统底层交互的方法,jvm通过本地方法接口调用底层功能。
作用:给本地方法运行提供内存空间。
特点:线程私有

4.堆(Heap)

4.1定义

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

特点:
1. 线程共享,堆中的对象要考虑线程安全的问题。
2.有垃圾回收机制(堆中不再使用的对象会当成垃圾回收以释放内存资源)

4.2堆内存溢出

Exception: OutOfMemoryError
参数:-Xmx 修改堆空间大小

4.3堆内存诊断

  1. jps工具
    查看当前系统中有哪些Java进程
  2. jmap工具
    监测某一时刻堆内存的占用情况
    jmap -heap 进程id
  3. jconsole工具
    连续监测堆内存占用情况
  4. jvisualvm工具
    堆转储 dump 查看对象信息

案例:
垃圾回收后,内存占用依然很高

5.方法区(Method Area)

5.1 定义

方法区是所有线程共享的区域,存储了类结构的相关信息,包括运行时常量池、成员变量、方法数据、成员方法和构造器方法的代码等。
方法区在虚拟机启动时被创建,逻辑上属于堆(并不强制使用堆内存)。

5.2 组成

方法区在jdk1.6和1.8版本的组成和逻辑位置如下图所示。
在这里插入图片描述
在这里插入图片描述
作为一个进程部署在系统内存中。

5.3方法区内存溢出

Exception: OutOfMemoryError
参数:-XX:MaxMetaspaceSize 修改内存大小

1.8以前会导致永久代内存溢出 OutOfMemoryError:PermGen space
1.8以后导致元空间内存溢出 OutOfMemoryError:Metaspace

运行时动态产生并加载Class 实际场景:
- Spring
- mybatis

5.4运行时常量池

二进制字节码文件(.class)组成:类基本信息、常量池、类方法定义(包含了虚拟机指令)。javap -v *.class反编译后可以查看。根据编译原理,执行虚拟机指令。
字节码文件反编译后代码部   分
常量池是一张表,存于.class文件中,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量(如基本数据类型、字符串等)等信息。
字节码文件反编译后常量池
运行时常量池:类被加载时,常量池信息会放入运行时常量池并把其中的符号地址(#1等)变为真实地址。

5.5StringTable

特性:

  • 运行时常量池中的字符串仅是符号,第一次使用时才变为对象,并加载到串池中
  • 采用串池的机制,避免创建重复的字符串对象
  • 字符串变量拼接的原理是StringBuilder(1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用intern方法,主动将串池中还没有的对象放入串池。
    将字符串对象尝试放入串池,如果有则并不会放入,如果没有则将同一个对象放入串池,(即串池中的对象和堆中的对象是同一个) 会把串池中的对象返回

面试题:

以常考的面试题为例,
String s1 = “a”; /
String s2 = “b”;
String s3 = “ab”;
String s4 = s1 + s2;
String s5 = “a” + “b”;
String s6 = s4.intern();
String x2 = new String(“c”) + new String(“d”);
String x1 = “cd”;
x2.intern();

	问:
	System.out.println(s3 == s4);
	System.out.println(s3 == s5);
	System.out.println(s3 == s6);
	System.out.println(x1 == x2);
	如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢

我们先来看一段代码的底层原理,首先捋清楚代码在底层运行的逻辑,之后我们再聚焦到字符串在哪里创建的:

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

反编译Demo1.class文件

运行时常量池: 注意逻辑地址,即 #及后面的数字
在这里插入图片描述
Main方法:
在这里插入图片描述
在底层理解一下代码 String s1 = “a”; 的意思:在运行时常量池#7位置加载字符串变量,而#7位置指向#8位置存储的字符串"a",加载后把它存入到局部变量表2中。
局部变量表
在这里插入图片描述
理解了代码运行的逻辑,知道了符号加载的底层逻辑,我们区分一下常量池和串池即StringTable的关系。常量池开始存在于字节码文件中,当程序运行时,常量池被加载到运行时常量池中,但此时运行时常量池中的符号还没有成为Java中的字符串对象。只有执行到引用符号的代码时,才会把符号变为字符串对象。
串池StringTable数据结构上是一个哈希表,长度固定,不能扩容。运行到引用符号的代码,生成一个字符串对象,将生成的字符串对象放入串池中。
所以说,常量池是存在于.class文件中的,而串池是运行时形成的。

那么两个引用的字符串相加的原理是什么呢?按照上面的分析思路,底层原理为
new StringBuilder().append(“a”).append(“b”).toString() new String(“ab”)
可以看出来,最后是new 了一个String。另外一种思路是,s1 s2是引用型变量,运行时引用可以被修改,编译期无法判断相加后是否不变,因此会new新的对象。
因此System.out.println(s3 == s4);的结果也显而易见了,s3位于串池中,而s4是new出来的对象,位于堆中,"==" 做出的是引用地址是否相同,结果为false。

同样的分析原理, String s5 = “a” + “b”;底层实现逻辑为在常量池中找到"ab"(Javac在编译期的优化,因为"a" “b"是常量,在编译期就能确定合并后仍然是常量),并把它放在串池中,而此时串池中已经有了"ab"对象,因此s5指向已经存在的"ab”。
因此System.out.println(s3 == s5);结果为true。

// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class Demo2 {
    // 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
    // ldc #2 会把 a 符号变为 "a" 字符串对象
    // ldc #3 会把 b 符号变为 "b" 字符串对象
    // ldc #4 会把 ab 符号变为 "ab" 字符串对象
    public static void main(String[] args) {
        String s1 = "a"; // 懒惰的
        String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()  new String("ab")
        System.out.println(s3 == s4);
        String s5 = "a" + "b";  // javac 在编译期间的优化,结果已经在编译期确定为ab
        System.out.println(s3 == s5);
    }
}

补充小细节:如何理解只有执行到引用符号的代码时,才会把符号变为字符串对象。
我们用IDEA调试证明一下:

public class TestString {
    public static void main(String[] args) {
        int x = args.length;
        System.out.println(); // 字符串个数 2275

        System.out.print("1");
        System.out.print("2");
        System.out.print("3");
        System.out.print("4");
        System.out.print("5");
        System.out.print("6");
        System.out.print("7");
        System.out.print("8");
        System.out.print("9");
        System.out.print("0");
        System.out.print("1"); // 字符串个数 2285
        System.out.print("2");
        System.out.print("3");
        System.out.print("4");
        System.out.print("5");
        System.out.print("6");
        System.out.print("7");
        System.out.print("8");
        System.out.print("9");
        System.out.print("0");
        System.out.print(x); // 字符串个数
    }
}

给程序添加断点,依次执行,内存标签中可以观察String的变化,每执行一次,生成一个对象并放入串池。当执行到重复的字符时,String不会变化,即不会放入串池中。
在这里插入图片描述

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-07-22 14:38:04  更:2021-07-22 14:40:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 16:34:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码