深入Java虚拟机(JVM)_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > 深入Java虚拟机(JVM)

深入Java虚拟机(JVM)

 2018/11/11 0:27:59  bijian1013  程序员俱乐部  我要评论(0)
  • 摘要:一.Java整体的运行结构以及与JVM的关系1.类加载器在JDK1.8以前和JDK1.9以后不管版本如何变化,双亲加载依然是使用的主体,不可能改变。packagecom.bijian.study;publicclassTestClassLoaderDemo{publicstaticvoidmain(String[]args){Stringstr="";System.out.println(str.getClass().getClassLoader());//Bootstrap加载器}}运行结果
  • 标签:Java 虚拟机 JVM

一.Java 整体的运行结构以及与 JVM 的关系


1.类加载器在 JDK 1.8 以前和 JDK 1.9 以后

  不管版本如何变化,双亲加载依然是使用的主体,不可能改变。

class="java">package com.bijian.study;

public class TestClassLoaderDemo {
	
	public static void main(String[] args) {
		
		String str = "";
		System.out.println(str.getClass().getClassLoader()); // Bootstrap加载器
	}
}

  运行结果:null

package com.bijian.study;

class Member {
}

public class TestClassLoaderDemo {
	public static void main(String[] args) {
		Member member = new Member();
		System.out.println(member.getClass().getClassLoader()); // Bootstrap 加载器
		System.out.println(member.getClass().getClassLoader().getParent());// Bootstrap加载器
		System.out.println(member.getClass().getClassLoader().getParent().getParent());
	}
}

  JDK1.8及以下版本运行结果:

sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742
null

?  JDK1.9及以上版本运行结果:

jdk.internal.loader.ClassLoaders$AppClassLoader@4b85612c
jdk.internal.loader.ClassLoaders$PlatformClassLoader@66133adc(改变:ExtClassLoader)
null

?  其中,null就是bootstrap 加载器,可以看到JDK1.9的类加载器已经发生了改变。

2.运行时数据区是整个JVM设计的关键所在,那么在整个运行时数据区里面,就有若干个组成部分

  栈内存:是程序的运行单位,里面存储的信息都使与当前线程有关的内容,包括:局部变量、程序的运行状态、方法返回值。

  堆内存:Java 的引用传递的实现依靠的就是堆内存,同一块堆内存空间可以被不同的栈内存所指向。

  程序计数器:是一个非常小的内存空间,这个空间主要是进行一个计数的操作,对象的晋升问题(依靠的就是计数器)。

  方法栈内存:在进行递归调用的时候所保存的栈帧的内容;

|- 组成部分:局部变量表、操作数栈、当前方法所属于类的运行时产量的引用、返回地址。

在整个 JVM 运行时数据区之中,关键的部分在于需要进行堆的优化,既然要进行优化,那么就必须清除 Java 的对象访问模式。Java 在进行对象引用的时候并没有使用到句柄的概念(步骤多一些,导致性能下降),它直接采用的 HotSpot虚拟机标准的指针引用。

Java 是一个开源的编程语言,实际上在世界的技术公司里面有三个所谓的虚

拟机标准:

· SUN(被 Oracle 收购了):所推出的 JVM 是基于 HotSpot 标准的虚拟

机;

· BEA(被 Oracle 收购了):JRockit;

· IBM(曾经打算收购 SUN 公司):JVM's、J9。

Oracle 不可能花费额外费用去维护两个虚拟机标准,所以未来的发展趋势:

HotSpot + JRockit,而现在所使用的 JVM 实际上也全部都是 HotSpot 标准,执

行:java -version

java version "11" 2018-09-25

Java(TM) SE Runtime Environment 18.9 (build 11+28)

Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, mixed mode)

一般都不需要去改变所谓的 JVM 运行模式,但是有一点需要清楚,当前的

Java 行业里面,Java 已经不再适合于进行桌面程序开发了,也就是说客户端程序

不是 Java 的重点了,那么这样一来对于资源的启动分配就非常重要了。

默 认 的 JDK 的 配 置 使 用 的 全 部 是 服 务 器 的 运 行 模 式 :

/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home/lib/jvm.cfg

-server KNOWN

-client IGNORE

二、堆内存组织结构以及与内存有关的调整参数。

JVM 的组成只是做为一个概念存在,如果每一天都只是进行 JVM 结构研究

对开发的作用很小,最关键的问题就是优化:堆内存空间。

package cn.mldn.jvm;

public class TestGCDemo {

public static void main(String[] args) {

String str = "" ;

for (int x = 0 ; x < Integer.MAX_VALUE ; x ++) {

str += x + str ; // 多么万恶

str.intern() ; // 万恶加三级

}

}

}

堆内存之中需要考虑关于 GC 的问题,真正导致程序变慢的原因就在于堆内

存的控制策略上。控制回收策略(JDK 1.9 之后的默认策略已经非常好了,因为

其已经更换为了 G1)

1、年轻代

· 伊甸园区:新生的小对象。每当使用关键字 new 的时候默认的时候都会在

此空间内进行对象创建。

|- 如果创建的对象过多,那么最终的结果也有可能造成伊甸园区的内存

空间沾满,所以此时就会发生晋级的操作(若干次 MinorGC 执行还保留的对象,

晋升到存活区)。

· 存活区:进行一些 GC 后保存的对象(程序计数器,会记录 GC 的执行次

数),存活区准备两块空间:S0、S1,有一块空间永远都是空的,是向老年代晋

升。

2、老年代

哪些又臭又硬的对象,这些对象都已经经历了无数次的 GC 之后依然被保留

下来的对象。于是这些对象很难被清除。但是有可能也会被清除。同时如果是一

个很大的对象,那么默认的也会直接保存到老年代,如果现在老年代空间不足了,

会出现 MajorGC(FullGC),进行老年代的清理(这样的清理是非常耗费性能的),

所以这也是为什么不去使用 System.gc()方法。

3、于是现在最核心的问题在于:如何可以进行堆的结构优化。

· 每一块的空间实际上都会提供有一个伸缩区;

· 伸缩区的考虑是在某个内存空间不足时:会自动打开伸缩区继续扩大可用

的内存,当发现当前的区域的空间内存可以满足要求的时候,就可以进行收缩。

|- 如果不进行收缩的优点:可以提升堆内存分配效率;

|- 如果不进行收缩的缺点:空间太大了,那么如果没有选择好合适的 GC

算法,就会造成堆内存的性能下降。

package cn.mldn.jvm;

public class ShowSpaceDemo {

public static void main(String[] args) {

Runtime runtime = Runtime.getRuntime() ; // 获取 Runtime 实例化对象

System.out.println("MAX_MEMBER:" + runtime.maxMemory()); //

最大可用内存

System.out.println("TOTAL_MEMBER:" + runtime.totalMemory()); //

默认的可用内存

}

}

maxMemory:默认大小为当前物理内存的“1 / 4”、4294967296

totalMemory:默认大小为当前物理内存的“1 / 64”、268435456

伸缩区有这么大的处理范围,所以在进行堆内存分配的过程里面当用户访问

量增加的时候就一定会导致不断的判断空间是否充足,不断的进行内存空间的增

长,不断的进行内存空间的收缩于释放。

至关重要的两个参数:可以使用的单位(k、m、g)

-Xms:设置初始化的内存分配大小;

-Xmx:设置最大的可用内存空间。

-Xms16g -Xmx16g

可以减少堆内存的收缩处理操作。

当堆内存空间很大得情况下就需要考虑到 GC 的执行效率问题。

年轻代:

所以在这个环节里面就需要考虑两个技术名词:BTP、TLAB

· BTP:在伊甸园区采用栈的形式将最晚创建的对象保存在栈顶。

· TLAB:分块保存,适合于多线程的处理操作上。

-Xmn:设置年轻代的空间大小,默认采用的时物理内存的“1 / 64”

-Xss:设置每一个线程所占用的栈的线程

-X:SurvivorRatio:设置伊甸园区与两个存活区之间的内存分配比,默认“8 :

1 : 1”。

老年代:

与年轻代比率:-XX:NewRatio

当对象很大的时候往往不在年轻代进行保存,而是直接晋级到老年代,利用

“-XX:PretenureSizeThreshold”。

【分水岭】JDK1.8 之后取消了所谓的永久代,而变为了元空间(不在堆内存里面

保存,而是直接利用物理内存保存。)

三、GC 算法(主流:G1、未来:ZGC)

GC 算法的选择直接决定了你最终程序的执行性能。

传统意义上进行的回收处理操作,只是认为简单的有垃圾产生了,而后自动

进行 GC 操作(MinorGC、MajorGC),或者手工利用“System.gc()”操作(MajorGC、

FullGC)。

Java 的 GC 机制是经历快了 20 年的发展,对于电脑硬件技术也已经产生了

很大得变化,最初的时候是在一块 CPU 上进行多线程的分配,而现在手机都多

核 CPU,多线程支持了。

对于 GC 算法里面就需要考虑不同的内存分代(新的 JDK 开发版本之中,以

及现在项目里面不建议再使用如下的 GC 算法):

· 年轻代 GC 策略:串行 GC、并行 GC;

· 老年代 GC 策略:串行 GC、并行 GC。

【年轻代串行 GC】

· 扫描年轻代中得所有存活对象;

· 使用 MinorGC 进行垃圾回收,同时将还能够存活下来的对象保存在存活区

(S0、S1)里面;

· 每一次进行 MinorGC 的时候都会引起 S0 和 S1 的交换;

· 经过若干次 MinorGC 还能够继续保存下来的就进入到老年代。

【年轻代并行 GC】

· 算法:复制-清理算法,在扫描和复制的时候均采用多线程的处理模式来完

成。

在年轻代进行 MinorGC 的时候实际上也由可能触发到老年代 GC 操作。

【老年代串行 GC】

算法:标记-清除-压缩;

扫描老年代中的存活对象 ,并且进行对象的标记;

遍历整个老年代的内存空间,回收所有标记对象;

为了保证可以方便的计算出老年代的大小,还需要进行压缩(碎片整理,把

空间集中在一起。)

【老年代并行 GC】

· 在最早的时候主要使用了此种 GC 算法,但是这种算法会有一个严重性的

问题:STW(产生中断,因为需要进行垃圾的标记)。

|- 暂停当前的所有执行程序(挂起);

|- 标记出垃圾,标记的时间越长,那么挂起的时间就越长,如果此时你

的堆内存空间很大,那么时间一定会更长;

|- 预清除处理;

|- 重新标记过程:看看还有没有垃圾;

|- 进行垃圾的处理;

|- 程序恢复执行。

以前使用的:-Xms48m -Xmx48m -XX:+PrintGCDetails

替换后使用:-Xms48m -Xmx48m -Xlog:gc*

【砍掉】串行 GC:-Xms48m -Xmx48m -Xlog:gc* -XX:+UseSerialGC

【砍掉】并行 GC:-Xms48m -Xmx48m -Xlog:gc* -XX:+UseParallelGC

【砍掉】并行年轻代 GC:-Xms48m -Xmx48m -Xlog:gc* -XX:+UseParallelNewGC

【砍掉】并行老年代 GC:-Xms48m -Xmx48m -Xlog:gc* -XX:+UseParallelOldGC

最终的 GC 发展到了今天,已经不单单再以上的古老算法了,不管是并行还是串

行算法,实际上都有可能引起大范围的程序暂停问题(程序的性能不高),现在

最关键的问题就需要去解决大空间下的性能问题。

最初的电脑是没有这么高的硬件配置的,内存最早出现的时候售卖的单位是 K,

这样的背景下就产生了 G1 回收算法(现在 JDK 1.9 之后的标配算法),支持的最

大内存为 64G(每一个小得区域里面可以设置的范围“1 - 32”)

G1 收集:-Xms48m -Xmx48m -Xlog:gc* -XX:+UseG1GC

JVM 核心优化问题:

· 减少伸缩区的使用;

· 提升 GC 的效率,G1 是现在最好用的 GC 算法。

· 线程的栈的大小配置;

· 线程池的配置。

如果现在是在 Tomcat 下那么如何优化呢?

JAVA_OPTS="-Xms4096m -Xmx4096m -Xss1024K"

?

特别说明:此文章是《开课吧》公开课的笔记

  • 大小: 129.7 KB
  • 大小: 70.6 KB
  • 查看图片附件
上一篇: Java通讯模型-BIO、NIO、AIO综合演练 下一篇: 没有下一篇了!
发表评论
用户名: 匿名