• 【面试】JVM


    关于内存、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的相关信息,对于这些区域测试的基本思路:运行时产生大量的类去填满方法区,直到溢出。

    参考:

    https://www.cnblogs.com/kivi/p/3197825.html

    https://blog.csdn.net/wang740209668/article/details/65474752

    https://www.cnblogs.com/doit8791/p/5820037.html

    写出几个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时,执行一个脚本,比如发送邮件报警,重启程序。后面跟着一个脚本的路径。

  • 相关阅读:
    网络--会话层、表示层、应用层
    Unix C
    操作系统原理
    TCP/UDP
    config OSX firewall programmatically
    Objective-C 浅谈
    OS X background process
    CreateProcessWithToken 1058 error
    WebBrowser keystroke
    MFC加载PNG图片并实现双缓冲
  • 原文地址:https://www.cnblogs.com/Rain1203/p/11214898.html
Copyright © 2020-2023  润新知