-
【功能模块】【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
-
【功能模块】使用Java语言,本地build_and_run以及zip.sh运行均没有问题,提交之后显示选手程序运行失败,希望后台能够帮忙查找一下原因,十分感谢!【操作步骤&问题现象】使用Java语言,本地build_and_run以及zip.sh运行均没有问题,提交之后显示选手程序运行失败,希望后台能够帮忙查找一下原因,十分感谢!【截图信息】【日志信息】(可选,上传日志内容或者附件)
-
-
使用java的JNI包调用c语言库实现,例如:编写java:package JNI;public class sm3{static{ System.load("/home/minqian/java/sm3.so"); //使用绝对路径加载名称为sm3.so的库文件}public native static void sm3(); //native关键字public static void main(String[] args){ sm3 test = new sm3(); test.sm3();}}编译java文件为class: javac -d . sm3.java根据class文件生成.h文件:javah -classpath . -jni JNI.sm3编写c文件,实现引用第3步中生成的.h头文件,并实现其中声明的方法:#include "JNI_sm3.h"#include <openssl/bio.h>#include <openssl/ssl.h>#include <openssl/err.h>#include <openssl/engine.h>JNIEXPORT void JNICALL Java_JNI_sm3_sm3(JNIEnv *env, jclass jc){ /* Initializing OpenSSL */ SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms(); /*You can use ENGINE_by_id Function to get the handle of the Huawei Accelerator Engine*/ ENGINE *e = ENGINE_by_id("kae"); /*使能加速引擎异步功能,可选配置,设置为“0”表示不使能,设置为“1”表示使能,默认使能异步功能*/ ENGINE_ctrl_cmd_string(e, "KAE_CMD_ENABLE_ASYNC", "1", 0) /*指定引擎用于SM3摘要算法*/ENGINE_init(e); /*The user code*/ …. const EVP_MD *md_sm3 = EVP_sm3(); EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(md_ctx, md_sm3, e); EVP_DigestUpdate(md_ctx, buf_in, buf_len); EVP_DigestFinal_ex(md_ctx, buf_out, &out_len); EVP_MD_CTX_free(md_ctx); …. ENGINE_free(e);}将.c文件编译生成so文件:gcc -shared -Wl,-soname,sm3 -D_REENTRANT -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -L/usr/local/lib -lssl -lcrypto -o sm3.so -fPIC sm3.c注:$JAVA_HOME/include 是jni.h头文件所在的路径 $JAVA_HOME/include/linux是jni_md.h所在的路径6、编译生成的.class文件即可执行调用到.so库
-
【问题现象】编译也成功了,但是上传出现“打开选手输出文件失败”,相对路径和绝对路径都用过了,都是这个问题,。。【截图信息】
-
【问题描述】编程语言为Java 本地idea上测试没问题,也能输出solution.txt文件 ,但是上传报编译错误。于是在本地的Linux系统上编译,也出现异常,然后拿什么都没动过的Demo也测试,发现还是这样。哭死。。。。。【截图信息】
-
【功能模块】【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件) java可以引入jar包吗?
-
请问java输出打包成的zip文件下需要包含什么?一个jar包吗
-
面向对象三大特性:继承,封装,多态封装3中修饰符:public,private,protected,给位于同一个或不同包中的对象赋予了不同的访问权限封装的一些好处通过隐藏对象的属性来保护对象内部的状态提高代码的可用性,可维护性提高模块化继承给对象提供从基类获取字段和方法的能力,基础提高代码的重用性,可以在不修改类的情况下添加新的特性多态多态就是同一函数在不同类中有不同的实现;面向对象的多态性,即“一个接口,多个方法”。多态性体现在基类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式。多态性允许一个接口被多个同类使用,弥补了单继承的不足。final,finally,finalize的区别finalfinal可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个 常量不能被重新赋值。finallyfinally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码放入finally代码块中,表示不管是 否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。finalizefinalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调 用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。int和integer的区别intint是基本数据类型integerinteger是其包装类,是一个类为了在各种类型中转换,通过各种方法调用int a = 0; String result = Integer.toString(a); //将int转换为String重载与重写的区别override重写overload重载抽象类和接口的区别接口时公开的,不能有私有的方法和变量,抽象类可以有私有的方法或私有的变量抽象类和接口的区别反射的用途以及实现反射机制所提供的功能在运行时创造一个类的对象;判断一个类所具有的成员变量和方法调用一个对象的方法生成动态代理Java反射的主要功能:确定一个对象的类取出类的modifiers,数据成员,方法,构造类,超类在运行时刻调用动态对象的方法.创建数组,数组大小和类型反射机制的理解和应用自定义注解的场景及实现登陆、权限拦截、日志处理,以及各种Java 框架,如Spring,Hibernate,JUnit 提到注解就不能不说反射,Java自定义注解是通过运行时靠反射获取注解。实际开发中,例如我们要获取某个方法的调用日志,可以通过AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志 注解,就进行日志记录。http请求的get和post方式的区别原理区别一般我们在浏览器输入一个网址访问网站都是GET请求;再FORM表单中,可以通过设置Method指定提交方式为GET或者POST提交方式,默认为GET提交方式。HTTP定义了与服务器交互的不同方法,其中最基本的四种:GET,POST,PUT,DELETE,HEAD,其中GET和HEAD被称为安全方法,因为使用GET和HEAD的HTTP请求不会产生什么动作。不会产生动作意味着GET和HEAD的HTTP请求不会在服务器上产生任何结果。但是安全方法并不是什么动作都不产生,这里的安全方法仅仅指不会修改信息。根据HTTP规范,POST可能会修改服务器上的资源的请求。比如CSDN的博客,用户提交一篇文章或者一个读者提交评论是通过POST请求来实现的,因为再提交文章或者评论提交后资源(即某个页面)不同了,或者说资源被修改了,这些便是“不安全方法”。请求的区别GET方法会把名值对追加在请求的URL后面。因为URL对字符数目有限制,进而限制了用在 客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方式传递。POST方法通过把请求参数值放在请求体中来克服GET方法的限制,因此,可以发送的参数的数目是没有限制的。最后,通过POST请求传递的敏感信息对外部客户端是不可见的。参考:get和post请求方式的区别seesion与cookie的区别cookie数据存放在客户的浏览器上,session数据放在服务器上.cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。设置cookie时间可以使cookie过期。但是使用session-destory(),我们将会销毁会话。session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie。单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。(Session对象没有对存储的数据量的限制,其中可以保存更为复杂的数据类型)JDBC流程加载JDBC驱动程序:在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机), 这通过java.lang.Class类的静态方法forName(String className)实现。例如:try{ //加载MySql的驱动类 Class.forName("com.mysql.jdbc.Driver") ; }catch(ClassNotFoundException e){ System.out.println("找不到驱动程序类 ,加载驱动失败!"); e.printStackTrace() ; 成功加载后,会将Driver类的实例注册到DriverManager类中。提供JDBC连接的URL连接URL定义了连接数据库时的协议、子协议、数据源标识。书写形式:协议:子协议:数据源标识协议:在JDBC中总是以jdbc开始 子协议:是桥连接的驱动程序或是数据库管理系统名称。数据源标识:标记找到数据库来源的地址与连接端口。例如:jdbc:mysql://localhost:3306/test? useUnicode=true&characterEncoding=gbk;useUnicode=true;(MySql的连接URL)表示使用Unicode字符集。如果characterEncoding设置为 gb2312或GBK,本参数必须设置为true 。characterEncoding=gbk:字符编码方式。创建数据库的连接java.sql.DriverManager Connection代表一个数据库的连接。使用DriverManager的getConnectin(String url , String username , String password )方法传入指定的欲连接的数据库的路径、数据库的用户名和 密码来获得。例如: //连接MySql数据库,用户名和密码都是rootString url = "jdbc:mysql://localhost:3306/test" ; String username = "root" ; String password = "root" ; try{ Connection con = DriverManager.getConnection(url , username , password ) ; }catch(SQLException se){ System.out.println("数据库连接失败!"); se.printStackTrace() ; • 要执行SQL语句,必须获得java.sql.Statement实例,Statement实例分为以下3 种类型:1、执行静态SQL语句。通常通过Statement实例实现。2、执行动态SQL语句。通常通过PreparedStatement实例实现。3、执行数据库存储过程。通常通过CallableStatement实例实现。具体的实现方式:Statement stmt = con.createStatement() ; PreparedStatement pstmt = con.prepareStatement(sql) ; CallableStatement cstmt = con.prepareCall("{CALL demoSp(? , ?)}") ;执行SQL语句Statement接口提供了三种执行SQL语句的方法:executeQuery 、executeUpdate 和execute1、ResultSet executeQuery(String sqlString):执行查询数据库的SQL语句 ,返回一个结果集(ResultSet)对象。2、int executeUpdate(String sqlString):用于执行INSERT、UPDATE或 DELETE语句以及SQL DDL语句,如:CREATE TABLE和DROP TABLE等3、execute(sqlString):用于执行返回多个结果集、多个更新计数或二者组合的 语句。 具体实现的代码:ResultSet rs = stmt.executeQuery(“SELECT * FROM …”) ; int rows =stmt.executeUpdate(“INSERT INTO …”) ; boolean flag = stmt.execute(String sql) ;处理结果两种情况:1、执行更新返回的是本次操作影响到的记录数。2、执行查询返回的结果是一个ResultSet对象。• ResultSet包含符合SQL语句中条件的所有行,并且它通过一套get方法提供了对这些 行中数据的访问。• 使用结果集(ResultSet)对象的访问方法获取数据:while(rs.next()){String name = rs.getString(“name”) ;String pass = rs.getString(1) ; // 此方法比较高效}(列是从左到右编号的,并且从列1开始)关闭JDBC对象操作完成以后要把所有使用的JDBC对象全都关闭,以释放JDBC资源,关闭顺序和声 明顺序相反:1、关闭记录集2、关闭声明3、关闭连接对象 if(rs!=null){ // 关闭记录集 try{ rs.close(); }catch(SQLException e){e.printStackTrace(); } }try{ stmt.close(); } }catch(SQLException e){e.printStackTrace(); } if(conn!=null){ // 关闭连接对象 try{ conn.close(); }catch(SQLException e){ e.printStackTrace();20} }MVC思想M:Model 模型V:View 视图C:Controller控制器模型就是封装业务逻辑和数据的一个一个的模块,控制器就是调用这些模块的(java中通常是 用Servlet来实现,框架的话很多是用Struts2来实现这一层),视图就主要是你看到的,比如JSP 等.当用户发出请求的时候,控制器根据请求来选择要处理的业务逻辑和要选择的数据,再返回去 把结果输出到视图层,这里可能是进行重定向或转发等.equals 与 == 的区别值类型(int,char,long,boolean等)都是用==判断相等性。对象引用的话,判断引用所指的对象 是否是同一个。equals是Object的成员函数,有些类会覆盖(override)这个方法,用于判 断对象的等价性。例如String类,两个引用所指向的String都是"abc",但可能出现他们实际对应的对象并不是同一个(和jvm实现方式有关),因此用 ==判断他们可能不相等,但用equals判断一定是相等的。线程创建线程的方法及实现Java中创建线程主要有三种方式:一、继承Thread类创建线程类(1) 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。(2) 创建Thread子类的实例,即创建了线程对象。(3) 调用线程对象的start()方法来启动该线程。package com.thread; public class FirstThreadTest extends Thread{ int i = 0; //重写run方法,run方法的方法体就是现场执行体public void run() { for(;i<100;i++){ System.out.println(getName()+" "+i); } } public static void main(String[] args) { for(int i = 0;i< 100;i++) { System.out.println(Thread.currentThread().getName()+" "+i): if(i==20) { new FirstThreadTest().start(); new FirstThreadTest().start(); } } }二、通过Runnable接口创建线程类(1) 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。(2) 创建 Runnable实现类的实例的target来创建Thread对象,该Thread对象才是真正的线程对象。(3)调用线程对象的start()方法来启动该线程package com.thread; public class RunnableThreadTest implements Runnable { private int i; public void run() { for(i = 0;i <100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); } } public static void main(String[] args) { for(int i = 0;i < 100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); if(i==20) { RunnableThreadTest rtt = new RunnableThreadTest(); new Thread(rtt,"新线程1").start(); new Thread(rtt,"新线程2").start(); } } } } 三、通过Callable和Future创建线程(1) 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。(2) 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(3) 使用FutureTask对象作为Thread对象的target创建并启动新线程。(4) 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值package com.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); 13 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " 的循 环变量i的值" + i); if (i == 20) { new Thread(ft, "有返回值的线程").start(); } } try { System.out.println("子线程的返回值:" + ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception 34 { int i = 0; for (; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } return i; } }采用实现Runnable、Callable接口的方式创见多线程时,优势是:线程类只是实现了 接口或 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同 一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面 向对象的思想。劣势是:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。使用继承Thread类的方式创建多线程时优势是:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this 即可获得当前线程。劣势是:线程类已经继承了Thread类,所以不能再继承其他父类。sleep() 、join()、yield()的区别一、sleep()在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度 程序精度和准确性的影响。 让其他线程有机会继续执行,但它并不释放对象锁。也就是如 果有Synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一 个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级 的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的 线程有执行的机会。二、join()Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前, B不能工作。保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有 存活,则当前线程不需要停止。三、yield()yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方 法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态 后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也 和sleep()方法不同。再给大家推荐一本书:Java并发编程想要电子版也可以私信我线程池的几种方式newFixedThreadPool(int nThreads)创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数 量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程newCachedThreadPool()创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当 需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制newSingleThreadExecutor()这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会 创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行newScheduledThreadPool(int corePoolSize)创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。private static final Executor exec=Executors.newFixedThreadPool(50); Runnable runnable=new Runnable(){ public void run(){ ... } } exec.execute(runnable); Callable<Object> callable=new Callable<Object>() { public Object call() throws Exception { return null; } } Future future=executorService.submit(callable); future.get(); // 等待计算完成后,获取结果 future.isDone(); // 如果任务已完成,则返回 true future.isCancelled(); // 如果在任务正常完成前将其取消,则返回 true future.cancel(true); // 试图取消对此任务的执行,true中断运行的任务,false 允许正在运行的任务运行完成线程的生命周期新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态(1)生命周期的五种状态新建(new Thread)当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。例如:Thread t1=new Thread();就绪(runnable)线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队 等候得到CPU资源。例如:t1.start();运行(running)线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有 优先级更高的线程进入,线程将一直运行到结束。死亡(dead)当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态 等待执行。自然终止:正常运行run()方法后终止异常终止:调用**stop()**方法让一个线程终止运行堵塞(blocked)由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。正在等待:调用wait()方法。(调用motify()方法回到就绪状态)被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)锁机制线程安全线程安全是指要控制多个线程对某个资源的有序访问或修改,而在这些线程之间没有产生冲 突。在Java里,线程安全一般体现在两个方面:1、多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体现在关 键字synchronized。如ArrayList和Vector,HashMap和Hashtable(后者每个方法前都有synchronized关键字)。如果你在interator一个List对象时,其它线程remove一个element, 问题就出现了。2、每个线程都有自己的字段,而不会在多个线程之间共享。它主要体现在java.lang.ThreadLocal类,而没有Java关键字支持,如像static、transient那样。volatle实现原理深入分析 Volatile 的实现原理悲观锁,乐观锁是一种思想。可以用在很多方面。比如数据库方面。悲观锁就是for update(锁定查询的行)乐观锁就是 version字段(比较跟上一次的版本号,如果一样则更新,如果失败则要重复读比较写的操作。)JDK方面:悲观锁就是sync乐观锁就是原子类(内部使用CAS实现)本质来说,就是悲观锁认为总会有人抢我的。乐观锁就认为,基本没人抢。乐观锁是一种思想,即认为读多写少,遇到并发写的可能性比较低,所以采取在写时先读出 当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重 复读比较写的操作。CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。CAS顶多算是乐观锁写那一步操作的一种实现方式罢了,不用CAS自己加锁也是可以的。乐观锁的业务场景及实现方式每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁, 但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不 进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁, 期间该数据可以被其他线程进行读写操作。乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可 能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量 的查询操作,降低了系统的吞吐量。最后都看到这了,给孩子一个三连支持一下吧,Java对初学者很友好;Java资源丰富,因为它可以解决不同的问题;Java有一个庞大而友好的社区;Java无处不在,因此更容易找到第一份工作;Java开发人员缺口大,薪水很高。只要你具备能力,薪资待遇你敢要公司就敢给。链接:https://bbs.huaweicloud.com/blogs/336625
-
使用Java SDK上传一个对象,如 ```java ObsClient obsClient = xxx; String bucket = "xxx", key = "yyy"; InputStream inputStream = new FileInputStream("xxx.jpg"); PutObjectResult result = obsClient.putObject(bucketName, key, inputStream); ``` 其中result中可以获得该对象的url,通过浏览器访问url,并不会直接显示,而是下载 看到有一个帖子,想像该地址一样,可以在浏览器直接展示图片 [https://hot-image-1.obs.cn-east-3.myhuaweicloud.com/huawei/full/0fa336004bff33b6443b79f3feebf751734992f6.jpg](https://hot-image-1.obs.cn-east-3.myhuaweicloud.com/huawei/full/0fa336004bff33b6443b79f3feebf751734992f6.jpg) 对比后发现我这里的响应多了一个`Content-Disposition: attachement`,这个就是下载,我想把这个响应头属性去掉,但是不知道如何弄  也尝试在上传对象的时候或上传后重新设置元属性,比如以下方式 ```java ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentType("image/jpeg"); // 设置ContentType为jpeg metadata.setContentDisposition("inline"); // 设置ContentDispositon不为attachement,防止下载 PutObjectResult result = obsClient.putObject(bucketName, key, inputStream); ``` 后台看确实设置成功了  但是通过链接访问还是下载,请问如何让该图片直接在浏览器中直接展示呢 ---- ---- 下午补充:貌似现在不支持在线预览了?[华为云对象存储服务OBS禁止通过OBS的默认域名(桶访问域名或静态网站访问域名)在线预览桶内对象](https://support.huaweicloud.com/obs_faq/obs_03_0087.html#obs_03_0087__section178821448171714)。但是为什么上边那个可以预览呢,是因为他是规定颁布之前上传的吗
-
# 基于KML_SPBLAS的JAVA语言适配 ### 项目介绍 本项目的主要任务是为鲲鹏处理器的高性能稀疏线性代数库函数提供Java平台适配,包括向量与矩阵,矩阵与矩阵等共32个函数。基本原理是利用Java语言的JNI调用C语言的高性能库函数接口。 下面详细介绍我们团队在此次项目中的经验总结。 ### JNI简述 **JNI**是Java Native Interface的缩写,为Java程序提供与本地程序交互的能力。使用JNI技术,能够使得Java程序充分利用本地代码的优势,如高性能,不必重复造轮子等,在生产中,有着诸多实用价值。  ### JNI工作流程 JNI的典型使用场景是:Java程序调用C,C++代码编译而成的动态库文件。动态库文件在Windows下是.dll文件,在Linux下为.so文件。其主要工作流程如图所示:  1. 编写.java源代码,其中用native关键字标明需要本地实现的函数。 2. 使用命令 `javac -h 输出位置> 源代码路径>` 编译出.h头文件。 3. 编写.cpp源代码实现.h文件中声明的native函数。 4. 编译.h头文件和.cpp源文件生成.dll动态库,并移动到.java源代码中指定的位置。 5. 使用命令 `javac 源代码路径>` 将.java源代码编译成.class字节码。 6. 使用命令 `java 类名>` 执行类名.class文件,调用.dll库文件得到输出。 ### Hello, world! 这里实现一个简单的Windows版本的demo,带读者来熟悉上述流程。 1. 编写Hello.java源代码 ```java public class Hello { // native 关键字声明native函数 native void sayHelloToC(); // 加载 java2c.dll库文件 static { System.loadLibrary("java2c");} public static void main(String []args){ Hello hello = new Hello(); hello.sayHelloToC(); } } ``` 2. 在Hello.java所在目录,打开cmd命令行,编译出Hello.h头文件。(也一并编译出了Hello.class文件) ```sh javac -encoding utf-8 -h . Hello.java ``` utf-8防止中文注释乱码,. 表示输出到当前路径下。 3. 为了简化较长的.dll编译命令,此处我们借助 Dev-C++ 开发工具来完成C++代码的编写和编译。 1. 打开Dev-C++,新建dll项目,将自动生成dll.h和dllmain.cpp两个文件。 2. 将dll.h的内容替换为Hello.h。 ```c++ /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class Hello */ #ifndef _Included_Hello #define _Included_Hello #ifdef __cplusplus extern "C" { #endif /* * Class: Hello * Method: sayHelloToC * Signature: ()V */ JNIEXPORT void JNICALL Java_Hello_sayHelloToC (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif ``` 3. 清空dllmain.cpp中的内容,修改为如下 ```c++ #include"dll.h" #include // 实现dll.h中声明的函数 JNIEXPORT void JNICALL Java_Hello_sayHelloToC (JNIEnv * env, jobject obj){ printf("Hello, C! I am Java."); } ``` 4. 编译生成.dll库文件。 dll.h的第一行,引入了jni.h头文件。在java安装目录下的include文件夹,找到**jni.h**文件和**jni_md.h**。将它们复制到dev-c++项目的 工具->编译选项->目录->C++包含文件 中的任一目录下。  点击编译,生成hello_c++.dll文件(hello_c++为项目名),将其重命名为java2c.dll(与1中的java源代码保持一致),移动到Hello.java所在的文件夹。 5. 编译Hello.java生成Hello.class字节码,其实在第2步已经顺带完成。 ```sh javac -encoding utf-8 Hello.java ``` 6. 在Hello.class目录下,打开cmd命令行,执行字节码程序。 ```sh java Hello ```  以上实现一个简单的demo,完成Java程序调用C++编写的动态库的目标。通过JNI技术还可以实现Java程序和C/C++程序间的数据传递,下面将一一陈述。 ### 基本数据 | Java类型 | JNI类型 | 描述 | | :------- | :------- | :--------------- | | boolean | jboolean | unsigned 8 bits | | byte | jbyte | signed 8 bits | | char | jchar | unsigned 16 bits | | short | jshort | signed 16 bits | | int | jint | signed 32 bits | | long | jlong | signed 64 bits | | float | jfloat | 32 bits | | double | jdouble | 64 bits | | void | void | void | JNI的基本数据类型与Java类型的对照表如上,下面演示传递基本类型的数据。 Hello.java中添加: ```java native double average(int x, int y); ``` Hello.h中新增: ```c++ /* * Class: Hello * Method: average * Signature: (II)D */ JNIEXPORT jdouble JNICALL Java_Hello_average (JNIEnv *, jobject, jint, jint); ``` java编译程序自动添加了三行注释,Class表示类名,Method表示方法名,Signature是java的函数签名,告诉我们函数的参数类型和返回类型。参数是两个Interger,返回是Double。 函数体代码中,函数返回值为jdouble与java中的double对映,函数名为程序自动生成的**Java _类名 _方法名**形式,JNIEnv参数为jni环境,jobject为native函数所在的java对象,这两个是jni自带的。后面两个jint参数对应原本java方法的int参数。 在dllmain.cpp中实现average的逻辑: ```c++ JNIEXPORT jdouble JNICALL Java_Hello_average (JNIEnv * env, jobject obj, jint x, jint y){ return jdouble(x + y) / 2; } ``` 执行输出: ```java System.out.println(hello.average(1, 2)); // 1.5 ``` ### 字符串 JNI的字符串处理函数丰富全面,此处演示传递字符串的简单例子,并以此引出JNI处理复杂数据类型时的一般流程。更多API文件见参考链接的官方文档。 Hello.java中添加: ```java native String sendMessage(String msg); ``` Hello.h中生成: ```c++ /* * Class: Hello * Method: sendMessage * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_Hello_sendMessage (JNIEnv *, jobject, jstring); ``` dllmain.cpp中实现: ```c++ JNIEXPORT jstring JNICALL Java_Hello_sendMessage (JNIEnv * env, jobject obj, jstring msg){ // 1.convert jstring to cstring const char* cMsg = env->GetStringUTFChars(msg, NULL); if(NULL == cMsg) return NULL; // 2.use cstring printf("Java: %s\n", cMsg); // 3.release resources env->ReleaseStringUTFChars(msg, cMsg); // 4.return const char* cMsg2 = "Good afternoon! Miss.Java."; return env->NewStringUTF(cMsg2); } ``` 注意:JNI中需要手动显式地释放资源,否则会造成内存泄漏。 执行输出: ```java String msg = "Good morning! Mr.C++."; System.out.println("C++: " + hello.sendMessage(msg)); // Java: Good morning! Mr.C++. // C++: Good afternoon! Miss.Java. ``` ### 对象 JNI同样可以传递对象,也就是说,JNI让C/C++代码能够访问Java类中的成员变量和方法。 从这里开始,我们使用Maven来管理我们的代码。 新建maven项目,其目录结构如下:  Hello.java的代码如下 ```java package org.example; public class Hello { static { System.loadLibrary("java2c"); } private int num = 2021; private void printNum(){ System.out.println("In Java, num is " + num); } // 演示访问成员变量和方法 native void jniMethod(); public static void main(String []args){ Hello hello = new Hello(); hello.jniMethod(); } } ``` 此处引入了java包,且Hello中导入了Color类,所以如果直接在Hello.java同级文件夹下调用**javac**命令,会报找不到符号的错误,应在所有源文件的根目录,即maven项目的java文件夹下,调用**javac**命令。这样编译器能将包名和路径对应起来。 进入src\main\java路径,运行: ```sh javac -encoding utf-8 -h . ./org/example/Hello.java ``` 如果源文件还引入了其他位置的包,如test文件夹下的源文件引入了main文件夹下的包,可使用`javac -cp 依赖路径>`告知java编译器依赖包的位置。更多javac选项,可使用`javac -help`自行查看。 Hello.h中生成: ```c++ /* * Class: org_example_Hello * Method: jniMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_org_example_Hello_jniMethod (JNIEnv *, jobject); ``` dllmain.h中实现定义的函数: ```c++ JNIEXPORT void JNICALL Java_org_example_Hello_jniMethod (JNIEnv* env, jobject obj){ jclass cls = env->GetObjectClass(obj); // fieldID is certain for a class jfieldID numField = env->GetFieldID(cls, "num", "I"); // get number jint num = env->GetIntField(obj, numField); printf("In C++, num is %d\n", num); // change number env->SetIntField(obj, numField, 2035); // call method jmethodID printNumField = env->GetMethodID(cls, "printNum", "()V"); env->CallVoidMethod(obj, printNumField); return; } ``` 执行编译,在maven项目中编译执行的路径不再是源文件所在的路径。为简单起见,我们将生成的dll文件放到系统环境变量中,以便java虚拟机能够找到。执行输出得到: ```c++ public static void main(String []args){ Hello hello = new Hello(); hello.jniMethod(); } // In Java, num is 2035 // In C++, num is 2021 ``` 通过JNI,获取对象的成员变量或调用对象的主要过程如下: 1. `GetObjectClass`通过jobject获得jclass,或者`FindClass`通过类名直接获得jclass 2. 通过jclass获得jfieldID获得jmethodID。 3. 使用jfieldID获取成员变量的值或使用jmethodID调用对象的方法。 dllmain.cpp中的"I"、"()V"是java的签名字符串,可以在终端通过`javap`命令获取。 ### 数组 jni也可以传递基本类型的数组和对象数组,此处通过向量加法来演示此功能。 Hello.java中新增: ```java native void vectorAdd(int[] a, int[] b, int[] c); public static void main(String []args){ Hello hello = new Hello(); int []a = {1, 2, 3}; int []b = {4, 5, 6}; int []c = new int[3]; hello.vectorAdd(a, b, c); for(int i:c){ System.out.print(i + " "); } ``` `javac -h`重新编译后,Hello.h中生成: ```c++ /* * Class: org_example_Hello * Method: vectorAdd * Signature: ([I[I[I)V */ JNIEXPORT void JNICALL Java_org_example_Hello_vectorAdd (JNIEnv *, jobject, jintArray, jintArray, jintArray); ``` 在dllmain.h中实现上述函数: ```c++ JNIEXPORT void JNICALL Java_org_example_Hello_vectorAdd (JNIEnv *env, jobject obj, jintArray a, jintArray b, jintArray c){ // 1.convert JNI jintArray to C jint[] jint *cA = env->GetIntArrayElements(a, NULL); jint *cB = env->GetIntArrayElements(b, NULL); jint *cC = env->GetIntArrayElements(c, NULL); jint len = env->GetArrayLength(a); // 2.use array for(jint i=0; i+ cB[i]; } // 3.release resources env->ReleaseIntArrayElements(a, cA, JNI_ABORT); env->ReleaseIntArrayElements(b, cB, JNI_ABORT); env->ReleaseIntArrayElements(c, cC, 0); return; } ``` 函数依旧分为:转化数据、使用数据、释放资源三步。 Release函数第三个参数为mode,有三个备选值如下: | mode | 行为 | | :--------- | :-------------------------------------------------------- | | 0 | copy back the content and free the elems buffer | | JNI_COMMIT | copy back the content but do not free the elems buffer | | JNI_ABORT | free the buffer without copying back the possible changes | 编译后执行得输出如下: ``` 5 7 9 ``` 对象数组与基本类型数组的方法相似,但是没有**Get``ArrayElements**对数组元素进行批量转化,究其原因,Java对象不能直接转化为C/C++的对象。 使用如下两个函数,可以操作基本数据类型数组的直接指针,极大的加快程序运行的效率。但同时,我们要保证在调用ReleasePrimitiveArrayCritical函数之前,不能进行任何可能导致线程阻塞的操作。 void * **GetPrimitiveArrayCritical**(JNIEnv *env, jarray array, jboolean *isCopy); void **ReleasePrimitiveArrayCritical**(JNIEnv *env, jarray array, void *carray, jint mode); ### 工程经验 #### c语言宏定义 观察`javac -h`生成的函数声明,我们会发现都是如下结构: ``` Java_包名_方法名 JNIEXPORT void JNICALL Java_org_example_Hello_jniMethod(...) JNIEXPORT void JNICALL Java_org_example_Hello_vectorAdd(...) ``` 当程序越来越复杂,我们调整Java的包结构,每一个地方都要修改。可以使用C++的宏替换来减少这种冗余如下: ```c++ #define FULL_FUNC_NAME(SHORT_NAME) Java_org_example_Hello_##SHORT_NAME JNIEXPORT void JNICALL FULL_FUNC_NAME(jniMethod)(...) JNIEXPORT void JNICALL FULL_FUNC_NAME(vectorAdd)(...) ``` #### 将dll动态库打入jar包 首先我们要明白Java加载动态库的方式: 1. **System.load** System.load加载绝对路径下的库文件,如: ```java System.load("D:\\workplace\\java2c.dll"); ``` 2. **System.loadLibrary** System.loadLibrary加载相对路径下的库文件,参数为库文件名,不包含库文件的扩展名,如: ```java System.loadLibrary("java2c"); ``` 这里java2c.dll必须在**java.library.path**这一jvm变量指向的路径中。 可以通过如下方法来获得该变量的值: ```java System.getProperty("java.library.path"); ``` 默认情况下,在Windows平台下,该值包含如下位置: 1)和jre相关的一些目录 2)程序当前目录 3)Windows目录 4)系统目录(system32) 5)系统环境变量path指定目录 上述两个函数都是以路径作为参数的,但是jar包中的文件没有路径,只能以文件流的形式获取。为了能将动态库文件打入jar包,并且能够顺利调用,我们的策略是将jar包中的动态库写入系统临时目录下,再调用System.load载入,参考代码如下: ```java static { // copy .so from jar to syetem tmp dir String libName = "java2c.dll"; String nativeTempDir = System.getProperty("java.io.tmpdir"); File extractedLibFile = new File(nativeTempDir + File.separator + libName); InputStream in = null; BufferedInputStream reader = null; FileOutputStream writer = null; if(!extractedLibFile.exists()){ try{ in = Level2.class.getClassLoader().getResourceAsStream(libName); reader = new BufferedInputStream(in); writer = new FileOutputStream(extractedLibFile); byte[] buffer = new byte[1024]; while(reader.read(buffer) > 0){ writer.write(buffer); } }catch(IOException e){ e.printStackTrace(); }finally { if (in != null){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if (writer != null){ try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } } System.load(extractedLibFile.toString()); } ``` ### 参考链接 [JNIdemo图文](https://blog.csdn.net/WeiHao0240/article/details/99568579) [JNI完全指南文档](https://www.zybuluo.com/cxm-2016/note/563686) [JNI 官方文档](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html)
-
很荣幸参加《鲲鹏Java性能分析工具IntelliJ插件实现》项目开发工作的,我们项目主要是做Inellij插件开发实现性能分析工具功能。由于插件开发的特殊性,常常需要和一些命令行打交道,下面记录一次通过Java代码打开Windows证书安装窗口安装证书cer文件。思路如下:第一步:下载证书,保存到本地 通过cer文件下载接口将文件保存到本地,记录文件的保存位置第二步:通过代码在IDEA的Terminal命令窗口输入命令打开证书安装窗口 // 将项目对象,ToolWindow的id传入,获取控件对象 ToolWindow toolWindow = ToolWindowManager.getInstance(e.getProject()).getToolWindow("Terminal"); if (toolWindow != null) { // 无论当前状态为关闭/打开,进行强制打开ToolWindow toolWindow.show(new Runnable() { @Override public void run() { Process p = Runtime.getRuntime().exec("rundll32.exe cryptext.dll,CryptExtAddCER "+ path); } }); } }第三步:提醒用户重启IDEA,使证书生效 Messages.showMessageDialog("安装证书", "安装证书后请重启IDEA,证书才能生效", Messages.getInformationIcon());
-
IDEA是目前Java开发人员最受欢迎的工具开发之一,很重要的原因是其提供了很多插件,这让开发人员降低了开发难度,能更有效更高质量的开发代码,我也很荣幸成为IDEA插件开发的一员。 我所在的是鲲鹏Java性能分析工具IntelliJ这个开源项目,刚开始参与进来的时候感觉这个项目很难,因为对Java Swing这块的技术了解的很少。长时间下来感觉挺有意思的,以下我是我对Swing这块的总结: Swing图形组件是java中一套图形界面类,主要分为两类:容器类和元素类。容器类表示可以放置其他的元素组件或者容器组件,JFrame 窗体组件,JPanel 面板容器,JMeauBar 菜单栏;元素类表示不可以放其他的组件,比如:JButton 按钮组件,JLable 文本标签组件,JTextField 文本输入框组件,JPasswordField 密码输入框组件,JCheckBox 复选框组件等。主要就是在合理布局下各种组件之间的搭配,其中还有很多重要的事件,如:ActionEvents,ChangeEvents,ItemEvents等,来响应用户的鼠标点击等行为。 每当感觉自己已经掌握了Swing这门技术的时候,但是在开发过程中处处有惊喜,总会碰到这样或那样的难题,因此还是得通过不断的学习来提高自己,这样才能更好、更有效更轻松的为这个团队服务。加油!
-
链接:https://bbs.huaweicloud.com/blogs/3359231 偏向锁的意义无多线程竞争时,减少不必要的轻量级锁执行路径。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一条线程去多次获得锁,为了让线程获得锁的性能代价更低而引入了偏向锁。偏向锁主要用来优化同一线程多次申请同一个锁的竞争,即当对象被当做同步锁并有一个线程抢到了锁时,则在Mark Word设置该线程的线程ID、是否偏向锁设置1、锁标志位设置01等信息,此时的Mark Word 存储的就是偏向锁状态信息。在:创建一个线程并在线程中执行循环监听的场景下或单线程操作一个线程安全集合时同一线程每次都需获取和释放锁,每次操作都会发生用户态与内核态的切换。获取偏向锁的场景:在自己的线程栈生成一条Lock Record,然后Object Reference指向对象头,此时Lock Record与对象头就建立了联系:① : 先判断Mard Word的Thread ID是否有值没有,则表示当前资源没有被其他线程占用,把当前线程ID等信息记录到Mark Word(这需CAS,可能多条线程修改Mark Word,需要保证原子性)有,则表示当前资源被线程占用,需要判断该线程是不是自己该线程ID是自己的,则表示可重入,直接获取(此时在自己的线程栈中继续生成一条新的Lock Record)该线程ID不是自己的,说明出现其他线程竞争,当前持有偏向锁的线程就需要撤销了,即当其他线程尝试获取偏向锁才释放锁轻量级锁的获取及释放依赖多次的CAS操作,而偏向锁只依赖一次CAS置换ThreadID。一旦出现多个线程竞争时必须撤销偏向锁,所以:撤销偏向锁消耗的性能必须 < 之前节省下来的CAS原子操作的性能消耗不然得不偿失!JDK6默认开启偏向锁,可通过-XX:-UseBiasedLocking禁用偏向锁。2 偏向锁的获取偏向锁的入口,synchronizer.cpp 文件的ObjectSynchronizer::fast_enter由BiasedLocking::revoke_and_rebias实现2.1 markOop mark = obj->mark()获取对象的markOop数据mark,即对象头的Mark Word2.2 判断mark是否为可偏向状态mark的偏向锁的锁标志位为 012.3 判断mark中JavaThread的状态若指向当前线程,则执行同步代码块若为空,则走4若指向其它线程,则走52.4 执行CAS原子指令设置mark中JavaThread为当前线程ID。若CAS成功,则执行同步代码块,否则走5。2.5 执行CAS失败说明当前存在多个线程竞争锁,当达到全局安全点(safepoint),获得偏向锁的线程就会被挂起,撤销偏向锁,并升级为轻量级锁。升级完成后被阻塞在安全点的线程继续执行同步代码块。BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) { assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint"); // We can revoke the biases of anonymously-biased objects // efficiently enough that we should not cause these revocations to // update the heuristics because doing so may cause unwanted bulk // revocations (which are expensive) to occur. // step1 markOop mark = obj->mark(); if (mark->is_biased_anonymously() && !attempt_rebias) { // We are probably trying to revoke the bias of this object due to // an identity hash code computation. Try to revoke the bias // without a safepoint. This is possible if we can successfully // compare-and-exchange an unbiased header into the mark word of // the object, meaning that no other thread has raced to acquire // the bias of the object. markOop biased_value = mark; markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED; } } else if (mark->has_bias_pattern()) { Klass* k = obj->klass(); markOop prototype_header = k->prototype_header(); if (!prototype_header->has_bias_pattern()) { // This object has a stale bias from before the bulk revocation // for this data type occurred. It's pointless to update the // heuristics at this point so simply update the header with a // CAS. If we fail this race, the object's bias has been revoked // by another thread so we simply return and let the caller deal // with it. markOop biased_value = mark; markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark); assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked"); return BIAS_REVOKED; } else if (prototype_header->bias_epoch() != mark->bias_epoch()) { // The epoch of this biasing has expired indicating that the // object is effectively unbiased. Depending on whether we need // to rebias or revoke the bias of this object we can do it // efficiently enough with a CAS that we shouldn't update the // heuristics. This is normally done in the assembly code but we // can reach this point due to various points in the runtime // needing to revoke biases. if (attempt_rebias) { assert(THREAD->is_Java_thread(), ""); markOop biased_value = mark; markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED_AND_REBIASED; } } else { markOop biased_value = mark; markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED; } } } } HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias); if (heuristics == HR_NOT_BIASED) { return NOT_BIASED; } else if (heuristics == HR_SINGLE_REVOKE) { Klass *k = obj->klass(); markOop prototype_header = k->prototype_header(); if (mark->biased_locker() == THREAD && prototype_header->bias_epoch() == mark->bias_epoch()) { // A thread is trying to revoke the bias of an object biased // toward it, again likely due to an identity hash code // computation. We can again avoid a safepoint in this case // since we are only going to walk our own stack. There are no // races with revocations occurring in other threads because we // reach no safepoints in the revocation path. // Also check the epoch because even if threads match, another thread // can come in with a CAS to steal the bias of an object that has a // stale epoch. ResourceMark rm; if (TraceBiasedLocking) { tty->print_cr("Revoking bias by walking my own stack:"); } EventBiasedLockSelfRevocation event; BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD, NULL); ((JavaThread*) THREAD)->set_cached_monitor_info(NULL); assert(cond == BIAS_REVOKED, "why not?"); if (event.should_commit()) { event.set_lockClass(k); event.commit(); } return cond; } else { EventBiasedLockRevocation event; VM_RevokeBias revoke(&obj, (JavaThread*) THREAD); VMThread::execute(&revoke); if (event.should_commit() && (revoke.status_code() != NOT_BIASED)) { event.set_lockClass(k); // Subtract 1 to match the id of events committed inside the safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.set_previousOwner(revoke.biased_locker()); event.commit(); } return revoke.status_code(); } } assert((heuristics == HR_BULK_REVOKE) || (heuristics == HR_BULK_REBIAS), "?"); EventBiasedLockClassRevocation event; VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD, (heuristics == HR_BULK_REBIAS), attempt_rebias); VMThread::execute(&bulk_revoke); if (event.should_commit()) { event.set_revokedClass(obj->klass()); event.set_disableBiasing((heuristics != HR_BULK_REBIAS)); // Subtract 1 to match the id of events committed inside the safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.commit(); } return bulk_revoke.status_code(); }3 偏向锁的撤销只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销由BiasedLocking::revoke_at_safepoint实现:void BiasedLocking::revoke_at_safepoint(Handle h_obj) { assert(SafepointSynchronize::is_at_safepoint(), "must only be called at safepoint"); oop obj = h_obj(); HeuristicsResult heuristics = update_heuristics(obj, false); if (heuristics == HR_SINGLE_REVOKE) { revoke_bias(obj, false, false, NULL, NULL); } else if ((heuristics == HR_BULK_REBIAS) || (heuristics == HR_BULK_REVOKE)) { bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL); } clean_up_cached_monitor_info(); }偏向锁的撤销动作必须等待全局安全点(safepoint,GC时会让所有线程阻塞的停顿点)暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态撤销偏向锁,恢复到无锁(标志位 01)或轻量级锁(标志位 00)状态偏向锁在Java 1.6后默认启用,但在应用程序启动几s后才激活,可关闭延迟:-XX:BiasedLockingStartupDelay=0若确定应用程序中所有锁通常情况下处于竞争状态,可关闭偏向锁:XX:-UseBiasedLocking=false(默认打开)偏向锁的释放遍历线程栈的所有Lock Record,把ObjectReference切断,即ObjectReference = null.把ObjectReference置null,但锁对象的对象头的Mark Word还是没改变,依然偏向之前的线程,那还是没释放锁的嘛,的确是,线程退出临界区时候,并没有释放偏向锁,这么做是为 : 当再次需要获取锁时,只需要简单判断是否是重入,即可快速获取锁,而不用每次都CAS,这也是偏向锁在只有一个线程访问锁的情景下高效的核心。总结当出现锁资源访问的时候,都会在当前线程栈生成一条Lock Record,并且ObjectReference将指向锁对象的对象头 的Mark Word,该设置可能出现多线程,需CAS操作多线程情况下竞争同一个锁资源,偏向锁的撤销会影响效率偏向锁的重入计数依靠线程栈里Lock Record个数偏向锁撤销失败,最终会升级为轻量级锁偏向锁退出时并没有修改Mark Word,也就是没有释放锁偏向锁相对轻量级锁来说,当同一线程去再次获取锁的时候,不用进行CAS操作,提高了性能.(轻量级锁在同一线程情况下每次去获取锁,在无锁的状态下,每次都要进行一次CAS操作)偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁偏向锁的撤销是很复杂,成为理解代码的障碍,也阻碍了对同步系统重构,而且现如今基本都是多核系统,偏向锁的劣势越来越明显,所以在Java 15废弃了偏向锁】
-
原文链接:https://bbs.huaweicloud.com/blogs/335767大风吹去了往日的雾霾,阳光透过窗户照进来,透过窗户可以看到远处的山脉与蓝天相接,这可比我那永远见不到阳光的出租屋好多了。渐渐走进的脚步声打断了我的思绪,一位小姐姐坐在了面前,甜甜的香水味立刻钻进了我的鼻孔。小姐姐微笑地说:”您好,我是今天的面试官,那么开始吧?“我收起直勾勾的眼睛,说:“好的。”小姐姐说:“在Java的异常处理中有两大组成要素:抛出异常和捕获异常。那么抛出异常可以分为哪两种呢?”我立刻回答到:抛出异常可以分为显式和隐式。显式抛异常是在代码中使用throw关键字手动将异常实例抛出。隐式抛异常是 Java 虚拟机在执行过程中,遇到无法继续执行的异常状态,自动将异常实例抛出,比如我们经常遇到的空指针异常(NullPointerException)。小姐姐说:“很好,那么捕获异常中经常用到哪些关键字呢?”我立刻回答到:我们一般用到try、catch、finally等关键字。try被用来标记需要进行异常监控的代码;catch被用来捕获在try 监控的代码中触发的某种指定类型的异常,还可以定义针对该异常类型进行如何处理;finally被用来声明一段无论发生什么异常都必定运行的代码,它避免跳过某些关键的清理代码,比如:关闭已打开的IO资源。小姐姐说:“很好,如果三个关键字一起使用,代码执行的顺序是什么样子的?”我立刻回答到:在正常执行的情况下,先执行try中的代码再执行finally中的代码。如果try中的代码触发异常,并且异常没有被捕获,finally中代码会被直接执行,并且在执行之后重新抛出该异常;如果异常被catch捕获,先执行catch中的代码再执行finally中的代码。如果catch中的代码也触发了异常,那么finally中代码同样会被执行,并会抛出catch代码触发的那个异常。如果finally中的代码也触发了异常,那么会中断当前finally 代码的执行,并抛出异常。小姐姐说:“很好,在Java虚拟机中,是通过什么方式实现异常处理?”这个问题有点难度,我稍微思考了一下回答到:主要是通过异常表。在编译生成的字节码中,每个方法都附带一个异常表。异常表中可能有多条记录,每一条记录都包括from指针、to 指针、target指针和所捕获的异常类型。这些指针的值是字节码索引(bytecode index),用于定位字节码。其中,from指针和to指针表示异常处理监控的范围,比如: try所覆盖的范围。target指针指向异常处理代码的起始位置,比如:catch代码的起始位置。如果有异常触发时,Java虚拟机会从上至下遍历异常表中的所有记录。当触发异常的字节码的索引值在某个异常表记录的监控范围内,Java虚拟机会判断所抛出的异常和该记录想要捕获的异常是否匹配。如果匹配,控制流转将会移至该记录 target 指针指向的字节码。小姐姐说:“有点抽象,可以举个例子吗?”“当然可以。”我一边说,一边在纸上写了起来:public class OneMore { public static void main(String[] args) { String str = "万猫学社"; try { str = "try"; } catch (Exception e) { str = "catch"; } } }这段代码的 main 方法中,我定义了一段try 和catch 代码。编译过后的字节码中,这个方法的异常表拥有一个记录:public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=3, args_size=1 0: ldc #2 // String 万猫学社 2: astore_1 3: ldc #3 // String try 5: astore_1 6: goto 13 9: astore_2 10: ldc #5 // String catch 12: astore_1 13: return Exception table: from to target type 3 6 9 Class java/lang/Exception其from指针和to指针分别为 3 和 6,代表它的监控范围从索引为 3 的字节码开始,到索引为 6 的字节码结束(不包括 6)。该记录的 target 指针是 9,代表这个异常处理从索引为 9 的字节码开始。该记录的最后一列,代表该异常处理所捕获的异常类型是Exception。当触发异常的字节码的索引值在 3 和 6 之间时,Java虚拟机会判断所抛出的异常是否时Exception。如果是,控制流转将会移至索引为 9 的字节码开始执行。小姐姐满意地说:“很好,我去叫HR和你聊聊,如果顺利的话,今天就可以发offer。”参考文献:《Java编程思想》《Java核心技术》《深入理解Java虚拟机:JVM高级特性与实践》
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签