写点什么

JVM 浅析(二)

作者:andy
  • 2022-10-28
    北京
  • 本文字数:9839 字

    阅读完需:约 32 分钟

7-引用类型


根搜索算法

把内存中的每个对象看作一个节点,定义对象作为根节点“GC Roots”。如果一个对象有另一个对象引用,则认为这个对象有一条指向另一个对象的边

GC Roots

第一种:虚拟机栈中的引用的对象

第二种:类中定义的全局的静态对象

第三种:使用关键字 static final 的常量引用

第四种:JNI 技术,使用 native 方法


四种引用

(1)强引用

使用关键 new 生成的对象的引用,如 Object obj = new Object(),只要 obj 对象生命周期未结束,或者没有显示把 obj 赋为 null,JVM 不会进行回收。

当内存容量不足時,宁愿出现 OOM 错误,也要一直保存的引用,不會對其對象進行回收。

强引用是 Java 默认支持的模式,只要栈内存中还有指向堆内存的引用,則当内存不足時,只会抛出 OutOfMemoryError 错误,不会对对象进行回收

因强引用是我們开发一直使用的模式,并且具有这样的异常错误产生,所以,尽量少实例化对象,避免因无法回收对象造成内存溢出


public class StrongReference{	public static void main(String[]atgs){		String str = "StrongReference";		String obj = str;		str = null;		//obj = null;		System.gc();		System.out.println(obj);	}}
复制代码


(2)软引用

当内存不足时,才會对软引用对象进行回收,常应用于高速缓存

*Mybatis 开发框架使用高速缓存,也就是软引用方式。

对于当下的开源项目,使用软引用作为项目的缓存组件。当内存充足时,不进行对象回收,反之,进行对象回收。使用 java.lang.ref.SoftReference 类实现,所有的引用都在 java.lang.ref 包下。

SoftReference 类的使用方法如下:

构造方法:public SoftReference​(T referent)

取得对象:public T get​()

程序範例:


import java.lang.ref.SoftReference;public class SoftReferenceDemo{	public static void main(String[]atgs){		String obj = "SoftReference";		SoftReference<String> ref = new SoftReference<>(obj);		obj = null;		String str = "garbage";		try{			while(true){				str += "+"+str;			}		}catch(Throwable r){}		System.gc();		System.out.println(ref.get()+"######");	}	}
复制代码


(3)弱引用

无论内存是否充足,只要出現了垃圾,便会对其进行回收处理

*弱不禁风,弱爆了

当内存进行 GC 处理时,弱引用对象进行回收,不建议使用。一旦發生 GC,則必须进行对象处理,所以容易数据丟失。可以使用以下类实现弱引用:java.util.WeakHashMap 和 WeakReference

範例一


import java.util.*;public class WeakReferenceDemo{	public static void main(String[]atgs){		String key = new String("stack");		String value = "heap";		// new String()和接賦值結果爲何不同?		//String key = "stack";		//String value = "heap";		Map<String,String> ref = new WeakHashMap<String,String>();		ref.put(key,value);		System.out.println(ref);		key = null;		System.out.println(ref);		System.gc();		System.out.println(ref);	}	}
复制代码


範例二


import java.lang.ref.*;public class WeakReferenceDemo{	public static void main(String[]atgs){		String obj = new String("weakReference");		// 爲什麽new String()和直接賦值,結果完全不同		//String obj = "weakReference";		WeakReference<String> ref = new WeakReference<>(obj);		System.out.println(ref.get());		obj = null;		System.gc();		System.out.println(ref.get());	}	}
复制代码


注意:

1、弱引用不建议使用,就是因为一旦发生 GC,就会被清空。

2、使用字符串举例,需要小心对象自动入池的问题,一旦入池,是无法清空的,所以,建议使用 new String()方式

3、字符串对象一旦声明,不可修改

四、引用隊列

引用隊列就是對要回收的對象進行保存,在对象回收前可以进行操作处理。對於對象的回收都是從根對象開始向下掃描的。對於 GC 而言,想要確定哪些對象被回收,確定好引用的强度即引用路徑的設定。以下展示引用路徑。



圖 11 引用路徑


對於對象的引用,要考慮到引用的關聯,因此必須找到強關聯。爲了避免非強關聯對象所帶來的内存引用的問題,故而提出了引用隊列的概念。如果創建軟引用和弱引用使用了引用隊列,那麽,在引用對象回收前會保存到引用隊列之中。引用隊列的主要作用就是對被回收對象的控制。可以使用 java.lang.ref.ReferenceQueue 類實現。

範例:


import java.lang.ref.*;public class ReferenceQueueDemo{	public static void main(String[]atgs) throws Exception{		String obj = new String("referenceQueue");		// new String()和直接賦值的區別		//String obj = "referenceQueue";		ReferenceQueue<String> queue = new ReferenceQueue<>();		WeakReference<String> ref = new WeakReference<>(obj,queue);		System.out.println(queue.poll());		obj = null;		System.out.println(queue.poll());		System.gc();		Thread.sleep(100);		System.out.println(queue.poll());	}	}
复制代码


(5)幽靈引用(虚引用)

相當於沒有引用。

永遠得不到的數據叫做幽靈引用,更多意義在於提出了一種思想。所有保存在幽灵引用中的数据都不会真正保留。对象无法回收,finalize()方法无法执行。幽灵引用可以使用以下類實現:

java.lang.ref.PhantomReference

範例:


import java.lang.ref.*;public class PhantomReferenceDemo{	public static void main(String[]atgs) throws Exception{		String obj = new String("PhantomReference");		ReferenceQueue<String> queue = new ReferenceQueue<>();		PhantomReference<String> ref = new PhantomReference<>(obj,queue);		System.out.println(queue.poll());		//obj = null;		System.out.println(queue.poll());		System.gc();		System.out.println(queue.poll());	}	}
复制代码


延伸:

String 字符串如何存儲?構造方法(new String(""))和直接賦值("")有何區別?

问题:

1、WeakHashMap 和 HashMap 的区别?


8-JVM 性能调优


性能优化的关键在于怎么找到当前系统的性能瓶颈,并不在于怎么进行优化。

性能优化分为好几个层次,比如系统层次、算法层次、代码层次等等JVM 的性能优化被认为是底层优化,门槛较高,精通这种技能的人比较少。

JVM 本身给我们提供了很多强大而有效的监控进程、分析定位瓶颈的工具,比如 JConsole、JMap、JStack、JStat 等等。使用这些命令,再结合 Linux 自身提供的一些强大的进程、线程命令,能够快速定位系统瓶颈。

1、使用系统压力测试工具,例如 LoadRunner,通过工具的压测结果分析出系统瓶颈。

2、使用服务器和数据库压测工具,例如 Spotlight,查看并发压测时服务器和数据库,CPU 以及内存的各项指标信息。

3、JStack。JStack 能够看到当前 Java 进程中每个线程的当前状态、调用栈、锁住或等待去锁定的资源,而且很强悍的是它还能直接报告是否有线程死锁,可谓解决线程问题的不二之选。

JStack 拉取的文件信息基本分为以下几个部分:

该拉取快照的服务器时间

JVM 版本

以线程 ID(即 tid)升序依次列出当前进程中每个线程的调用栈

死锁(如果有的话)

阻塞锁链

打开的锁链

监视器解锁情况跟踪

每个线程在等待什么资源,这个资源目前在被哪个线程 hold,尽在眼前。JStack 最好在压测时多次获取,找到的普遍存在的现象即为线程瓶颈所在。

多次拉取 JStack 信息,发现很多线程处于重新获取大小的状态。因此,判断每个线程的分配空间不足,可以适当调整 thread local area(TLA)的大小。TLA 默认最小大小 2KB,默认首选大小 16KB - 256 KB (取决于新生代分区大小)。这里我们调整 TLA 空间大小为最小 32KB,首选 1024 KB,JVM 启动参数中加入:-XXtlaSize:min=32k,preferred=1024k

4、GC 日志

服务器开启 GC 日志报告只需在 Java 进程启动参数里加入-Xverbose:memory -Xverboselog:verboseText.txt。通过这些日志同样能够指出当前压力下的 GC 的频率,为本次性能瓶颈 - GC 过于频繁提供有力的佐证。

5、JStat 报告

通过 JStat 指令,获取当前已用碓大小和新生代分区大小,从而进一步查看性能。

jstat -J-Djstat.showUnsupported=true -snap 12224

综上分析,可以对内存空间进行适当调整。

-Xms10240m -Xmx10240m -Xns:1024m -XXtlaSize:min=32k,preferred=1024k

7、性能瓶颈的定位

1)、性能进程的获取

使用 TOP 命令拿到最耗 CPU 的 Java 进程。

2)、性能线程的获取

通过进程,进一步找到该进程下的占用 CPU 资源最高的线程。

ps p 10495 -L -o pcpu,pid,tid,time,tname,cmd

3)、使用 JStack 查看线程当前状态信息

通过该方式,从而进行系统性能最终调优。


9-对象访问模型


一、對象訪問模式

JVM 运行不是简单的程序执行,而是充满了各种各样的算法,只有清楚了其中的运行算法,才能够很好地进行内存调优。

对象访问模式便是其中的体现,也是非常重要的内容,理解了对象访问模式,调优也才有了门道。

在 Java 中,引用數據類型是最主要的数据处理模型。引用数据类型主要涉及到运行时数据区中的棧内存、堆内存、方法區。实际上,引用数据类型也是最容易产生内存垃圾的数据模型。

引用数据類型的對象訪問,可用簡單的實例化(Object obj = new Object())进行闡述,主要思路如下:

  • 新定義的對象的名稱 Object obj,存储在栈内存中,保存有堆内存的引用。但是严格上讲保存至本地變量表中。由本地变量表中的表格,确定出與之對應的棧内存,最后才能够由栈找到堆。这也是为什么 Java 中不允许变量重名的问题所在;

  • new Object(),真正的实例化对象,保存在堆内存中;

  • 利用堆内存的對象進行方法的調用,即訪問方法區;

简而言之,引用数据类型的对象访问思路如下:

本地变量表 --> 栈 --> 堆 --> 方法区

引用数据类型拥有两种对象访问模式:通过句柄访问和通过直接指针访问。C++中使用句柄访问的模式。在 Java 中直接使用的是對象保存模式。

通过句柄访问模式非常繁琐,但比较稳定。因为先有对象实例,而后才能调用方法。访问模式思路如下:

栈内存中的引用 --> 句柄池中的对象实例数据指针 --> 实例池中的对象实例数据 --> 对象类型数据指针 --> 方法区中的对象类型数据

通过直接指针访问模式相对句柄方式,能够快速进行对象操作。访问模式思路如下:

栈内存中的引用 --> 堆内存中的对象实例数据 --> 对象类型数据指针 --> 对象类型数据



圖 句柄訪問模式



圖 直接指針訪問模式(對象保存模式)


由指令 java -version 可以得出虚拟机的版本信息,而目前世界上有三种 JVM。

  • SUN 公司改良的 HotSpot,2006 年之后,HotSpot 架构开源了;

  • Bea 公司开发的 JRockit,目前最快的 JVM;

  • IBM 的虚拟机。

Oracle 收购了 SUN 和 Bea 两家公司,因此,不可能开发两套 JVM,在 JDK1.8 已经开始融合,JDK1.9 则极有可能融合完成。

獲取當前 JVM 版本:java -version

純解釋模式啓動:java -Xint -version

純編譯模式啓動:java -Xcomp -version

Java 虚拟机的模式:

  • mixed mode:混合模式,即适合于编译与执行;

  • interpreted mode:纯解释模式,不需要进行编译;

  • compiled mode:纯编译模式,不需要进行解释。

从目前的 Java 发展来看,实际上是为 JEE 服务的,也就是为服务器使用,企业级的应用程序。故设计了虛擬機有兩種啓動模式

  • -server:服務器模式,占用内存大,啓動速度慢,默認模式

  • -client:本地單機運行程序模式,啓動速度快

虚拟机启动模式的配置文件路徑如下:

  • Linux:$java_home/jre/lib/CPU 廠商+操作系統位數/jvm.cfg

  • Windows:%java_home%\jre\lib\CPU 廠商+操作系統位數\jvm.cfg


10-Stack


Java 虛擬機棧(Java Virtual Machine Stacks)

Java 虛擬機棧,是綫程私有的,生命周期和綫程相同。

Java 虛擬機棧描述的是 Java 方法執行的内存模型:

執行一個方法時會產生一個棧幀(Stack Frame),隨後將其保存至棧的頂部,即入棧。方法執行完畢后自動將此棧幀進行出棧。頂部的棧幀就表示當前方法。

通常有以下錯誤:

請求的棧的深度過大,虛擬機抛出 StackOverflowError;

*举例如下。

程序示例:


public class Test {	public static void main(String[] args) {		func();	}	public static void func() {		func();	}}
复制代码


异常错误:

Exception in thread "main" java.lang.StackOverflowError

at org.fuys.ownutil.Test.func(Test.java:10)


如果虛擬機允許虛擬機棧動態擴展,當内存不足以擴展棧的情況,虛擬機抛出 OutOfMemoryError;


棧幀(Stack Frame)


Java 虛擬機棧存放多個棧幀,棧幀包括以下幾個部分。

局部變量表(local variables):

保存方法的局部變量和形參,以變量槽(solt)為最小單位,只允許保存 32 位的變量,如果超過 32 位,則會開闢兩個連續的變量槽,如 long 和 double 類型數據,这里需要注意的是局部变量的数据类型,有可能是基本数据类型,也可能是引用数据类型。也就是说,对象引用存在局部变量表中;

操作數棧(operand stack):

表達式計算在棧中完成;

指向當前方法所屬的類的運行時常量池的引用(reference to runtime constant pool):

引用其他類的常量或者使用 String 池中的字符串;

方法返回地址(return address):

方法執行完后需要返回調用此方法的位置,所以需要在棧幀中保存方法返回地址;

動態鏈接(dynamic linking):

概念等同於計算機科學中的動態鏈接,在 Java 代碼運行過程中,動態生成方法和變量的符號引用,從而減少内存碎片的產生,節省空間,提高效率。

*对于以上理解,使用程序即可理解。


package org.fuys.ownutil.instance;// Method Areaclass PapeBook{	private String name ;	private int count ;	private double price;	private double total;	public String getName() {		return name;	}	public void setName(String name) {		this.name = name;	}	public int getCount() {		return count;	}	public void setCount(int count) {		this.count = count;	}	public double getPrice() {		return price;	}	public void setPrice(double price) {		this.price = price;	}	public double getTotal() {		return total;	}	public void setTotal(double total) {		this.total = total;	}	public PapeBook() {		super();	}	// stack frame 2	public PapeBook(String name, int count, double price) {		// stack frame 3		super();		// local variables		this.name = name;		this.count = count;		this.price = price;		// operand stack		this.total = count * price;	}	// stack frame 4	@Override	public String toString() {		// reference to runtime constant pool --> String		// local variables --> name,count,price,total		return "PapeBook [name=" + name + ", count=" + count + ", price=" + price + ", total=" + total + "]";	}}// Method Areapublic class StackInstance {	// stack frame 1 --> main	// local variables --> args	public static void main(String[] args) {		// local variables --> pageBook,1,30.0		// reference to runtime constant pool --> THE LONG VIEW		PapeBook papeBook = new PapeBook("THE LONG VIEW", 1, 30.0);		// reference to runtime constant pool --> System.out		// stack frame 4 --> toString()		// stack frame 5 --> println()		System.out.println(papeBook.toString());	}}
复制代码



圖 棧幀


Java 中的對象池是對常量池的規則破壞,在 Java 啓動的時候已經為常量池分配好了内存空間,但是 String 中的 intern()打破了這種限制,可以動態的進行常量池設置。

問題

StackOverflowError 和 OutOfMemoryError 的區別?

提醒:

如果虛擬機允許虛擬機棧動態擴展,當内存不足以擴展棧的情況,虛擬機抛出 OutOfMemoryError;


10-JVM 架构


JVM 架构

Java 是一种跨平台的语言,JVM 屏蔽了底层系统的不同,为 Java 字节码文件构造了一个统一的运行环境



堆和栈

堆:每个 JVM 实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放

在这个堆中,并由应用所有的线程共享

堆栈:JVM 为每个新创建的线程都分配一个堆栈。也就是说,对于一个 Java 程序来说,

它的运行就是通过对堆栈的操作来完成的

Java 中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,

也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对

象,而在堆栈中分配的内存只是一个指向这个堆对象的引用而已


方法区和程序计数器

方法区主要存放从磁盘加载进来的类字节码,而在程序运行过程中创建的类实例则存放在堆里。程序运行的时候,实际上是以线程为单位运行的,当 JVM 进入启动类的 main 方法的时候,就会为应用程序创建一个主线程,main 方法里的代码就会被这个主线程执行,每个线程有自己的 Java 栈,栈里存放着方法运行期的局部变量。而当前线程执行到哪一行字节码指令,这个信息则被存放在程序计数寄存器




Java(线程)栈

所有在方法内定义的基本类型变量,都会被每个运行这个方法的线程放入自己的栈中,线程的栈彼此隔离,所以这些变量一定是线程安全的



线程工作内存 & volatile

Java 内存模型规定在多线程情况下,线程操作主内存变量,需要通过线程独有的工作内存拷贝主内存变量副本来进行

一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的



JDK、JRE、JVM 之间的区别与联系




11-类加载


双亲委托模型

低层次的当前类加载器,不能覆盖更高层次类加载器已经加载的类。如果低层次的类加载器想加载一个未知类,需要上级类加载器确认,只有当上级类加载器没有加载过这个类,也允许加载的时候,才让当前类加载器加载这个未知类



自定义类加载器

隔离加载类:同一个 JVM 中不同组件加载同一个类的不同版本。

扩展加载源:从网络、数据库等处加载字节码。

字节码加密:加载自定义的加密字节码,在 ClassLoader 中解密


扩展-JAVA 影响 7 个性能指标


1、响应时间和吞吐量

响应时间,根据应用程序处理请求,进行业务处理,最终完成数据传输的过程所花费的时间。从专业术语而言,就是请求到响应所花费的时间。可以从 HTTP 级别,亦可以从数据库级别进行考察。响应时间过慢,可以从两方面进行分析,第一,业务逻辑程序代码处理效率不高,第二,则有可能是网络贷款过窄,导致响应时间过慢。

吞吐量,是指单位时间内系统处理客户端请求的数量。数量越大,则吞吐量越大。

需要注意的是,网络带宽,会对响应时间和吞吐量造成影响。

通过使用 APMs(诸如 New Relic、AppDynamics、Ruxit)工具衡量该类指标,可以通过数据进行对比,从而观察出程序变化对于指标变化的影响。

谷歌浏览器亦可以分析 HTTP 协议的响应时间,针对网页请求进行分析。



扩展

国内知名的服务器监控工具,诸如阿里云、腾讯云,采用 SNMP 协议对服务器进行监控,并且大多数收费,对于企业用户较为合适。

SNMP,简单网络管理协议,由一组网络管理的标准组成,包含一个应用层协议、数据库模型和一组资源对象。该协议支持网络管理系统,用以监控连接到网络上的设备是否有任何引起管理上关注的情况。



New Relic 是国外知名的监控服务商,不采用 SNMP 协议,通过自身的 Agent 实现。New Relic Agent 支持主流的 Linux 及 Windows 服务器平台。主要有:

Ubuntu 14.04 +

Debain 5 +

CentOS 及 RedHat 5 +

Windows Server 2003 +

移动互联网的带动,New Relic 同时提供了移动端的监控工具支持。截止目前,IOS 系统依然支持,但是 Android 系统则已经下架了。

2、平均负载

平均负载表示系统在一定时间内“运行进程队列”中的平均进程数量。运行队列,既不包含等待 IO,也不包含 WAIT,也没有 KILL 的进程队列集合。

在 LINUX 系统中,可以通过使用 uptime、w、top、cat /proc/loadavg 等指令,查看系统的平均负载。指令结果会显示 3 个浮点型数字,分别表示过去 1 分钟,5 分钟,15 分钟内系统的平均负载。对于系统而言,平均负载数值低于 0.7,是最为稳定安全的。

[root@iZuf68pmxwulw5vt2slnmlZ ~]# uptime

13:56:43 up 218 days, 23:11, 2 users, load average: 0.04, 0.06, 0.05

[root@iZuf68pmxwulw5vt2slnmlZ ~]# cat /proc/loadavg

0.06 0.06 0.05 1/464 6112


单核 CPU,平均负载数值间于 0.00~1.00 之间正常。



多核 CPU,平均负载数值(数字/CPU 核数)间于 0.00~1.00 之间正常。



平均负载的推荐工具 htop。

对于推荐工具 htop 而言,通过 LINUX 实际分析结果,可以得出系统下的平均负载,也可以得到系统中的“进程”使用 CPU 和 MEM 情况。

这里需要注意的是,思考一款工具是否合格,可以判断该工具对于程序运行的精确定位程度。精度越高,信息量越大,则越好。



3、GC 率和暂停时间

GC 率和暂停时间,可以从某一角度查看程序运行的效率和性能。对于这一指标,需要通过收集 GC 日志和 JVM 参数,分析得出不同指标之间的相互影响。

对于 GC 日志而言,需要通过 JVM 配置 GC Print 参数获取 GC 日志。基于 JVM 运行机制,GC 应该尽可能发生在年轻代完成,而不是老年代。

推荐工具如:jClarity Censum、GCViewer。

4、业务指标

应用程序的性能不光可以通过技术指标进行查看,也可以通过业务指标进行分析。业务指标通常包含收益、用户数。当然,这些数据可以系统自带,通过一定的图形和报表进行展示。也可以使用成熟的工具。

推荐的工具如:Grafana、The ELK stack、Datadog、Librato。

5、正常运行时间和服务运行状态

通过以上指标,分析应用程序的性能。可以通过 Pingdom 的 servlet 功能进行运行状态检查,检查应用程序的所有传输,包括数据库和 S3。

推荐工具:Pingdom。

扩展

Amazon S3(Amazon Simple Storage Service),简单存储服务,可用在 Web 上的任何位置存储和检索任意数量的数据。它能够提供 99.99%的持久性,并且可以在全球大规模传递数万亿对象。既能存储数据,又能进行数据分析,同时,亦可作为数据的备份和恢复目标。

使用 Amazon 的云数据迁移选项,客户可以轻松将大量数据移入或移出 S3。数据在存储到 S3 之后,自动采用成本更低、存储期限更长的云存储类(如 S3 Standard - Infrequent Access 和 Amazon Glacier)进行存档。


6、错误率

错误率通常指的是 HTTP 传输失败百分占比。但是该比例数值,意义不大,应当需要注意特定传输的错误率,由此精确定位出程序运行错误的具体位置,也即具体代码方法执行处。

Takipi 是一款根据错误率,获取错误线索的工具。Takipi 通过日志文件,可以获取关于服务器状态的信息,包括堆栈跟踪、源代码和变量值等等信息。

7、日志大小

系统日志随着系统运行,一直不断增加。但是当系统运行性能降低或者异常,日志大小会有变化。故可以通过日志大小这一个角度,观察系统情况。

解决办法可以通过使用 logstash 划分使用日志,并将它们发送并存储在 Splunk、ELK 或其他的日志管理工具中。

推荐工具:Splunk、Sumo Logic、Loggly。


12-执行


二、執行流程(JVM 构成)

Java 源程序(*.java)由編譯器(通过 javac.exe 命令)編譯為字節碼(*.class),在编译的过程中,由校驗器校驗代码语法是否正確。校验通过后,通過類加載器(ClassLoader)加載類信息,再由解釋器轉換爲機器碼運行執行。

類加載器(ClassLoader)通過獲取類文件(*.class)的路徑,將類加載進來,從而獲取類的信息。这里类加载器获取类文件的路径,由 CLASSPATH 环境变量属性配置。有了类加载器,可以配置 class 路径的任意位置,从而执行程序。Java 中提供过了一个类加载器 java.lang.ClassLoader。

执行引擎(Execution Engine),解析 Java 程序,分配存储空间。

本地方法接口(Native Interface),JVM 提供的支持调用本地方法的接口,也就是实现对于本地 C 函数的调用。尤其是在调用操作系统的资源上,会大量调用本地 C 函数,这在 IO 和 MutilThreading 上体现得非常清楚。往更高说,那就是 JNI 技术。

运行时数据区(Runtime Data Area),程序的真正运行在运行时数据区之中。实际上,Java 内存管理,就是对运行时数据区进行内存管理。



圖 1 程序執行流程



圖 3 運行時數據區


栈与堆的关系,可以用下图理解。



图 Java 内存管理


运行时数据区与 Java 线程对象有关。开发人员能够控制的内存空间,指的就是运行时数据区。调优也是针对该区域。而对于运行时数据区,对象共享涉及的内存更大,故调优又更加针对于堆。


13-字节码编译


Java 如何实现跨平台,即实现在不同操作系统、不同硬件平台上,无需变更代码就能够实现运行?

增加中间层(虚拟层)解决

字节码文件包含大量的字节,一个字节(8 位)可以存储 256 个指令,Java 具有 200 个左右指令。代码执行过程中,JVM 将字节码解释执行,屏蔽底层操作系统,亦可以将字节码编译执行,如果是热点代码,则通过 JIT 动态编译为机器码,提高执行效率

执行流程



字节码编译过程



用户头像

andy

关注

还未添加个人签名 2019-11-21 加入

还未添加个人简介

评论

发布
暂无评论
JVM 浅析(二)_andy_InfoQ写作社区