jvm相关

jvm基础

垃圾回收机制

  1. 为什么会有垃圾回收机制?
    • 安全性考虑
    • 减少内存泄露
    • 见扫程序员工作量
  2. 哪些内存需要回收?
    jvm的方法区和堆。程序计数器、虚拟机栈、本地方法栈是每个线程私有的内存框架,随线程而生,而死。方法区和堆只有在程序运行期间才知道会创建多少对象,所以这部分内存的分配和回收是动态的,垃圾回收关注的主要是这部分。
  3. GC什么时候回收垃圾?
    一个对象是否还存在引用,如果该对象没有任何引用就该被回收。
    引用分为:

    • 强引用Object object=new Object()(只要强引用存在,gc就不会回收被引用的对象,即使抛出内存溢出也不回收)
    • 软引用SoftReference r=new SoftReference(object);软引用非常适合于创建缓存。当系统内存不足的时候,缓存中的内容是可以被释放的。(系统发生内存溢出了,才会进行回收),
    • 弱引用WeakReference(如果gc扫描到弱引用就会被回收)
    • 虚引用PhantomReference(如果这个对象只有一个虚引用,和没有引用一样,任何时候都可能被回收)。
  4. 导致内存溢出的几种情况

    • 内存中加载的数据量过大,如一次去从数据库取过多的数据;
    • 代码中存在死循环,产生过多重复的实体对象;
    • 启动参数内存值设定的太小
  5. jvm类加载器包括几种?父子关系是?双亲委派机制?

    启动类加载(BootStrap)、扩展类(Extension)类加载器、应用程序类加载器(系统类加载器)(Application ClassLoader);

    • 启动类加载器,由c++是实现按,没有父类,主要加载的时jvm自身需要的类,是加载/lib下的类库,并且仅按照文件名识别如rt.jar;
    • 扩展类加载器,由java语言实现,父类加载器为null,主要加载/lib/ext下的类库;
    • 系统加载器,由java语言实现,父类加载器为扩展类加载器;主要加载指定路径下的类库,就是我们经常用到的classpath路径下的;
    • 自定义类加载器,父类加载器为AppClassLoder.

双亲委派机制:父加载器收到类的请求,自己不加载,向上委托给父类,父类加载不了再自己加载;

假如我们自己也写了一个java.lang.Object类,也在classthpath下,那么系统中就会出现多个不同的Object类,我们都知道Object类是所有类的父类,现在我们自己也建立了一个包名相同的Object,岂不是乱套了。
所以,双亲委派模型对于保证Java程序的稳定运行很重要。原理也不是很复杂:先检查该类是否被加载过,若没有加载则调用父类的加载器的load()方法,若父类加载器为空则默认使用启动类记载器作为父加载器。如果父加载器加载失败报ClassNotFound异常后,再调用自己的findClass()加载。就不会导致一个类被加载多次。

package java.lang;
/**
*自己写的 java.lang.Object
*/
public class Object {
    public static void main(String[] args) {
        Object o=new Object();
        System.out.println("sss");
    }
}

输出:错误: 在类 java.lang.Object 中找不到 main 方法, 请将 main 方法定义为:
   public static void main(String[] args)
   否则 JavaFX 应用程序类必须扩展javafx.application.Application
//说明调用的是jdk自己的Obejct而不是我们写的;当加载我们写的这个类的时候(我们没有自己写加载器)默认是调用Application ClassLoader,这是系统类加载器,会将我们的请求提交到启动类加载器,而启动类加载器默认是加载<JAVA_HOME>/lib下类库,(不熟悉的同学可以点开自己的jdk下面的rt.jar(library root),里面有java.lang这个包)所以加载的是自己的Obejct.
  1. 类加载的五个过程

    • 加载:通过一个类的全限定名查找此类的class字节码文件并加载到内存中,然后利用字节码文件在堆中创建一个代表这个类的java.lang.Class对象,作为方法区类数据的访问路口,这个过程需要类加载器参与;
    • 验证:目的是要确保class文件的字节流中包含信息符合虚拟机的要求,不会危害虚拟机的安全;
    • 准备:为类变量分配内存并设置该类变量的初始值(int=5,这一步则设为0,初始化时赋值为5)这里不包含用final修饰的static,因为final在编译的时候就会分配了;
    • 解析:虚拟机常量池的符号引用替换为直接引用,直接引用就是直接指向目标的执政、相对偏移量等;
    • 初始化:类加载的最后一个阶段,若该类具有父类则进行初始化,执行静态初始化器和静态初始化成员变量;

    java运行时数据区域

    java虚拟机在执行java程序的过程中会把它锁管理的内存坏分为若干个不同的数据区域。这些区域都有各自的用途。以及各自创建和销毁时间,有的区域随着虚拟机的启动而存在,有的区域则以来用户线程的启动和借宿而建立和销毁。

    • 程序计数器
      是一块较小的内存框架。它可以看作是当前线程锁执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、循环等基础功能都需要以来这个计数器来完成。
      由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,在任何一个确定的时刻,一个处理器都只会执行一条线程中的一条指令。因此,为了新程切换后能恢复到正确的位置,每条线程需要有一个独立的 程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存称为“线程私有”的内存。
    • java虚拟机栈
      与程序计数器一样,java虚拟机栈也是线程私有的,它的生命周期和线程相同,虚拟机栈描述的是java方法执行的内存模型;每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数、动态链接、方法出口等信息。每个方法从调用直至执行完成的工厂,就对应一个栈帧在虚拟机中入栈到出栈的过程。
      局部变量表存放了编译期可知的各种数据类型、对象引用和returnAddress类型(指向一条字节码指令的地址)。
      局部变量表所需的内存空间在编译期间就完全确定了,64位的long和double数据会占用两个局部变量空间,其余的数据类型值占用一个。当一个方法进入时,这个方法需要在帧中分配多大的局部变量空间时完全确定的。
    • 本地方法栈
      本地方法栈与虚拟机栈的作用极为相似,他们直接按的区别不过是虚拟机栈位虚拟机执行java方法,而本地方法栈是位native(如c/c++)方法服务。
    • java堆
      对于大对数应用来说,java堆是java虚拟机管理内存中最大的一块。java堆是所有线程共享的一块区域。在虚拟机启动的时候,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都分配在这里。这一点在java虚拟机规范中的秒速是:所有对象实例及数组都要在堆上分配,但是随着jit(即时)编译器的发展等因素,所有对象都在对上分配也渐渐变得不是那么“绝对”了。
      java堆是垃圾收集器管理的主要区域,因此很对时候也被称“GC堆”,幸好没翻译成“垃圾堆”;从内存回收的角度来看,由于现在收集器基本采用分代收集算法,所以java还可以细分为:新生代和老年代;从内存分配的角度来看,可以划分为多个线程私有的分配缓冲池。
      根据java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间,逻辑上连续即可。
    • 方法区
      方法区和java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码数据等。(方法区可以被垃圾回收的两种情况:
      1.当一个常量对象不再任何地方被引用的时候,则标记为废弃常量,这个常量可被回收;
      2.无用类的回收
    • 运行时常量池
      是方法区的一部分。运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,不同之处是:它的字面量可以动态的添加(String#intern()),符号引用可以被解析为直接引用。
      JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。
什么是字面量和符号引用:

字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;
符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。

class常量池简介:

我们写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);
每个class文件都有一个class常量池。

字符串常量池在Java内存区域的哪个位置?
  • 在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;
  • 在JDK7.0版本,字符串常量池被移到了堆中了。至于为什么移到堆内,大概是由于方法区的内存空间太小了。
字符串常量池是什么?
  • 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。
  • 在JDK6.0中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降;
    在JDK7.0中,StringTable的长度可以通过参数指定:
字符串常量池里放的是什么?
  • 在JDK6.0及之前版本中,String Pool里放的都是字符串常量;
  • 在JDK7.0中,由于String#intern()发生了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用。

总结

  • 栈:(局部变量)保存基本数据类型的值、保存类的实例对象的引用
  • 堆:(动态产生的数据)对象实例、数组;每个对象的成员变量
  • 方法区(永久的数据):类的常量、静态变量
  • 字符串常量池(在堆中):String s=”sss”;sss就在字符串常量池
  • 运行时常量池(方法区的一部分,每个类都有):
    • 1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;
    • 1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。

注:
- 以上内容部分来自与https://blog.csdn.net/zm13007310400/article/details/77534349
- 欢迎指正错误~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值