如果多个Java线程同时访问创建同一个对象的构造器,此时构造器方法的调用情况如下:
- 对象创建过程的原子性:
- 尽管有多个线程同时试图创建同一个对象,但JVM会保证对象创建过程的原子性。也就是说,在任意时刻只会有一个线程成功地完成对象的创建。
- 线程安全的对象创建:
- 由于对象创建是一个原子操作,因此即使多个线程同时访问同一个构造器,也不会出现线程安全问题,如数据竞争或者其他并发问题。
- 线程阻塞:
- 当有多个线程同时试图创建同一个对象时,JVM会自动对后来的线程进行阻塞,直到第一个线程完成对象的创建。这样可以确保只有一个线程成功创建对象。
- 对象引用返回:
- 成功创建对象的线程会返回一个指向该对象的引用。其他被阻塞的线程在获得CPU时间片后,会再次尝试创建对象,但由于对象已经被创建,它们会直接返回之前创建的对象引用。
总之,即使有多个线程同时访问同一个构造器,Java的对象创建机制能够保证线程安全,确保只有一个线程成功创建对象。构造器本身不需要同步化,JVM会自动处理这类并发创建对象的情况。
在 Web 服务中,每个 HTTP 请求到达后端时,是否会启动一个新的线程来处理任务,这个取决于所使用的Web应用服务器的具体实现。
通常情况下,Web 应用服务器会采用下面两种主要的线程处理模型:
- 每请求一个线程(Thread-per-request):
- 这种模型中,每个 HTTP 请求都会启动一个新的线程来处理任务。
- 当请求到达时,服务器会为该请求分配一个新的线程,当请求处理完成后,线程会被释放回线程池。
- 这种模型适用于请求处理相对独立,计算量不大的情况。
- 线程池(Thread Pool):
- 这种模型中,服务器会维护一个固定大小的线程池,当请求到达时,服务器会从线程池中分配一个空闲线程来处理请求。
- 如果线程池中没有空闲线程,请求会被放入队列等待处理。
- 这种模型适用于请求处理需要大量计算资源,或者存在共享资源的情况。
具体使用哪种模型,取决于所使用的 Web 应用服务器的设计。比如 Tomcat 默认使用线程池模型,Jetty 则默认使用每请求一个线程的模型。
不同的线程处理模型会对应用程序的性能和扩展性产生影响,开发者需要根据应用的具体需求,选择合适的线程处理模型。
8.下面有关java的一些细节问题,描述错误的是? A一个子类不可以覆盖掉父类的同步方法 B定义在接口中的方法默认是public的 C容器保存的是对象的引用 D构造方法不需要同步化
答案是A,错误的描述是 “一个子类不可以覆盖掉父类的同步方法”。
在Java中,子类可以覆盖掉父类的同步方法。子类可以使用相同的方法名和参数列表来定义一个同步方法,从而覆盖父类的同步方法。子类可以使用synchronized关键字来定义同步方法,它将覆盖父类中的同步方法。
其他选项描述正确。在接口中定义的方法默认是public的,容器保存的是对象的引用,构造方法不需要同步化,因为在创建对象时它们是单线程执行的
HashMap和HashTable都是用于存储键值对的数据结构,但它们之间有一些区别。
- 线程安全性:HashTable是线程安全的,而HashMap不是。在多线程环境下,使用HashTable可以避免并发访问的问题,而HashMap需要通过外部同步来保证线程安全。
- 性能:由于线程安全的额外开销,HashTable的性能通常比HashMap要差。
- null值:HashMap允许key和value为null,而HashTable不允许。
- 继承关系:HashMap继承自AbstractMap类,而HashTable继承自Dictionary类,这意味着HashMap可以被视为一个集合类,而HashTable则是一个遗留类。
综上所述,虽然HashMap和HashTable在功能上很相似,但在实际应用中,HashMap更常用,因为它在性能和灵活性上都优于HashTable。
2.String和StringBuffer的区别? 3.数据库内连接和外连接的区别? 4.Select语句中,哪些情况下,虽然在列上建立了索引,但索引不起作用? 5.在Unix系统当前的test目录中查找所有以test开头的文件?
- String和StringBuffer的区别在于String是不可变的,每次对String进行操作都会生成一个新的String对象,而StringBuffer是可变的,可以直接对其进行修改而不会生成新的对象。
- 数据库内连接和外连接的区别在于内连接只返回两个表中共同符合条件的数据,而外连接则会返回一个表中所有的数据,以及另一个表中符合条件的数据。
- 在Select语句中,索引可能不起作用的情况包括:在对索引列进行函数操作时、进行类型转换时、在使用通配符进行模糊查询时、在对索引列进行计算或者使用表达式时。
- 在Unix系统中,可以使用以下命令来查找所有以test开头的文件:
find /path/to/test -name 'test*'
其中,/path/to/test是test目录的路径。
声明别名的 as 关键字可以省略
SELECT empid,dept,salary FROM employee outer
WHERE salary>(SELECT min(salary) FROM employee inner
WHERE inner.dept=outer.dept)
- SELECT - 用于指定需要查询的字段,这里是empid、dept和salary。
- FROM employee outer - 这是外层的查询,主要查询employee表中的数据。
- WHERE salary > (subquery) - 这个WHERE子句是外层查询的条件,用于筛选出薪资高于内层子查询结果的员工记录。
- (SELECT min(salary) FROM employee inner WHERE inner.dept=outer.dept) - 这是一个嵌套的子查询(subquery)。它的作用是,在内层查询中找到每个部门的最低薪资,然后外层查询将这个结果作为条件,筛选出薪资高于所在部门最低薪资的员工。
- min(salary) - 聚合函数,用于找到每个部门的最低薪资。
- FROM employee inner - 内层的查询,针对employee表。
- WHERE inner.dept=outer.dept - 关联条件,将内层和外层的部门字段关联起来。
总的来说,这个SQL语句使用了嵌套子查询的方式,先找到每个部门的最低薪资,然后在外层查询中筛选出薪资高于本部门最低薪资的员工信息。这种查询方式可以满足比较复杂的业务需求。
牛客错题本
Java基础
java运算符优先级 参考答案: 答:此题考查运算符优先级。 题中符号的优先级排序是:’>’,’<’,’&&’,’||’。 即 b=(x>50&&y>60)||(x>50&&y<-60)||(x<-50&&y>60)||(x<-50&&y<-60); x>50结果为0,x<-50结果为0,所以括号中的表达式结果都为0,四个0或的结果0。 b为boolean类型,所以输出为false。 知识点:Java
方法的重写(override)两同两小一大原则: 方法名相同,参数类型相同 子类返回类型小于等于父类方法返回类型, 子类抛出异常小于等于父类方法抛出异常, ** 子类访问权限大于等于父类方法访问权限**
- 一般依赖把被依赖的名字放前面,继承把父类名字放后面
- GenericServlet类实现ServletConfig接口
- HttpServlet是GenericServlet的实现类,GenericServlet是一个抽象类,继承了servlet接口,ServletConfig配置信息接口 java.io.serializable序列化接口
在Myeclipse中敲了一下,确实ABDE都可以。 也就是说数组命名时名称与[]可以随意排列,但声明的二维数组中第一个中括号中必须要有值,它代表的是在该二维数组中有多少个一维数组。
类的构造方法:(1)无返回值; (2)其名称与本类名称相同。
答案:C 关于方法的重载:
方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。调用重载方法时,Java编译器能通过检查调用的方法的参数类型和个数选择一个恰当的方法。方法重载通常用于创建完成一组任务相似但参数的类型或参数的个数不同的方法。 方法重载具体规范
一.方法名一定要相同。
二.方法的参数表必须不同,包括参数的类型或个数,以此区分不同的方法体。
三.方法的返回类型、修饰符可以相同,也可不同。
instance是java的二元运算符,用来判断他左边的对象是否为右面类(**接口,抽象类,父类)的实例 **
A:设置HTTP头标
response.setHeader(“Refresh”,”3”); //三秒刷新页面一次
B:设置cookie
Cookie c1 = new Cookie("username","only");
response.addCookie(c1);
C(错误):读取路径信息,request读取路径信息
从request获取各种路径总结
request.getRealPath("url"); // 虚拟目录映射为实际目录
request.getRealPath("./"); // 网页所在的目录
request.getRealPath("../"); // 网页所在目录的上一层目录
request.getContextPath(); // 应用的web目录的名称
D:输出返回数据
HttpServleteResponse.getOutputStream().write();
HttpServletResponse完成:设置http头标,设置cookie,设置返回数据类型,输出返回数据;读取路径信息是HttpServletRequest做的
内部类
为什么需要变量定义为final 内部类不会因为定义在方法中就随着方法执行完毕而被销毁
A首先,外部类和内部类是处于同一个级别的,内部类不会因为定义在方法中就随着方法执行完毕而被销毁。那么问题来了,如果外部类的方法中变量不定义为final,那么外部类的方法执行完毕后,这个局部变量肯定被gc了。这时候内部类的某个方法还没执行完,却找不到他引用的外部变量了。如果定义为final,Java就会将这个变量复制一份作为成员变量置于内部类中,这样的话,final修饰的值始终无法改变,所以这个变量所指向的内存区域就不会变。
C. 匿名内部类用法与局部内部类不一致,首先从定义上就不一样,匿名类用在任何允许存在表达式的地方,而局部内部类用于在任何允许出现局部变量的地方出现。 还有更重要的是匿名类只能使用一次,而局部类则可以在自己的定义域内多次使用。 D. 错。静态内部类不能直接访问外部类的非静态成员,但可以通过new外部类().成员的方式访问。
内部类详解
- 成员内部类: 地位与类成员相同
- 局部内部类:与局部变量地位相同
- 匿名内部类:匿名内部类是唯一一种没有构造器的类
- 静态内部类:静态内部类不依赖于外部类,不能引用外部类的static成员变量和方法
多线程-锁
A应该是”任何”那错了,比如在一个static方法中就无法对一个非static对象加锁
因为使用volatile修饰的变量会告诉编译器,该变量可能会被多个线程同时访问,从而禁止编译器对该变量进行优化,确保每个线程都能获取到最新的值。因此,这种变量是线程共享的。
private volatile String a = "lock";
public static void f() {
synchronized (a) {} //错误的,无法获取该对象的锁
}
JVM内存参数
参考答案:D Java虚拟机常用的参数 -Xmx:堆最大容量 -Xms:堆初始大小 -Xmn:新生代大小 -Xss:每个线程堆栈大小 -XX:PermSize:持久代大小 -XX:MaxPermSize:持久代最大值 -XX:SurvivorRatio:Eden和Survivor大小比值 -XX:NewRatio:年轻代与老年代大小比值 -XX:+HeapDumpOnOutOfMemoryError:内存溢出时自动Dump出当前的内存堆转储快照 -XXSurvivorRatio:年轻代中Eden区与Survivor区的大小比值 Edan:fromSurvivor:toSurvivor=3:1:1, 年轻代5120m, Eden:Survivor=3,Survivor区大小=1024m(Survivor区有两个,即将年轻代分为5份,每个Survivor区占一份),总大小为2048m。 -Xms初始堆大小即最小内存值为10240m 知识点:2015、Java、Java工程师
-Xmx:最大堆大小 |
-Xms:初始堆大小 |
-Xmn:年轻代大小
说明:年轻代是由 Eden区 和Survivor区 两部分组成的堆内存区域。
-XXSurvivorRatio=3
表示的含义是:Eden与Survivor的占用比例为 3:1。
也就是说,一个survivor区占用 1/3
的Eden内存,而新生代中都有2个survivor,
所以survivor总共占用新生代内存的
2/5,同时Eden与新生代的占比则为 3:5。
-------------------------------分割线--------------------------------------
- 知识补充:JVM内存区域总体分两类,heap区 和 非heap区 。
- heap区:Eden Space(伊甸园)、SurvivorSpace(幸存者区)、Tenured Gen(老年代-养老区)。
- 非heap区: Code Cache(代码缓存区)、
Perm Gen(永久代)、
JvmStack(java虚拟机栈)、
Local Method Statck(本地方法栈)
声明:这是从网上拼凑而成,有问题请指正(来源:http://blog.csdn.net/wy5612087/article/details/52369677)
-Xmx:最大堆大小
-Xms:初始堆大小。(有的也叫最小堆大小,此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。)
-Xmn:年轻代大小
-XXSurvivorRatio:年轻代中Eden区与Survivor区的大小比值
HotSpot JVM(注意是HotSpot Jvm
不是所有jvm,某一种)把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor
GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。
所谓的
Copying算法
是空间换时间,而
Mark-Compact算法
则是时间换空间。 (
http://hllvm.group.iteye.com/group/topic/28594
)
因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收
算法
使用的是复制算法(
Copying算法
)。
Copying算法:(
http://edu.gamfe.com/tutor/d/14130.html,
)
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
Mark-Compact算法:
在工作的时候则需要分别的mark与compact阶段,mark阶段用来发现并标记所有活的对象,然后compact阶段才移动对象,清楚未标记对象来达到compact的目的。如果compact方式是sliding compaction,则在mark之后就可以按顺序一个个对象“滑动”到空间的某一侧。因为已经先遍历了整个空间里的对象图,知道所有的活对象了,所以移动的时候就可以在同一个空间内而不需要多一份空间
A、java异常和错误的基类Throwable,包括Exception和Error B、try…catch…finally finally不管什么异常都会执行 C、java是面向对象的,但是不是所有的都是对象,基本数据类型就不是对象,所以才会有封装类的; D、如果是等待清理队列中如果又被调用,则不会执行finallize方法 E、采用高版本的JDK编写的程序,在低版本的JRE中无法运行。 F、synchronized实现方式:三种
Synchronized修饰非静态方法,实际上是对调用该方法的对象加锁,俗称“对象锁”。
Synchronized修饰静态方法,实际上是对该类对象加锁,俗称“类锁”。
synchronized作用在非静态成员方法上锁的是此对象,作用在静态成员方法上锁的是此类对象
** 就是说静态方法锁的对象是整个类,因为静态方法从属于类不属于对象,所以一个静态同步方法执行了,其他静态同步方法无法获取到锁,因为他们都共享整个类的唯一类对象。实例方法不用说如果锁的是this或者非静态成员对象,那不同的实例拿到的锁都不一样**大家互不影响。 (加锁时非静态方法加锁的对象是调用方法的实例,静态方法加锁的对象是类对象)
Spring中的事务属性种类
事务的四种属性
- 传播行为
- 隔离级别
- 只读
- 事务超时
传播行为
事务的传播行为是指在方法调用链中,当一个事务方法调用另一个方法时,被调用方法如何处理事务的策略。 在 Spring 框架中,可以通过传播行为属性控制事务的传播行为,例如 “required”(如果有事务则加入,没有则新建), “supports”(支持当前事务,没有则不控制)、”mandatory”(必须在一个事务中执行)等。这些传播行为属性可以在方法级别或类级别上进行设定。通过适当设置传播行为属性,可以确保方法之间的事务处理关系得到正确的处理,保证事务操作的一致性和完整性。 在 Spring 框架中,事务的传播行为有以下几种:
- REQUIRED(默认值):如果当前存在事务,则加入该事务,如果当前没有事务,则新建一个事务。
- REQUIRES_NEW:新建一个事务,如果当前存在事务,则将当前事务挂起。
- SUPPORTS:支持当前事务,如果当前不存在事务,则以非事务方式执行。
- ** MANDATORY:必须在一个已存在的事务中执行,否则会抛出异常。**
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将其挂起。
- NEVER:以非事务方式执行,如果存在事务,则会抛出异常。
- NESTED:如果当前存在事务,则在该事务中嵌套一个事务进行执行,如果当前没有事务,则新建一个事务。
PROPAGATION_SUPPORTS 从翻译上看--仅仅支持事务,没有就没有呗 PROPAGATION_REQUIRED 支持事务,如果有,就用着,没有的话就自己弄一个 PROPAGATION_REQUIRES_NEW 比较有个性,不管有没有,都自己弄一个(把原来的放在一边) PROPAGATION_MANDATORY 强制必须要有事务,要是没有,就不开心了(抛出异常) PROPAGATION_NOT_SUPPORTED 不支持事务,有就挂起来 PROPAGATION_NEVER 此处事务与狗不得入内,如果有,就赶出去(抛出异常)
在数据库事务处理中,"required_new"是一种事务传播行为的设置选项。 意思是说,如果设置为"required_new",那么当一个新事务被调用时, 如果当前已经存在一个事务在执行,那么当前事务会被挂起(暂停执行), 新事务将会以独立的形式被新建并执行。这样做可以确保每个事务的独立性, 避免不同事务之间的干扰, 同时也保证了事务的数据完整性和一致性。
``` PROPAGATION_REQUIRED表示被调用方法会加入当前事务, 与当前事务并行执行; 而PROPAGATION_REQUIRES_NEW表示被调用方法会创建一个新的独立事务, 与当前事务隔离开来。 两者的区别在于事务的隔离性和执行顺序。 PROPAGATION_REQUIRED会与当前事务并行执行, PROPAGATION_REQUIRES_NEW会将当前事务挂起,确保被调用方法独立执行。
当前事务会被挂起的原因是, PROPAGATION_REQUIRES_NEW指定的事务传播行为是新建一个独立事务, 与当前事务隔离开来。 因此,为了确保不会发生事务间的干扰和资源冲突, 当前事务会被挂起,等新建的事务执行完毕后再恢复当前事务的执行。 这样可以保证事务的独立性和安全性。
###### 传播行为的设置
在Spring中,可以使用@Transactional注解来设置方法的事务传播属性。@Transactional注解可以加在方法上,也可以加在类上,用来对整个类的所有方法设置事务属性。
举例来说,对于一个方法,可以在方法声明前加上@Transactional注解,并通过设置propagation属性来指定事务的传播行为,如下所示:
```java
@Transactional(propagation = Propagation.REQUIRED)
public void doSomething() {
// 执行数据库操作
}
在这个例子中,@Transactional注解的propagation属性被设置为Propagation.REQUIRED,表示如果当前存在事务,则加入该事务;如果当前没有事务,则新建一个事务。
隔离级别
经典错误读取
隔离级别理解 脏读、不可重复读和幻读是数据库事务隔离级别中常见的问题,它们分别表示不同的数据一致性问题:
- 脏读(Dirty Read):脏读指一个事务中访问到了另一个事务未提交的数据。也就是说,在一个事务未完成之前,另一个事务可以读取到该事务的数据,如果事务执行失败或被回滚,那么读取到的数据就是无效的。脏读会导致数据的不一致性。
举例:假设有一个银行系统,用户A正在转账时,用户B读取到了用户A的账户余额,在用户A完成转账前,用户B可能会看到错误的余额。
- 不可重复读(Non-Repeatable Read):不可重复读指在一个事务中,同一记录在事务执行过程中被多次读取,但在读取的过程中另一个事务修改了这条记录,导致多次读取得到的数据不一致。不可重复读会导致数据的不一致性。
举例:假设一个事务首先读取某个订单的数据,然后再次读取相同的订单数据时,发现订单总金额被另一个事务修改了,导致读取到的数据不一致。
- 幻读(Phantom Read):幻读指在一个事务中执行两次相同的查询,但第二次查询返回的数据集合比第一次查询返回的数据集合多。这是因为在两次查询之间有另一个事务执行了插入、删除等操作,导致第二次查询返回的数据发生了变化。幻读会导致查询结果的不一致性。
举例:假设一个事务查询某个商品列表,第一次查询时返回了10个商品,然后在第二次查询时,发现商品列表增加到了12个,这是由于另一个事务在此期间新增了两个商品导致的。
脏读、不可重复读和幻读是数据库事务隔离级别中的三种典型问题,它们之间的区别在于导致数据不一致性的原因和表现形式:
- 脏读(Dirty Read):
- 原因:脏读是由于一个事务读取了另一个未提交事务的数据所导致的。
- 表现:一个事务读取到了另一个事务尚未提交的数据,若另一个事务回滚,则读取到的数据是无效的。
- 不可重复读(Non-Repeatable Read):
- 原因:不可重复读是由于在一个事务内,同一记录在事务执行过程中被更新或删除而导致的。
- 表现:一个事务对同一记录进行多次查询,在两次查询之间,另一个事务修改或删除了这条记录,导致两次查询的结果不一致。
- 幻读(Phantom Read):
- 原因:幻读是由于在一个事务内,同一个范围的数据发生了新增或删除操作所导致的。
- 表现:一个事务查询一定范围的数据,在两次查询之间,另一个事务插入、删除了符合查询条件的数据,导致第二次查询返回的结果比第一次查询返回的结果多或少。
总结:
- 脏读是读取了未提交的数据,不可重复读是对同一数据的多次读取得到的结果不一致,幻读是在同一个范围内多次查询得到的结果不一致。三者的区别主要在于数据的变化原因和表现形式。
Spring提供隔离级别
| 隔离级别 | 意义 | | — | — | | ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 | | ISOLATION_READ_UNCOMMITTED 读未提交 | 允许读取未提交的数据(对应未提交读),可能导致脏读、 不可重复读、幻读 | | ISOLATION_READ_COMMITTED 读已提交 | 允许在一个事务中读取另一个已经提交的事务中的数据(对应已提交读)。 可以避免脏读,但是无法避免不可重复读和幻读 | | ISOLATION_REPEATABLE_READ 可重复读 | 一个事务不可能更新由另一个事务修改但尚未提交(回滚)的数据(对应可重复读)。 可以避免脏读和不可重复读,但无法避免幻读 | | ISOLATION_SERIALIZABLE 序列化 | 这种隔离级别是所有的事务都在一个执行队列中,依次顺序执行, 而不是并行(对应可序列化)。可以避免脏读、不可重复读、幻读。 但是这种隔离级别效率很低,因此,除非必须,否则不建议使用。 |
只读
如果在一个事务中所有关于数据库的操作都是只读的,也就是说,这些操作只读取数据库中的数据,而并不更新数据,那么应将事务设为只读模式(READ_ONLY_MARKER),这样更有利于数据库进行优化。 因为只读的优化措施是事务启动后由数据库实施的,因此,只有将那些具有可能启动新事务的传播行为(PROPAGATION_NESTED、PROPAGATION_REQUIRED、PROPAGATION_REQUIRED_NEW)的方法的事务标记成只读才有意义。 如果使用Hibernate作为持久化机制,那么将事务标记为只读后,会将Hibernate的flush模式设置为FULSH_NEVER,以告诉Hibernate避免和数据库之间进行不必要的同步,并将所有更新延迟到事务结束。
事务超时
如果一个事务长时间运行,这时为了尽量避免浪费系统资源,应为这个事务设置一个有效时间,使其等待数秒后自动回滚。与设置“只读”属性一样,事务有效属性也需要给那些具有可能启动新事物的传播行为的方法的事务标记成只读才有意义。
类加载过程
参考答案:不应该选D,
而应该选B 类的加载包括:加载,验证,准备,解析,初始化。 **
选项A:生成java.lang.Class对象是在加载时进行的。生成Class对象作为方法区这个类的各种数据的访问入口。
选项B:既然是对象成员,那么肯定在实例化对象后才有。在类加载的时候会赋予初值的是类变量,而非对象成员。
选项C:这个会调用。可以用反射试验。 选项D:类方法解析发生在解析过程。
**知识点:Java、Java工程师
A. 生成java.lang.Class对象: 加载阶段
B. int类型对象成员变量赋予默认值:使用阶段(不属于类加载阶段)
C. 执行static块代码:初始化阶段(执行类构造器
类加载过程
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中准备、验证、解析3个部分统称为连接(Linking)。 加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。以下陈述的内容都已HotSpot为基准。
加载
在加载阶段(可以参考java.lang.ClassLoader的loadClass()方法),虚拟机需要完成以下3件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流(并没有指明要从一个Class文件中获取,可以从其他渠道,譬如:网络、动态生成、数据库等);
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
加载阶段和连接阶段(Linking)的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的内容,这两个阶段的开始时间仍然保持着固定的先后顺序。
验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 验证阶段大致会完成4个阶段的检验动作:
- 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以魔术0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
- 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
- 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
- 符号引用验证:确保解析动作能正确执行。
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:
| 1 | publicstaticintvalue=123; | | — | — |
那变量value在准备阶段过后的初始值为0而不是123.因为这时候尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。 至于“特殊情况”是指:public static final int value=123,即当类字段的字段属性是ConstantValue时,会在准备阶段初始化为指定的值,所以标注为final之后,value的值在准备阶段初始化为123而非0.
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
初始化
类初始化阶段是类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的java程序代码。在准备极端,变量已经付过一次系统要求的初始值,而在初始化阶段,则根据程序猿通过程序制定的主管计划去初始化类变量和其他资源,或者说:初始化阶段是执行类构造器