您的位置 首页 > 新农资讯

jvm入门,jvm newsize

JVM概述JVM全称Java Virtual Machine,它是一台虚构的计算机,在真实的计算机上实现各种功能。 JVM 位置如下图所示。 JVM 运行在操作系统之上,每个操作系统都需要不同的JVM 来运行。因此,Java程序是跨平台的,而JVM则不是。

JVM 架构图下图显示了JVM 架构。重点关注堆、堆栈和方法区域。 所谓JVM调优就是在不产生垃圾的情况下对堆、栈、本地方法栈、程序定时器进行调整。

ClassLoader ClassLoader 首先我们看一下将类加载到JVM 中的基本结构,如下图所示。

加载、连接和初始化类(理解)

加载:查找并记录类的二进制数据。

连接分为三个阶段。

验证:验证加载的类是否正确。准备工作:为类静态变量分配内存空间,并赋予默认初始值。解析:将类中的符号引用转换为直接引用。注意:Java编译成class文件时,虚拟机并不知道引用的地址,而是使用助记符来标记。类加载器必须将助记符转换为实际的直接引用。对应的直接地址。

初始化:为类中的静态变量分配正确的值。

public class Test{ public static int a=1;} 如上面代码所示,类加载过程大致如下:

1.加载:编译后的文件是.class文件,通过类加载的方式加载到JVM中。

2. 连接: (1) 类确保类文件没有问题。 (2) 分配内存区域为int类型(a=0)。 (3) 将符号引用转换为直接引用。

3.初始化:经过这个阶段的分析,我们将1赋给变量a。

静载荷分析

我们首先看一下部分代码。

package com.wunian.classloader;/** * 静态加载分析* * JVM参数* -XX:+TraceClassLoading用于跟踪并打印类加载信息* rt.jar是工厂自带的,必须在顶层Class加载loader * 分析项目启动慢的原因,快速判断某个类是否被加载*/public class ClassLoaderDemo2 { public static void main(String[ ] args) { System.out.println(MyChild1.str2) }} class MyParent1; { private static String str='Hello World!'; static{ System.out.println('MyParent1 static') }} class MyChild1 extends MyParent1 { public static String str2='Hello World ,My Child ';println('MyChild1 static');最终结果是:

MyParent1 staticMyChild1 staticHello World,My Child可以通过在启动参数VM选项中添加-XX:+TraceClassLoading参数来跟踪并打印类加载信息。这将帮助您理解类加载顺序。 MyChild1继承自MyParent1,因此当MyChild1调用str2时,在初始化之前必须先初始化其父类MyParent1,因此在初始化时会加载静态代码块。首先执行输出语句,初始化父类,初始化子类,然后加载MyChild1类的静态代码块,最后加载静态成员变量。 因此,我们可以得出结论:

当一个类被初始化时,首先初始化它的父类。当类初始化时,首先加载静态代码块,然后加载静态变量。最终负载分析

让我们首先看两段代码。

package com.wunian.classloader;/** *最终加载分析*/public class ClassLoaderDemo3 { public static void main(String[] args) { System.out.println(MyParent2.str) }} class MyParent2{ //最终常量在编译阶段放入常量池//其中常量放入ClassLoaderDemo3的常量池中。之后ClassLoaderDemo3和MyParent2就没有任何关系了。 static { System.out. println('MyParent2 static'); }}package com.wunian.classloader; import java.util.UUID; /** * 最后加载分析* * 如果编译时不能指定常量值确定后,该值不会放入方法调用类的常量池中。* 程序执行过程中,主动使用放置该常量的类。*/public class ClassLoaderDemo4 { public static void main(String[] args) { System.out.println(MyParent4.str); }} class MyParent4{ public static Final String str=UUID.randomUUID().toString(); static { System.out.println('MyParent4 static ');结果是:

hello worldMyParent4 static628c844a-e186-4287-8ac8-3809ec660f0b为什么两段看起来相似的代码却得到完全不同的结果呢?这是由常量加载机制决定的。

如果一个常量的值可以在编译时确定,则在编译阶段将该常量放入方法调用类的常量池中。之后,方法调用类和常量所在的类之间就没有关系了。因此,第一个代码不会加载MyParent2 静态代码块,并且不会打印“MyParent2 static”。反之,如果编译时无法确定某个常量的值,则该值不会放入方法调用类的常量池中,而是在程序运行时主动使用放置该常量的类。显然,代码第二部分中str的值调用了UUID类的randomUUID方法。这可确保UUID 类已初始化。所以编译时无法确定str的值,因此初始化了MyParent4类。加载静态代码块,因此首先打印“MyParent4 static”。类加载器分类

Java 虚拟机带有三个加载器。

BootStrap是根加载器,加载系统包,包括JDK核心库中的rt.jar等包。 Ext,扩展类加载器,加载多个扩展jar包中的类。系统(应用程序类)加载器Sys/App 加载您自己编写的类。要定制自己的类加载器,只需继承抽象类ClassLoader,该类通常很少使用。

家长委托机制

双亲委派模型是JVM的类加载机制。在这个模型中,除了引导加载器之外,所有其他类加载器也需要它们自己的父加载器。子加载器通过组合而不是使用继承来重用父加载器代码。当类加载器加载类文件时,它首先将类的加载委托给其父加载器,然后父加载器将其传递给顶级类加载器(引导程序)。仅当顶层无法加载时(在搜索范围内找不到此类),子加载器才会尝试加载类。 双亲委派机制可以防止Java的核心类被自定义类替换,并且在顶层加载器无法加载的情况下允许逐层加载父类。 示例代码如下。

package com.wunian.classloader; public class ClassLoaderDemo5 { public static void main(String[] args) { Object o=new Object();//jdk5附带的ClassLoaderDemo5 demo5=new ClassLoaderDemo5();//实例自定义创建一个东西。 //这里的null并不是什么都没有的意思,只是Java无法到达System.out.println(o.getClass().getClassLoader())。 //null System.out.println(demo5.getClass());getClassLoader());//AppClassLoader System.out.println(demo5.getClass().getClassLoader().getParent());//ExtClassLoader系统.out.println(demo5.getClass().getClassLoader( ).getParent ().getParent());//null //思考:为什么我定义的java.lang.String无效//jvm有它的?自己的安全机制。 //父类委托系统:如果顶层加载器无法加载,则让父类逐层加载。 //父级的委托机制可以: protected java核心类不能被自定义类替换//AppClassLoader 03 //ExtClassLoader 02 //BootStrap (top-level) 01 java.lang.String rt.jar }}Java 原生方法无法启动线程大家都知道。由于Java无法操作硬件,因此必须调用C语言库来启动线程。本质上,这意味着只要包含这个关键字,就无法到达Java作用域,只能调用底层的C语言库。 Robot类提供了许多用于鼠标和键盘交互的方法。所有这些方法都可以通过此类使用本机关键字来实现。

为什么会有土生土长的东西呢?

1995年,Java必须能够调用C和C++库,因此Java专门开辟了标记为本地方法的内存区域。

程序计数器每个线程都有自己的程序计数器。 程序计数器的内存空间很小,几乎可以忽略不计。

功能:可以看作是当前字节码执行的行号指示器。 分支、循环、跳转和异常处理都依赖于程序计数器。

如上图所示,左边是代码,右边是对应的Java字节码,“Code”栏是程序定时器,冒号右边是底层程序指令。

bipush:将int、float、String、常量值压入栈顶。

istore:将操作数堆栈中的值存储到局部变量表中。

iadd:添加。

伊穆鲁:乘法。

方法区的由来方法区(方法区)是Java虚拟机规范定义的一个可以在线程之间共享的执行数据区域,类似于堆。 方法区主要用来存储类信息、常量、字符串、静态变量、符号引用、方法代码等。 JDK1.7或更早版本

持久代:用于存储虚拟机加载类信息、常量、字符串、静态变量等,放置在持久代中。 持久代的大小是有限的,如果满了就会报OutOfMemoryError: PermGen异常。

JDK1.8以上版本

HotSpot JVM 完全去掉了持久代,加载类信息、常量、字符串、静态变量等的虚拟机都放在堆或元空间(Metaspcace)上。 Metaspace是HotSpot JVM的方法空间实现。

元空间和持久代都是JVM规范的方法区的实现。

元空间和持久代之间最大的区别是元空间不位于Java虚拟机内并使用本地内存。

设置元空间大小的JVM参数:-XX:MetaspaceSize10m

堆栈和队列

栈和队列是基本的数据结构。

栈:LIFO——后进先出(LIFO——后进先出),最后插入的元素最先输出。

队列:FIFO先进先出,最先插入的元素最先输出。

它们的结构如下图所示。

程序执行过程实际上就是压栈的过程,当栈为空时,线程终止。如下所示。

什么是堆栈?

堆栈是管理程序运行的地方,用于存储一些基本类型的值、局部变量、对象引用、方法等。

栈的优点:存储速度比堆快,仅次于寄存器,而且栈上的数据不能共享。

当使用new关键字创建对象时,实际上是在堆中创建了一个实例对象,然后在堆栈上创建了对该对象的引用,并且该引用指向堆中的实例对象。如下所示。

模拟堆栈溢出异常的代码如下:

package com.wunian.stack; /** * StackOverflow Stack Overflow*/public class StackOverflowDemo { public static void main(String[] args) { a() } //main a a a a a. stack full//java.lang .StackOverflowError private static void a() { a(); 因此,当线程终止时,堆栈的生命周期也会终止。并且线程是一致的。

堆栈原理

Java 栈的组成部分是栈帧。 每次调用函数时,都会在调用堆栈上维护一个单独的堆栈帧(stackframe)。栈帧的结构如下图所示。

下图说明了堆栈、堆和方法区之间的交互。

注:这里的栈是指JVM版本HotSpot的栈。

一些常见的JVM

HotSpot (SUN) JRockit (BEA) J9VM (IBM) 堆JVM 实例只有一个堆。您可以调整堆内存大小。 堆可以存储包含类、方法、常量和类型引用的实际信息。 堆在逻辑上分为三个部分。

新区(Young)又分为Eden区、Survivor区(s0、s1)、旧权区(Old Tenure)、永久区(Perm)。从JDK1.8开始,永久区域被元空间区域取代。它在本地内存中,而不是在JVM 中。 GC 垃圾收集主要发生在新的和高级的领域。 GC 包括常规GC 和Full GC,当堆满时会引发OutOfMemory 异常。

新生活区

新生儿区是种子诞生、生长和死亡的地方。 新区域分为伊甸园区和幸存者区。 Survivor区分为from区和to区。所有的类都是new在Eden空间中的,当Eden空间已满而程序还需要创建对象时,会触发一次轻量级GC,垃圾清理完毕后将存活的对象放入survivor中。在清理一个区域15 次之后,一些非常顽强的对象出现了,有些对象在15 次垃圾收集中幸存下来。此时,对象被发送到退休区,运行几个月后,当退休区满时,就会触发一次full GC。 假设一个项目运行一年后,整个堆空间完全被占满,有一天系统突然爆发OOM异常。此时,您必须解决OOM 问题或重新启动系统。

Sun HotSpot 虚拟机采用生成管理机制来管理堆内的内存。也就是说,不同的领域使用不同的算法。 Eden 中99% 的对象都是临时对象。

退休区

在新区域中存活15 次GC 的对象将进入退休区域。当退休空间满时,会触发完整GC。 15 次是默认值,可以更改。

永久区

持久区存储JDK本身维护的一些类和接口的元数据。这里的对象很少被垃圾回收。 如果你的系统显示OutOfMemoryError: PermGen,则意味着没有足够的持久代。可能的原因是加载了大量第三方包。

持久代因JDK 版本而异。

在JDK1.6之前,存在持久代,常量池位于方法区。

在JDK1.7中,我们有永久代,但是当我们尝试去永久代时,常量池在堆中。

从JDK1.8开始,不再有持久代,常量池被元空间取代。

注意:方法区和堆一样,都是线程共享区域,是JVM规范的逻辑部分,也称为非堆。

首先介绍堆内存调优Java环境:HotSpot、JDK1.8。

测试1(元空间在JVM内部吗?)

调整参数:

-XX:+PrintGCDetails:打印详细的垃圾收集信息。

-Xmx: 最大分配内存。默认为物理内存的1/4。

-Xms:初始分配的内存大小。默认为物理内存的1/64。

测试代码如下:

package com.wunian.oom;/** * 默认* maxMemory: 1801.0MB 虚拟机将尝试使用的最大内存量,通常为物理内存的1/4 * TotalMemory:123.0MB 虚拟机的默认内存总量会用去尝试,一般是物理内存的1/64 * * 允许自定义堆内存总量* -XX:+PrintGCDetails 打印详细的垃圾回收信息* -Xmx: 最大分配内存1/4 * -Xms: 首先分配的内存大小1/64 * * 调优参数:-Xmx1024m -Xms1024m -XX:+PrintGCDetails */public class HeapParamDemo { public static void main(String[] args) { //堆内存long maxMemory=Runtime.getRuntime () .maxMemory(); } long TotalMemory=Runtime.getRuntime().totalMemory(); System.out.println('maxMemory='+maxMemory+'bytes,'+(maxMemory/1024/) (double)1024)+'MB' );println('totalMemory='+totalMemory+'bytes,'+(totalMemory/(double)1024)+'MB');如图所示。

计算显示,虚拟机将尝试使用的最大内存量=新区域总内存+旧区域总内存,说明元空间不在JVM中。

测试2(模拟OOM)

可调参数有:-Xms8m -Xmx8m -XX:+PrintGCDetails

测试代码如下:

package com.wunian.oom;import java.util.Random;/** * * * 调参参数:-Xms8m -Xmx8m -XX:+PrintGCDetails * * GC日志分析: * [PSYoungGen: 1534K-504K(2048K)] 1534K - 656K (7680K), 0.0472431 秒] [Times: user=0.00 sys=0.00, real=0.06 秒] * 1.GC 类型GC: 正常GC FullGC: 重度GC * 2.1534K GC 前大小* 3.504K GC 后大小* 4 (2048K) 总计size of youngGen * 5.0.0472431 秒] 清理时间* 6.user GC 占用的总CPU 时间sys OS 调用等待时间实际应用程序暂停时间* * GC: 串行执行STW (Stop The World) G1 并行执行*/public class OOMDemo { public static void main(String[] args) { //java.lang.OutOfMemoryError: Java 堆空间String str='OOM 异常'; while (true){str+=str+new Random( ) + new Random().nextInt(999999999) } }} 程序运行时,出现java.lang.OutOfMemoryError: Java堆空间异常。这是因为字符串无限增长。新的学生区已经满了。

如果我正在运行一个转储内存快照的Java 程序,并且我想测试它的运行情况,可以使用以下几种工具来查看:

jconsole(包含在JDK 中) IDEA 的调试模式Eclipse 的MAT 插件IDEA 的JProfiler 插件JProfiler 插件

JProfiler是一个性能瓶颈分析插件。

安装说明:

1.IDEA安装JProfiler插件。在Settings中搜索Plugins,搜索JProfiler,下载并安装插件,然后重启IDEA。

2. 在Windows 上安装JProfiler。请注意,安装目录路径不能包含字符或空格。

3. 激活注册码。

4、IDEA绑定JProfiler,在设置中搜索JProfiler,选择位于JProfiler安装目录下bin目录下的jprofiler.exe文件作为JProfiler可执行文件。

立即体验JProfiler 插件

首先,模拟OOM异常。调整参数为: -Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError 代码是:

package com.wunian.oom;import java.util.ArrayList;import java.util.List;import java.util.concurrent.TimeUnit;/** * 转储内存快照* * jconsole、JProfile 测试* * -Xmx10m - Xms10m - XX:+HeapDumpOnOutOfMemoryError //如果JVM发生OOM,自动生成DUMP文件*/public class DumpDemo { byte[] bytes=new byte[1*1024*1024]; public static void main( String[ ] args) throws InterruptedException { /*System.out.println('start'); TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);*///DumpDemo dumpDemo=new DumpDemo(); 尝试{while(true){ list.add(new DumpDemo()); count++;} } catch (Throwable e) {//Throwable 或ErrorSystem.out.println('count='+count ) );e .printStackTrace() ; } }} 当您运行该程序时,您会注意到项目目录中自动生成了一个DUMP 文件。使用JProfiler 打开它并分析DUMP 文件。或者,您也可以直接点击IDEA右上角的JProfiler图标。过一会儿,JProfiler会自动打开并监控当前程序的执行状态。如果您觉得使用JProfiler过于复杂,还可以直接打开位于JDK bin目录下的jconsole.exe,连接当前程序的监听端口,监控当前程序的执行状态。

本站涵盖的内容、图片、视频等数据,部分未能与原作者取得联系。若涉及版权问题,请及时通知我们并提供相关证明材料,我们将及时予以删除!谢谢大家的理解与支持!

Copyright © 2023