博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JVM
阅读量:5237 次
发布时间:2019-06-14

本文共 4494 字,大约阅读时间需要 14 分钟。

关于内存、JVM、类加载机制

about conception:

 

JVM内存模型:

 

从大的方面讲,JVM的内存模型分为两大块:

永久区内存(Permanent space)和堆内存(heap space)。

栈内存(stack space)一般不归在JVM内存模型中,因为栈内存属于线程级别。

每个线程都有个独立的栈内存空间。

Permanent space是存放加载的Class类级对象 如class本身,method,field等等。

heap space主要存放对象实例和数组。

heap space由new Generation和old Generation组成,old Generation存放生命周期长久的实例对象,而新的对象实例一般存放在New Generation。

New Generation还可以再分为Eden。Survivor区,新的对象实例总是首先放在Eden区,Survivor区作为Eden区和old区的缓冲,可以向old区转移活动的对象实例。

下图是JVM在内存空间(堆空间)中申请新对象过程的活动图:

没错,我们常见的OOM(out of memory)内存溢出异常,就是堆内存空间不足以存放新对象实例时导致。

永久区内存溢出相对少见,一般是由于需要加载海量的class数据,超过了堆内存的容量导致。通常出现在web应用刚刚启动时,因此web应用推荐使用预加载机制,方便在部署时就发现并解决该问题。

栈内存也会溢出,但是更加少见。

堆和栈:

java栈是与每一个线程关联的,JVM在创建每一个线程的时候,会分配一定的栈空间给线程。它主要用来存储线程执行过程中的局部变量,方法的返回值,以及方法调用上下文。栈空间随着线程的终止而释放。StackOverflowError:如果在线程执行的过程中,栈空间不够用,那么JVM就会抛出此异常,这种情况一般是死递归造成的。

Java堆是由所有的线程共享的一块内存区域,堆用来保存各种java对象,比如数组,线程对象等。

堆和栈分离的好处:面向对象的设计,当然除了面向对象的设计带来的维护性,复用性和扩展性方面的好处外,我们看看面向对象如何巧妙的利用了堆栈分离。如果从java内存模型的角度去理解面向对象的设计,我们就会发现对象它完美的表示了堆和栈,对象的数据放在堆中,而我们编写的方法一般是运行在栈中的,因此面向对象的设计是一种非常完美的设计方式,它完美的统一了数据的存储和运行。

堆内存优化:

调整JVM启动参数-Xms -Xmx -XX:newSize -XX:MaxNewSize,如调整初始堆内存和最大堆内存 -Xms256M,-Xmx512M,或者调整初始New Generation的初始内存和最大内存 -XX:newSize=128M -XX:MaxNewSize=128M。

永久区内存优化:

调整PermSize参数 如-XX:PermSize=512M -XX:MaxPermSize=512M。

栈内存优化:

调整每个线程的栈内存容量,如 -Xss2028K。

最终,一个内存中JVM所占的内存=堆内存+永久区内存+所有线程所占的栈内存总和。

 

 

一个bean被new出来后,在内存空间的走向?

Student s = new Student();

1)加载Student类文件到栈内存,开辟空间;

2)在栈内存为s开辟空间;

3)在堆内存为Student对象开辟空间;

4)给Student的成员变量分配默认值;

5)如果成员变量有给定值则用给定值覆盖默认值;

6)通过构造方法给成员变量赋值;

7)把Student对象在堆内存的地址值赋给s变量。

成员变量的初始化在构造函数之前。

 

类加载器:

java类装载方式:

1)隐式装载:程序在运行过程中碰到通过new等方式生成对象时,隐式调用类加载器加载对应的类到jvm中;

2)显示装载:通过class.forName()等方式,显示加载需要的类。

 

类加载的动态体现:

一个应用程序是由n多个类组成,java程序启动时,并不是一次性把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其他类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统开发的,内存宝贵,这是一种可以理解的机制,而用到时再加载也是java动态性的一种体现。

 

java类装载机制:

1)Bootstrap loader:Bootstrap加载器是用C++写的,它是在虚拟机启动后初始化的,主要负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classess中的类;

2)ExtClassLoader:BootStrap loader加载ExtClassLoader,并且将ExtClassLoader的父加载器设置为Bootstrap loader。ExtClassLoader是用java写的,主要加载%JAVA_HOME%/jre/lib/ext路径下所有的classes目录以及java.ext.dirs系统变量指定的路径中类库。

3)AppClassLoader:BootStrap Loader加载完ExtClassLoader后,就会加载AppClassLoader,并且将AppClassLoader的父加载器指定为ExtClassLoader。AppClassLoader也是用java写的。classLoader中有个getSystemClassLoader方法,此方法返回的正是AppClassLoader,AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,是java程序默认的类加载器。

关系如图;

 

为什么要有三个类加载器,一方面是分工,各自负责各自的区域,另一方面是为了实现委托模型。

 

类加载器之间是如何协调工作的?

java采用了委托模型机制,这个机制简单来说,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent找不到,那么才由自己按照自己的搜索路径搜索类”。

 

描述一下JVM加载class文件的原理机制?

【加载、验证、准备、解析、初始化、使用、卸载 7个阶段】

1)装载:查找和导入Class文件;

2)链接:其中解析步骤是可以选择的;

  • 检查:检查载入的class文件数据的正确性
  • 准备:给类的静态变量分配存储空间
  • 解析:将符号引用转成直接引用

3)初始化:对静态变量,静态代码块执行初始化工作。

java装载类使用“全盘负责委托机制”,“全盘负责”是指当一个ClassLoader装载一个类时,除非显示的使用另外一个ClassLoader,该类所依赖及引用的类也由这个ClassLoader载入;“委托机制”是指先委托父类装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。这一点是从安全方面考虑的,试想一个人如果写了一个恶意的基础类(java.lang.String)并加装到JVM将会引起严重的后果,但有了全盘负责制,java.lang.String永远是由根装载器来装载,避免了以上情况的发生。除了JVM默认的三个ClassLoader外,第三方可以编写自己的类加载器,以实现一些特殊的需求。类文件被装载解析后,在JVM中有一个对应的java.lang.Class对象,提供了类结构信息的描述。数组、枚举,及基本数据类型,甚至void都拥有对应的Class对象。Class类没有public的构造方法,Class对象是在装载类时由JVM调用类装载器中的defineClass()方法自动构造的。

 

为什么要使用这种双亲委托模式呢?

主要有两方面的原因,第一是避免重复加载,当父加载器已经加载了该类的时候,就没必要子加载器再来加载一次;

另一方面就是考虑到安全因素,我们试想一下,如果不使用这种委托机制,我们可以随时使用自定义的String动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。

 

定义自己的ClassLoader?

java中提供的默认类加载器,只能加载指定目录下的jar和class,如果我们想加载其他位置的类或者jar时,比如,我要加载网络上的一个class文件,通过动态加载到内存之后,要调用这个类中的方法实现我的业务逻辑。在这种情况下,默认的类加载器已经不能满足我们的需求了,需要定义自己的ClassLoader。

定义自己的类加载器分为两步:

1)继承java.lang.ClassLoader;

2)重写父类的findClass方法。

 

如何让栈溢出,如何让方法区溢出?

栈溢出:死循环;

方法区溢出:借助CGLib使方法区出现内存溢出异常。方法区用于存放Class的相关信息,对于这些区域测试的基本思路:运行时产生大量的类去填满方法区,直到溢出。

参考:

 

写出几个JVM优化配置参数?

-Xms:初始堆大小。只要启动,就占用的堆大小。

-Xmx:最大堆大小。java.lang.OutOfMemoryError:Java heap这个错误可以通过配置-Xms和-Xmx参数来设置。

-Xss:栈大小分配。栈是每个线程私有的区域,通常只有几百K大小,决定了函数调用的深度,而局部变量、参数都分配到栈上。当出现大量局部变量,递归时,会发生栈空间OOM(java.lang.StackOverflowError)之类的错误。

-XX:NewSize=n :设置新生代大小的绝对值。

-XX:NewRatio=n :设置年轻代和年老代的比值。比如设置为3,则新生代:老年代=1:3,新生代占总heap的1/4。

-XX:MaxPermSize=n :设置持久代大小。java.lang.OutOfMemoryError:PermGenspace这个OOM错误需要合理调大PermSize和MaxPermSize大小。

-XX:SurvivorRatio=n :年轻代中Eden区与两个Survivor区的比值。注意,Survivor区有form和to两个。比如设置为8时,那么eden:form:to=8:1:1。

-XX:HeapDumpOnOutOfMemoryError:发生OOM时转储堆到文件,这是一个非常好的诊断方法。

-XX:HeapDumpPath:导出堆的转储文件路径。

-XX:OnOutOfMemoryError:OOM时,执行一个脚本,比如发送邮件报警,重启程序。后面跟着一个脚本的路径。

 

转载于:https://www.cnblogs.com/Rain1203/p/11214898.html

你可能感兴趣的文章
015 spel
查看>>
POJ 1511 Invitation Cards ( 双向单源最短路 || 最小来回花费 )
查看>>
C陷阱与缺陷(一)
查看>>
grunt切换下载源
查看>>
FASTDFS安装
查看>>
Java基础--JDBC
查看>>
悲剧的升到了ios8
查看>>
js实现等待n秒后--按钮可用
查看>>
关于C++ const 的全面总结
查看>>
C#学习笔记(六):可空类型、匿名方法和迭代器
查看>>
Python自动化运维开发实战 一、初识Python
查看>>
img添加预加载图片
查看>>
noip2014普及组——珠心算测验
查看>>
韩信点兵的算术题目
查看>>
struts2的fastjson,jackson转换json简单使用
查看>>
oracle创建新数据库
查看>>
笔记本 F1 键盘
查看>>
初识c语言
查看>>
config.go
查看>>
.net程序员,该不该学IL?
查看>>