【读书笔记】JAVA基础:1、深入理解JVM

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_33602978/article/details/79733191

通过《深入理解JAVA虚拟机》和《深入理解计算机系统》两本经典著作的学习,注重了解系统进程运行时内存结构的变化,以此彻底了解JVM虚拟机在运行JAVA程序时的内存结构!

主要有三个方面:

        1、计算机系统的内部系统结构;

        2、JVM运行时数据内存结构;

        3、通过简单的JAVA程序彻底理解JVM执行流程。

一、计算机系统的内部系统结构

        先看一张图,有个大体印象:

首先,要记住的是,一系列语言的的源文件保存在磁盘上,通过解释器解释后成可执行文件,也保存在磁盘上。

下面举个简单的例子,下面有一段简单的C程序,保存为hello.c:

#include <stdio.h>

int main()
{
    printf("hello,world!");
    return 0;
}

C语言程序的设计是用来实现Unix操作系统的,那么就可以直接在linux的shell指令进行编译:

[root@localhost lgy] gcc -o hello hello.c

上面命令的意思就是通过GCC编译器将hello.c源文件编程成文件名为hello的可执行文件。

[root@localhost lgy] ./hello

上面命令回车后,发生了下面的流程:

1、通过指令将hello文件中的代码和数据复制到了主存

2、当处理器发现执行文件加载到了主存后,就处理器就会执行主存代码中的main程序,将主存的main程序中的字节复制到了处理器的寄存器中(如果代码中涉及到数据的运算,就会拷贝数据到算术/逻辑单元,计算完之后就会将数据返回寄存器)

3、寄存器执行完命令之后再将数据复制到界面。

这里,我们又会发现一个问题,就是简单的一个程序处理,涉及到了多次的复制,特别是在处理器与主存之间,当我们的程序中设计到函数的调用,结果再次返回主存,下次我们获取还得从主存复制到寄存器,就这影响了性能,这就出现了高速缓存。

高速缓存被设计在处理器中的一个部件,假如我们用的多核处理器,那么就意味着每核处理器里面都包含了寄存器、算术单元、高速缓存等组件。

            以上就是计算机系统执行一个可执行文件的整个流程,对于我们Java攻城狮来说,对其有个大概的了解就可以了,其中具体的指令操作,内存分配等,不是那么重要,当然兴趣是最好的老师,下面的JVM才是我们必须彻底了解的一块。

二、JVM运行时数据内存结构;

通过一张JVM运行时内存结构图,有个大致的印象:

JVM运行时内存主要分有五块区域:

堆,方法区---------------------线程共享区域

虚拟机栈、本地方法栈、(直接内存)-------------------线程私有区域

简单分解概念:

程序计数器:是一块很小的内存空间,可以看作当前线程所执行的字节码位置指示灯。这里只需要知道,程序中所出现的分支、循环、跳转(方法调用)、异常处理、线程恢复等基础功能都依赖它。如图,可以看到它在支配着两个栈的计数,如果线程中执行一个Java方法,计数器就会记录正在执行的字节码指令的位置,若是执行的Native方法,计数器的值就是空的。

虚拟机栈:生命周期同线程。每个方法在执行的时候都会创建一个栈帧用来存储局部变量表、操作数栈、动态链接、方法出口等信息。其中,局部变量表存放的是编译器可知的基本数据类型,有int,short,byte,long,float,char,boolean,double,还有对象引用的类型。

本地方法栈:顾名思义,就是Java程序内部调用Native属性的方法所占用的内存空间。

Java堆:很显然,栈中存放的是对象的引用,堆里就存放了对象实例本身(作为了解,有些编译器也不是这么绝对)。堆中重点需要了解的是GC垃圾回收机制。

方法区:主要用于存储已被虚拟机加载过的类信息、常量、静态变量、即时编译器编译后的代码等数据。这个区域也被垃圾回收机制成为持久代,主要是对常量池中数据的回收和对类的卸载。

            方法区中需要了解的是常量池,例如,public String s = "abc",那么“abc”和其符号引用就保存在常量池

直接内存:主要用于NIO,可以使用Native函数库直接分配堆外内存,存放这Java堆中的DirectByteBuffer对象的引用。

建议推荐https://blog.csdn.net/fuzhongmin05/article/details/78169044

概念的东西都讲完了,下面的就是重点了。

三、通过简单的JAVA程序彻底理解JVM执行流程。

一个Person类:

public class Person {
	
	private String name;
	
	private static String stat = "0";
	
	private static final int index = 1;
	
	public void work(){
		String address = "renminlu";
		int day = 0;
		while(day < 365){
			day ++ ;
		}
		System.err.println(address);
	}
	
	public static void main(String[] args) {
		Person person = new Person();
		person.work();
		assert args.length < 0;
		Integer i = 122;
		Integer j = 122;
		System.err.println(i == j);
	}
	
}

在这个类里面我们定义了一个普通属性name,一个静态属性stat并初始化“0”,一个static final修饰的属性index。

整个程序编译的过程如下图(网上摘抄):

主要看其运行时数据的内存分配:

打开Person所在目录,执行:

javac Person.java
javap -v -c -l Person

最后汇总一下JVM常见配置

  1. 堆设置
    • -Xms:初始堆大小
    • -Xmx:最大堆大小
    • -XX:NewSize=n:设置年轻代大小
    • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
    • -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
    • -XX:MaxPermSize=n:设置持久代大小
  2. 收集器设置
    • -XX:+UseSerialGC:设置串行收集器
    • -XX:+UseParallelGC:设置并行收集器
    • -XX:+UseParalledlOldGC:设置并行年老代收集器
    • -XX:+UseConcMarkSweepGC:设置并发收集器
  3. 垃圾回收统计信息
    • -XX:+PrintGC
    • -XX:+PrintGCDetails
    • -XX:+PrintGCTimeStamps
    • -Xloggc:filename
  4. 并行收集器设置
    • -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
    • -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
    • -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
  5. 并发收集器设置
    • -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
    • -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

猜你喜欢

转载自blog.csdn.net/weixin_33602978/article/details/79733191
今日推荐